Skip to content

Enahancement/multifile max days - #737

Open
Pr0metheus2 wants to merge 3 commits into
LogExperts:Developmentfrom
Pr0metheus2:fix/multifile-max-days
Open

Pr0metheus2 wants to merge 3 commits into
LogExperts:Developmentfrom
Pr0metheus2:fix/multifile-max-days

Conversation

@Pr0metheus2

@Pr0metheus2 Pr0metheus2 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

This is a proposed fix for the Issue #735 and #185

Problem

The Max days setting only controlled how many previous calendar dates were searched when the MultiFile mask contained a $D date placeholder.

It did not check the last-modified date of files found using index patterns such as:

  • *$J(.) → app.log, app.log.1, app.log.2
  • *$J(.).log → app.log, app.1.log, app.2.log

As a result, all indexed rollover files were loaded regardless of their age.

Changes

  • Added the file’s UTC last-write timestamp to ILogFileInfo.
  • Implemented timestamp retrieval for:
    • Local files
    • SFTP files
  • Applied the Max days limit when MultiFile files are discovered.
  • The file explicitly opened by the user is always retained.
  • MaxDayTry = 0 preserves the previous unlimited behavior.
  • Filesystem plugins that cannot provide a timestamp retain the previous behavior and their files are not excluded.

Tests

Added coverage for both supported rollover filename layouts:

  • app.log, app.log.1, app.log.2
  • app.log, app.1.log, app.2.log

The tests verify that with MaxDayTry = 3:

  • The active file is loaded.
  • A file modified two days ago is loaded.
  • A file modified four days ago is excluded.
  • Setting MaxDayTry = 0 disables age filtering.

The existing $D date-placeholder rule remains working.

  • added text hint (EN, DE CN)
image

- Added the file’s UTC last-write timestamp to ILogFileInfo.
- Implemented timestamp retrieval for:
  - Local files
  - SFTP files
- Applied the Max days limit when MultiFile companion files are discovered.
- The file explicitly opened by the user is always retained.
- MaxDayTry = 0 preserves the previous unlimited behavior.
- Filesystem plugins that cannot provide a timestamp retain the previous behavior and their files are not excluded.
EN, DE, CNs
@Hirogen

Hirogen commented Oct 19, 2026

Copy link
Copy Markdown
Member

hi, i added an analyses to the issue, its not really a bug

@Pr0metheus2 Pr0metheus2 changed the title Fix/multifile max days Enahncement/multifile max days Oct 19, 2026
@Pr0metheus2 Pr0metheus2 changed the title Enahncement/multifile max days Enahancement/multifile max days Oct 19, 2026
@Hirogen

Hirogen commented Oct 19, 2026

Copy link
Copy Markdown
Member

The PR doesn't do what the #735 spec asks. The spec wants a new, separate max-file-age option. The PR instead turns the existing MaxDayTry setting into an age limit. Because MaxDayTry still defaults to 3, every existing user with an index pattern like *$J(.) will silently lose rollover files older than 3 days. The spec lists exactly that change as out of scope.

Standards

Bugs

  1. The "0 = unlimited" hint is wrong for $D date patterns. The date search still loops while (tryCounter < _options.MaxDayTry). With 0 that loop never runs, so no dated companion files load at all. The new hint text and the XML doc comment ("Zero disables the file-age limit") are only true for index patterns.
  2. Existing users get a silent behaviour change. Index patterns had no age limit before. Now the default (and any saved MaxDayTry=3) drops app.log.N files older than 72 hours, and nothing tells the user.
  3. Age is measured from now, not by calendar day. DateTime.UtcNow.AddDays(-MaxDayTry) is a rolling 72-hour window, but the date search counts calendar days. A file from day −3 written early in the morning is dropped even though the date search reached it.

Repo standards (judgement calls, none hard)

  • Unrelated newline edits. A final newline is added to 9 .cs files the PR doesn't otherwise touch, and to Resources.zh-CN.resx. src/.editorconfig sets insert_final_newline = false for .cs, and .github/CLAUDE.md §3 says every changed line should trace to the request. The rule is a bit ambiguous, because the same editorconfig also sets resharper_csharp_insert_final_newline = true.
  • Comment edits in MultiFileMaskDialog.Designer.cs. An existing // separator becomes //, and the new block doesn't match its neighbours. Same §3 rule.

Smells (judgement calls)

  • One setting, two meanings. MaxDayTry now means both "missing days to skip" and "maximum file age", and its doc comment has to explain both.
  • Duplicated code in SFTP. SftpLogFileInfo.LastWriteTimeUtc copies the try/_sftp.Get/catch shape of Length. It also adds an extra remote call for every file it checks.
  • Over-broad catch. The SFTP getter catches ArgumentNullException, which Length next to it does not; catching it can hide bugs.
  • Clock can't be faked. The code reads DateTime.UtcNow directly. The tests use real timestamps but leave a full day of margin, so they are stable enough.

Minor:

  • TestData/app.log.2 ends with a trailing newline that app.log.1 doesn't have.
  • The tests call Cleanup() inline, so a failing assert leaves the temp directory behind. The neighbouring tests do the same.
  • The resource strings are consistent across all three languages and the designer file.

Spec (maintainer triage comment on #735)

Missing or partial

  • No new option. The spec asks for "A new MultiFile option, max file age in days, with 0 meaning off". There's no new property; MaxDayTry is reused.
  • Default isn't off. The spec says "0 is the default so existing behavior is unchanged". This is violated because the default stays at 3.
  • No relabel. The spec asks to rename the label to something like "Max day gap" and to update the pattern-syntax help. The label still says "Max days:"; only a hint was added.
  • Settings and Session File not touched. The spec asks for the option in Settings → MultiFile, and for a round-trip through SessionSnapshot, SessionFileComposer and PersistenceData, with old Session Files loading as 0. None of these files changed.
  • No SDK version bump. The spec asks to "bump its own version in ColumnizerLib.csproj". The ILogFileInfo API changed, but the version didn't.
  • Acceptance tests missing. There's no test for:
    • contiguity (an old app.log.1 followed by a new app.log.2)
    • the $D pattern being cut off by age
    • a file with no reported timestamp being included
    • the Session File round-trip
    • the setting being editable in both the dialog and Settings

Scope creep

  • The spec lists "Changing what MaxDayTry does" as out of scope, and that is the core of the PR.

Implemented but wrong

  • Clock not injectable. The spec says "Make 'now' injectable", but the code calls DateTime.UtcNow directly.
  • Tests don't use fakes. The spec says tests should run "in Core against a fake file system and an injected clock, with no real file timestamps". The tests use real files, File.SetLastWriteTimeUtc and PluginRegistry.Instance.
  • Stopping at the first old file only works for index patterns. The spec says the walk "stops at the first rollover file whose last-modified time is older". That holds for pure index patterns. In mixed patterns a too-old indexed file falls through to the date search, which keeps going. In date patterns a too-old file counts as a missing day.
  • Done correctly: the new ILogFileInfo member defaults to null, a file with an unknown timestamp is included, and the local and SFTP versions are implemented.

Summary: 10 standards findings, the worst being the "0 = unlimited" hint, which actually loads no files for $D patterns. 12 spec findings, the worst being that the PR changes MaxDayTry instead of adding a separate option that defaults to off. The fix the spec points to is a new MaxFileAgeDays option (default 0) with an injected clock, carried through Settings and the Session File, plus a ColumnizerLib version bump.

@Pr0metheus2

Copy link
Copy Markdown
Contributor Author

OK I will try to make your proposal as you require.

@Pr0metheus2

Copy link
Copy Markdown
Contributor Author

Check, please

image

@Hirogen

Hirogen commented Oct 19, 2026

Copy link
Copy Markdown
Member

2 Bugs left:

  • Overflow crash: a very large max file age crashes MultiFile loading.
  • Language-dependent test: one UI test only passes on an English machine.

New:

  • Bug: overflow crash. Both new up-downs allow values up to 2147483647. GetCandidateResult calls UtcDateTime.AddDays(-_options.MaxFileAgeDays), which throws ArgumentOutOfRangeException at about 740,000 days, and that breaks MultiFile loading. Fix: cap the up-downs (e.g. 3650) and/or clamp the value in code.
  • Bug: test depends on UI language. src/LogExpert.UI.Tests/Dialogs/MultiFileAgeSettingsTests.cs:80 compares the label text, which is hard-coded in the Designer, against UIStrings.SettingsDialog_UI_Label_labelMaxDayGap. On a German or Chinese machine that fails. Fix: pin the culture, as the Highlight dialog tests do, or drop the assert.
  • Judgement: unused resource keys. SettingsDialog never calls ApplyResources, so the three new SettingsDialog_UI_Label_* keys (in three languages) are never read; only the tooltips get localized. Either wire the labels up or don't add the keys.
  • Judgement: nullable annotations. TimeProvider timeProvider = null should be TimeProvider?. TryGetFile(out SftpFile file) should be [NotNullWhen(true)] out SftpFile? file, like EncodingRegistry.TryGetEncoding.
  • Smell: one concept, four names. MaxDayTry, MaxDayGap, MultiFileMaxDays and upDownMultifileDays all mean the same setting. The persisted names have to stay, so this is a judgement call.
  • Smell: minor duplication. The two loops in GetNameList repeat the same Include/TooOld block. Tolerable at this size.
  • Minor: SFTP cache. _lastWriteTimeUtc isn't cleared when TryGetFile fails. That's harmless for the short-lived info objects the rollover search uses.

Remaining:

  • No test for global-default seeding. The spec says "the global default seeds new windows". The code does this at LogWindow.cs:2851 and :2901, but no test asserts it.
  • Scope creep: SFTP error handling changed. With the shared TryGetFile, FileExists no longer catches ArgumentNullException and Length now also catches SshException. It's small and arguably an improvement.
  • Scope creep: XML read of a new field. PersisterXML reads a maxFileAgeDays attribute that no XML writer ever produces. It's harmless dead code.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants