Skip to content

BGLogger writes every day archive into one file named "<App>yyyy-MM-dd.log", so log history and MaxArchiveFiles do not work #137

Description

@karlspace

Problem

BGLoggerConfiguration sets up daily archiving of the file target like this (main, 02325dd; the file is identical in v3.0.8):

  • src/BAUERGROUP.Shared.Core/Logging/BGLoggerConfiguration.cs:550 TargetFile.ArchiveFileName = @"${gdc:item=LogDirectory}\${gdc:item=ApplicationName}.{#}.log";
  • src/BAUERGROUP.Shared.Core/Logging/BGLoggerConfiguration.cs:551 TargetFile.ArchiveSuffixFormat = "yyyy-MM-dd";
  • BGLoggerConfiguration.cs:552-553 ArchiveEvery = FileArchivePeriod.Day; and MaxArchiveFiles = 30;

The library uses NLog 6.1.3 (Directory.Packages.props). In NLog 6, ArchiveSuffixFormat is not a date format. It is a string.Format pattern where {0} is the archive sequence number and {1} is the archive date:

  • NLog v6.1.3 src/NLog/Targets/FileTarget.cs:1119: string.Format(cultureInfo, ArchiveSuffixFormat, sequenceNumber, fileLastModifiedObj)
  • NLog wiki, File-target, archiveSuffixFormat: "Works like string.Format where {0} outputs archive-sequence-number and {1} outputs the archiveFileName-timestamp. Default: _{0:00}."

"yyyy-MM-dd" has no placeholder, so every archive gets the same literal suffix yyyy-MM-dd. NLog reports no error. At startup it formats the suffix once and would report a failure as a configuration error (FileTarget.cs:568-571, 1117-1128), but string.Format does not fail on a format without placeholders. {#} is obsolete in NLog 6, and the ArchiveFileName setter removes .{#} (FileTarget.cs:328-333). The archive path is therefore <App> + yyyy-MM-dd + .log = <App>yyyy-MM-dd.log (FileTarget.cs:1095-1110).

Because ArchiveFileName is set, NLog uses LegacyArchiveFileNameHandler (FileTarget.cs:1396-1399):

  • First day change: <App>.log is moved to <App>yyyy-MM-dd.log.
  • Every later day change: the suffix has no {0}, so NLog cannot find a sequence number and uses 0 (LegacyArchiveFileNameHandler.cs:261-264). The archive file already exists, so NLog appends the active file to it and truncates the active file (LegacyArchiveFileNameHandler.cs:185-190).

This came in with the NLog 6 migration in bbd5318 (2026-01-16, first released in v2.0.1). That commit replaced ArchiveNumbering = ArchiveNumberingMode.Date and ArchiveDateFormat = "yyyy-MM-dd" with ArchiveSuffixFormat = "yyyy-MM-dd". NLog's own translation of the obsolete ArchiveDateFormat would have been _{1:yyyy-MM-dd}_{0:00} (FileTarget.cs:255).

Observed on 2026-09-15 on Windows 11 (10.0.26200). The test was a console app that references Shared.Core 3.0.8 (NLog 6.1.3). It uses the library's configuration unchanged, except that LogDirectory points to a temp folder. It forces real day changes with an NLog TimeSource that moves forward one day per step, not with size-based archiving. Result after 4 day changes (NLog internal log, info level):

Archive moving file from '<root>\Logging\LogArchiveProbe.log' to '<root>\Logging\LogArchiveProbeyyyy-MM-dd.log'
Archive appending to already existing file: <root>\Logging\LogArchiveProbeyyyy-MM-dd.log
Cleanup file archive Truncate Active File. Delete file: '<root>\Logging\LogArchiveProbe.log'
Archive appending to already existing file: <root>\Logging\LogArchiveProbeyyyy-MM-dd.log
Cleanup file archive Truncate Active File. Delete file: '<root>\Logging\LogArchiveProbe.log'
Archive appending to already existing file: <root>\Logging\LogArchiveProbeyyyy-MM-dd.log
Cleanup file archive Truncate Active File. Delete file: '<root>\Logging\LogArchiveProbe.log'

LogArchiveProbe.log              362 bytes   3 lines  days=[4]
LogArchiveProbeyyyy-MM-dd.log   1442 bytes  12 lines  days=[0,1,2,3]

The result on Ubuntu 26.04 (WSL, native Linux file system) is the same. The only difference there is the backslash in the file names from #134.

Retention does not work. The library sets only MaxArchiveFiles = 30. MaxArchiveDays is not set, and its default of 0 turns it off. Cleanup counts the files that match <App>*.log (LegacyArchiveFileNameHandler.cs:92-96, wildcard from BaseFileArchiveHandler.cs:378-431). There is only ever one archive file, so the limit is never reached (BaseFileArchiveHandler.cs:84-86). Nothing is deleted, and the file grows without limit, stored as UTF-16 (BGLoggerConfiguration.cs:554). Test with MaxArchiveFiles = 3 set at runtime and 6 day changes: no file was deleted, and LogArchiveProbeyyyy-MM-dd.log held days 0-5 (18 lines). MaxArchiveDays would not help either. It checks a file's creation time (BaseFileArchiveHandler.cs:326-341), so after N days it would delete the whole combined file, including yesterday's log. That last point comes from reading the source and was not tested.

Proposal

Put the date placeholder into the suffix and remove the obsolete {#}:

TargetFile.ArchiveFileName = "${gdc:item=LogDirectory}${dir-separator}${gdc:item=ApplicationName}.log"; // ${dir-separator}: see #134
TargetFile.ArchiveSuffixFormat = "_{1:yyyy-MM-dd}_{0:00}";

Archives are then named <App>_2026-09-14_00.log. The date is the last write time of the archived file (LegacyArchiveFileNameHandler.cs:165-167), which is the day its lines belong to. From the source, _01 should appear only if the same day is archived twice; that case was not tested. The active file stays <App>.log, so the log window and tail tools keep working as before.

Verified with the same console app. Changing only ArchiveSuffixFormat at runtime, on Windows and on Linux:

Archive moving file from '<root>\Logging\LogArchiveProbe.log' to '<root>\Logging\LogArchiveProbe_2026-09-15_00.log'
Archive moving file from '<root>\Logging\LogArchiveProbe.log' to '<root>\Logging\LogArchiveProbe_2026-09-16_00.log'
Archive moving file from '<root>\Logging\LogArchiveProbe.log' to '<root>\Logging\LogArchiveProbe_2026-09-17_00.log'
Archive moving file from '<root>\Logging\LogArchiveProbe.log' to '<root>\Logging\LogArchiveProbe_2026-09-18_00.log'

With MaxArchiveFiles = 3 and 6 day changes, retention works:

Cleanup file archive MaxArchiveFiles=3. Delete file: '<root>\Logging\LogArchiveProbe_2026-09-15_00.log'
Cleanup file archive MaxArchiveFiles=3. Delete file: '<root>\Logging\LogArchiveProbe_2026-09-16_00.log'
Cleanup file archive MaxArchiveFiles=3. Delete file: '<root>\Logging\LogArchiveProbe_2026-09-17_00.log'

LogArchiveProbe.log                3 lines  days=[6]
LogArchiveProbe_2026-09-18_00.log  3 lines  days=[3]
LogArchiveProbe_2026-09-19_00.log  3 lines  days=[4]
LogArchiveProbe_2026-09-20_00.log  3 lines  days=[5]

The exact two lines above, including ${dir-separator}, gave the same archive names and the same deletions on Windows and on Ubuntu 26.04 (WSL). On Linux the archives land in a real Logging directory. The active file keeps the backslash until FileName is fixed as well (#134).

Existing installations: the old <App>yyyy-MM-dd.log also matches the cleanup wildcard. It is the oldest file, so it is deleted once the limit is reached. This was verified with MaxArchiveFiles = 3: after two new archives, the old file was deleted right before the third new archive was written. With the default of 30, it stays for about a month. It can be large, so it is worth a line in the release notes.

Alternatives:

  • .{1:yyyy-MM-dd} restores the names from before v2.0.1 (<App>.2026-09-14.log, verified). Without {0}, a second archive for the same date is appended to that file (LegacyArchiveFileNameHandler.cs:185-190). That is harmless with daily archiving only, but it breaks if ArchiveAboveSize is added later.
  • The handler NLog 6 prefers: no ArchiveFileName and _{0:00} (FileTarget.cs:307-309, 1396-1397). It avoids moving files, but the active file changes name instead. After 3 day changes, <App>.log still held day 0 and the current log was <App>_03.log (verified). That breaks the rule that <App>.log is the current log, and the names carry no date.
  • Leaving out ArchiveFileName and keeping _{1:yyyy-MM-dd}_{0:00} gives the same files, because NLog falls back to FileName when the suffix contains {1 (FileTarget.cs:314, verified). This behaviour is less obvious, so setting it explicitly is clearer.

Regression test (in tests/BAUERGROUP.Shared.Test, xunit + FluentAssertions):

  • Quick test: the FILE target's suffix must include the date and must differ for two different days. Both checks were run in the console app: as shipped, both days give yyyy-MM-dd (both checks false). With the fix, _2026-09-14_00 and _2026-09-15_00 (both true):

    _ = BGLogger.Configuration;
    var target = LogManager.Configuration!.FindTargetByName<FileTarget>("FILE")!;
    string Suffix(DateTime day) => string.Format(CultureInfo.InvariantCulture, target.ArchiveSuffixFormat, 0, day);
    Suffix(new DateTime(2026, 9, 14)).Should().Contain("2026-09-14");
    Suffix(new DateTime(2026, 9, 15)).Should().NotBe(Suffix(new DateTime(2026, 9, 14)));
  • Optional end-to-end test: set LogDirectory to a temp folder and use an NLog TimeSource that moves forward one day per step. FromSystemTime must map file timestamps to the day that was active when they were written. Set MaxArchiveFiles = 2, then run three day changes. Expect exactly two archive files with different names, and no file name that contains yyyy. TimeSource.Current is global, so restore it afterwards and keep the test out of parallel test collections. The test takes a few seconds, because short real sleeps separate the fake days.

Related

Context

bgIndustrialAutomation Client (bauer-group/OT-AutomationClient, Shared.Core 3.0.8) uses the library's file target unchanged. It does not set LogDirectory or any archive option, and only reads the target's Layout (LogWindow.axaml.cs:88). Windows installations of the client therefore run this configuration. Their log folder under %ProgramData% should hold one <App>yyyy-MM-dd.log that keeps growing, instead of up to 30 day files. This is derived from the test above; no production station was checked. This was found while checking Linux logging (#134). Until this is fixed, an application can set ArchiveSuffixFormat on the FILE target after BGLogger.Configuration has been created, then call LogManager.ReconfigExistingLoggers(). The test above did exactly that.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreleased

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions