Skip to content

Commit 87be3fc

Browse files
authored
Merge pull request #38 from cgay/dev
Handle rolling logs multiple times within one second
2 parents 295f5bb + 1895ba6 commit 87be3fc

4 files changed

Lines changed: 71 additions & 6 deletions

File tree

documentation/source/index.rst

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,19 @@ log still logs to ancestor logs if they are themselves enabled.
8585
Errors
8686
------
8787

88-
If there is an error when parsing a :class:`<log-formatter>` format
89-
control string or in finding a :class:`<log>` object by name, a
90-
:class:`<logging-error>` will be signaled.
91-
9288
.. class:: <logging-error>
9389
:open:
9490

9591
:superclasses: :drm:`<error>`, :class:`<simple-condition>`
9692

93+
:description:
94+
95+
Errors explicitly signaled by this library are instances of
96+
:class:`<logging-error>`. Examples of when this error is signaled:
97+
98+
* Parsing a bad :class:`<log-formatter>` format control string.
99+
* Can't find a :class:`<log>` object by name.
100+
* Can't roll a log file due to file name conflicts.
97101

98102
Log Levels
99103
----------
@@ -435,7 +439,21 @@ Log Targets
435439
:keyword roll:
436440
A :drm:`<boolean>` specifying whether to roll the log file at the
437441
time this log target is created, if it already exists and is not
438-
empty.
442+
empty. The default is :drm:`#t`.
443+
444+
:description:
445+
446+
A file log target that is "rolled" when it reaches a maximum size. Rolling the log
447+
file consists of renaming the current file to a name that contains the date the
448+
original file was *created*. For example, :file:`foo.log` might be renamed to
449+
:file:`foo.log.20260303T185404`. (Note that the date is in local time.)
450+
451+
If the log file is rolled multiple times within the same second (e.g., due to a
452+
crash loop in your program) it will encounter file name conflicts. To resolve the
453+
conflict the file is renamed with an additional numeric suffix concatenated to the
454+
date, for example :file:`foo.log.20260303T185404.1`. After the suffix reaches
455+
``.9`` and the file still can't be renamed without clobbering an existing file, a
456+
:class:`<logging-error>` is signaled.
439457

440458
.. class:: <stream-log-target>
441459
:open:

library.dylan

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ define module logging-impl
9999
use locators,
100100
import: { <locator>,
101101
<file-locator>,
102+
file-locator,
103+
locator-directory,
102104
locator-name,
103105
merge-locators,
104106
simplify-locator };

logging.dylan

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,22 @@ define method roll-log-file
706706
let newloc = merge-locators(as(<file-locator>,
707707
concatenate(locator-name(oldloc), ".", date)),
708708
oldloc);
709-
rename-file(oldloc, newloc);
709+
// Try renaming the file up to 10 times and then give up.
710+
iterate loop (attempt = 1) // first numbered file is .1
711+
block ()
712+
rename-file(oldloc, newloc, if-exists: #"signal");
713+
// TODO: handle <file-exists-error> once
714+
// https://github.com/dylan-lang/opendylan/pull/1801 lands in an OD release.
715+
exception (err :: <file-system-error>)
716+
if (attempt >= 10)
717+
logging-error("can't roll log file %s (10 attempts)", oldloc);
718+
end;
719+
newloc := file-locator(newloc.locator-directory,
720+
format-to-string("%s.%s.%d",
721+
oldloc.locator-name, date, attempt));
722+
loop(attempt + 1);
723+
end;
724+
end iterate;
710725
target.file-roll-date := current-date();
711726
open-target-stream(target);
712727
end with-lock;

tests/logging-test-suite.dylan

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,33 @@ define test test-rolling-log-file-absolutism ()
208208
working-directory() := cwd;
209209
end block;
210210
end test;
211+
212+
// Ensure that the file can roll 9 times without error and that files with numeric
213+
// uniquifiers are created. (Don't check that a max of 10 rolls can occur within a
214+
// second because that could easily cause false failures due to the test depending on the
215+
// value of the clock. I verified it manually. --cgay)
216+
define test test-rolling-log-file-subsecond-rolling ()
217+
let dir = test-temp-directory();
218+
let pathname = file-locator(dir, "foo.log");
219+
let formatter = make(<log-formatter>, pattern: "%m");
220+
let target = make(<rolling-file-log-target>,
221+
pathname: pathname,
222+
max-size: 1); // roll whenever something is logged
223+
for (i from 1 to 10)
224+
log-to-target(target, $info-level, formatter, "woof", #[]);
225+
end;
226+
let files = directory-contents(dir);
227+
assert-true(files.size >= 9);
228+
let found = 0;
229+
local
230+
method is-subsecond-file? (loc :: <locator>)
231+
let name = loc.locator-name; // ex: "foo.log.20260303T180404.1"
232+
let parts = split(name, '.');
233+
if (parts.size == 4)
234+
let n = string-to-integer(parts[3]);
235+
assert-true(n >=1 & n <= 9, "%s is between 1 and 9 inclusive", n);
236+
#t
237+
end;
238+
end;
239+
assert-true(any?(is-subsecond-file?, files));
240+
end test;

0 commit comments

Comments
 (0)