From 54b28910acb13a5f7b86bb488088ffcb64e916f9 Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Wed, 26 Aug 2026 12:07:41 +0200 Subject: [PATCH 1/2] Fix/MultiFile pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed/MultiFile rollover patterns with an index before the extension: - RolloverFilenameBuilder: the parser was dropping literal text after the final placeholder, including .log. - *$J(.).log now correctly expands: app.log → app.1.log → app.2.log. - Updated MultiFile mask dialog legend: - Fixed incorrectly displayed and placeholders in English and German resources. - Added both usage examples: *$J(.) → app.log, app.log.1, app.log.2 *$J(.).log → app.log, app.1.log, app.2.log - Added the examples in English, German, and Chinese. - Enlarged the dialog and syntax-help label so the date-format lines are no longer clipped. - Fixed/Items in menu File/Multifile -> "Enable MultiFile" and "Multi file mask...", were always greyed out. Now they are displayed normally after the .log file is open. - removed the BackColor and ForeColor assignments for both: - multiFileEnabledStripMenuItem - multifileMaskToolStripMenuItem - Added regression coverage: - Filename-builder tests for app.1.log and app.2.log. - Reader-level test that loads app.log, app.1.log, and app.2.log in correct order using *$J(.).log. - Kept the existing *$J(.) coverage for app.log.1 / app.log.2. Verification: - Full test suite: 1064 passed, 0 failed, 7 skipped. - Resource project build: passed with 0 warnings, 0 errors. --- .../Classes/Log/RolloverFilenameBuilder.cs | 3 ++- src/LogExpert.Resources/Resources.de.resx | 10 ++++++--- src/LogExpert.Resources/Resources.resx | 10 ++++++--- src/LogExpert.Resources/Resources.zh-CN.resx | 4 ++++ src/LogExpert.Tests/LogExpert.Tests.csproj | 6 +++++ src/LogExpert.Tests/RollingNameTest.cs | 4 +++- .../LogfileReaderMultiFileFlagTests.cs | 22 +++++++++++++++++-- .../LogTabWindow/LogTabWindow.designer.cs | 8 +++---- .../Dialogs/MultiFileMaskDialog.Designer.cs | 12 +++++----- 9 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs b/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs index 3b8f1061..e449884b 100644 --- a/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs +++ b/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs @@ -238,9 +238,10 @@ private string EscapeNonvarRegions (string formatString) } } + _ = result.Append(Regex.Escape(segment.ToString())); fmt = result.ToString().Replace('\xFFFD', '*'); return fmt; } #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index e0e19567..3852c64c 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -1680,12 +1680,16 @@ Ein ausgewähltes Tool erscheint in der Iconbar. Alle anderen verfügbaren Tools Muster syntax: * = alle Zeichen (wildcard) -$D(&lt;date&gt;) = Datumsmuster +$D(<date>) = Datumsmuster $I = Dateiindexnummer $J = Dateiindexnummer, versteckt wenn 09 -$J(&lt;prefix&gt;) = Wie $J, jedoch wird ein &lt;prefix&gt; hinzugefügt when es nicht 0 ist +$J(<prefix>) = Wie $J, jedoch wird ein <prefix> hinzugefügt when es nicht 0 ist -&lt;date&gt;: +Beispiele: +*$J(.) → app.log, app.log.1, app.log.2 +*$J(.).log → app.log, app.1.log, app.2.log + +<date>: DD = Tag MM = Monat YY[YY] = Jahr diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index a08b755c..70d5e60d 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -1743,12 +1743,16 @@ Checked tools will appear in the icon bar. All other tools are available in the Pattern syntax: * = any characters (wildcard) -$D(&lt;date&gt;) = Date pattern +$D(<date>) = Date pattern $I = File index number $J = File index number, hidden when zero -$J(&lt;prefix&gt;) = Like $J, but adding &lt;prefix&gt; when non-zero +$J(<prefix>) = Like $J, but adding <prefix> when non-zero -&lt;date&gt;: +Examples: +*$J(.) → app.log, app.log.1, app.log.2 +*$J(.).log → app.log, app.1.log, app.2.log + +<date>: DD = day MM = month YY[YY] = year diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index f4af56f5..826db482 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -1554,6 +1554,10 @@ $I = 文件索引编号 $J = 文件索引编号,为零时隐藏 $J(<前缀>) = 类似 $J,但在非零时添加 <前缀> +示例: +*$J(.) → app.log, app.log.1, app.log.2 +*$J(.).log → app.log, app.1.log, app.2.log + <日期>: DD = 日 MM = 月 diff --git a/src/LogExpert.Tests/LogExpert.Tests.csproj b/src/LogExpert.Tests/LogExpert.Tests.csproj index addb6a7a..2ead6531 100644 --- a/src/LogExpert.Tests/LogExpert.Tests.csproj +++ b/src/LogExpert.Tests/LogExpert.Tests.csproj @@ -112,6 +112,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/LogExpert.Tests/RollingNameTest.cs b/src/LogExpert.Tests/RollingNameTest.cs index a85f94b4..8f0a079c 100644 --- a/src/LogExpert.Tests/RollingNameTest.cs +++ b/src/LogExpert.Tests/RollingNameTest.cs @@ -32,6 +32,7 @@ public void TestFilename1(string expectedResult, string formatString) [TestCase("engine.log", "engine1.log","engine$J.log")] [TestCase("engine1.log", "engine2.log","engine$J.log")] [TestCase("engine.log", "engine.log.1","*$J(.)")] + [TestCase("engine.log", "engine.1.log", "*$J(.).log")] [TestCase("engine_2010-06-12.log", "engine_2010-06-12.log.1", "*$D(yyyy-MM-dd).log$J(.)")] public void TestFilenameAnd1(string fileName, string expectedResult, string formatString) { @@ -45,6 +46,7 @@ public void TestFilenameAnd1(string fileName, string expectedResult, string form [Test] [TestCase("engine.log", "engine.log.2","*$J(.)")] [TestCase("engine.log", "engine.log.2","*.log$J(.)")] + [TestCase("engine.log", "engine.2.log", "*$J(.).log")] public void TestFilenameAnd2(string fileName, string expectedResult, string formatString) { RolloverFilenameBuilder fnb = new(formatString); @@ -65,4 +67,4 @@ public void TestFilenameMinus1(string fileName, string expectedResult, string fo var name = fnb.BuildFileName(); Assert.That(name, Is.EqualTo("engine.log")); } -} \ No newline at end of file +} diff --git a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs index 5522313d..ab3edd74 100644 --- a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -26,6 +26,8 @@ public void SetUp () File.Copy(Path.Combine(_testDataDirectory, "app.log"), _logFile); File.Copy(Path.Combine(_testDataDirectory, "app.log.1"), _logFile + ".1"); + File.Copy(Path.Combine(_testDataDirectory, "app.1.log"), Path.Combine(_testDirectory, "app.1.log")); + File.Copy(Path.Combine(_testDataDirectory, "app.2.log"), Path.Combine(_testDirectory, "app.2.log")); _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); } @@ -68,6 +70,22 @@ public void SingleFileCtor_MultiFileTrue_ExpandsRollover () }); } + [Test] + public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () + { + var options = new MultiFileOptions { FormatPattern = "*$J(.).log" }; + using var reader = CreateSingleFileReader(multiFile: true, options); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.True); + Assert.That(reader.GetLogFileInfoList().Select(file => Path.GetFileName(file.FullName)), + Is.EqualTo(new[] { "app.2.log", "app.1.log", "app.log" })); + }); + } + [Test] public void MultiFileCtor_AlwaysMultiFile () { @@ -91,7 +109,7 @@ public void MultiFileCtor_AlwaysMultiFile () }); } - private LogfileReader CreateSingleFileReader (bool multiFile) + private LogfileReader CreateSingleFileReader (bool multiFile, MultiFileOptions? options = null) { return new LogfileReader( _logFile, @@ -99,7 +117,7 @@ private LogfileReader CreateSingleFileReader (bool multiFile) multiFile, bufferCount: 40, linesPerBuffer: 50, - new MultiFileOptions(), + options ?? new MultiFileOptions(), ReaderType.System, PluginRegistry.PluginRegistry.Instance, maximumLineLength: 500, diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs index c7d64109..add47f8c 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs @@ -269,9 +269,9 @@ private void InitializeComponent () // // multiFileEnabledStripMenuItem // - multiFileEnabledStripMenuItem.BackColor = SystemColors.Control; + // multiFileEnabledStripMenuItem.BackColor = SystemColors.Control; multiFileEnabledStripMenuItem.CheckOnClick = true; - multiFileEnabledStripMenuItem.ForeColor = SystemColors.ControlDarkDark; + // multiFileEnabledStripMenuItem.ForeColor = SystemColors.ControlDarkDark; multiFileEnabledStripMenuItem.Name = "multiFileEnabledStripMenuItem"; multiFileEnabledStripMenuItem.Size = new Size(165, 22); multiFileEnabledStripMenuItem.Text = "Enable MultiFile"; @@ -279,8 +279,8 @@ private void InitializeComponent () // // multifileMaskToolStripMenuItem // - multifileMaskToolStripMenuItem.BackColor = SystemColors.Control; - multifileMaskToolStripMenuItem.ForeColor = SystemColors.ControlDarkDark; + // multifileMaskToolStripMenuItem.BackColor = SystemColors.Control; + // multifileMaskToolStripMenuItem.ForeColor = SystemColors.ControlDarkDark; multifileMaskToolStripMenuItem.Name = "multifileMaskToolStripMenuItem"; multifileMaskToolStripMenuItem.Size = new Size(165, 22); multifileMaskToolStripMenuItem.Text = "File name mask..."; diff --git a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs index d57d7e64..18763c3b 100644 --- a/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs +++ b/src/LogExpert.UI/Dialogs/MultiFileMaskDialog.Designer.cs @@ -121,7 +121,7 @@ private void InitializeComponent() // this.syntaxHelpLabel.Location = new System.Drawing.Point(15, 140); this.syntaxHelpLabel.Name = "syntaxHelpLabel"; - this.syntaxHelpLabel.Size = new System.Drawing.Size(402, 194); + this.syntaxHelpLabel.Size = new System.Drawing.Size(402, 254); this.syntaxHelpLabel.TabIndex = 7; this.syntaxHelpLabel.Text = "Syntax Help Label"; // @@ -129,7 +129,7 @@ private void InitializeComponent() // this.buttonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK; - this.buttonOk.Location = new System.Drawing.Point(261, 347); + this.buttonOk.Location = new System.Drawing.Point(261, 407); this.buttonOk.Name = "buttonOk"; this.buttonOk.Size = new System.Drawing.Size(75, 23); this.buttonOk.TabIndex = 8; @@ -141,7 +141,7 @@ private void InitializeComponent() // this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.buttonCancel.Location = new System.Drawing.Point(342, 347); + this.buttonCancel.Location = new System.Drawing.Point(342, 407); this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Size = new System.Drawing.Size(75, 23); this.buttonCancel.TabIndex = 9; @@ -152,7 +152,7 @@ private void InitializeComponent() // this.AcceptButton = this.buttonOk; this.CancelButton = this.buttonCancel; - this.ClientSize = new System.Drawing.Size(434, 386); + this.ClientSize = new System.Drawing.Size(434, 446); this.Controls.Add(this.buttonCancel); this.Controls.Add(this.buttonOk); this.Controls.Add(this.syntaxHelpLabel); @@ -162,7 +162,7 @@ private void InitializeComponent() this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.MaximizeBox = false; this.MinimizeBox = false; - this.MinimumSize = new System.Drawing.Size(329, 420); + this.MinimumSize = new System.Drawing.Size(329, 480); this.Name = "MultiFileMaskDialog"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "MultiFile settings"; @@ -188,4 +188,4 @@ private void InitializeComponent() private System.Windows.Forms.Label syntaxHelpLabel; private System.Windows.Forms.Button buttonOk; private System.Windows.Forms.Button buttonCancel; - } \ No newline at end of file + } From 3989ae2a5cfc7e7a54ccfaa0da3cbd9c105948dd Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Wed, 26 Aug 2026 13:22:16 +0200 Subject: [PATCH 2/2] test: add multi-file index test data --- src/LogExpert.Tests/TestData/app.1.log | 1 + src/LogExpert.Tests/TestData/app.2.log | 1 + 2 files changed, 2 insertions(+) create mode 100644 src/LogExpert.Tests/TestData/app.1.log create mode 100644 src/LogExpert.Tests/TestData/app.2.log diff --git a/src/LogExpert.Tests/TestData/app.1.log b/src/LogExpert.Tests/TestData/app.1.log new file mode 100644 index 00000000..dc4e726a --- /dev/null +++ b/src/LogExpert.Tests/TestData/app.1.log @@ -0,0 +1 @@ +app.1.log \ No newline at end of file diff --git a/src/LogExpert.Tests/TestData/app.2.log b/src/LogExpert.Tests/TestData/app.2.log new file mode 100644 index 00000000..67dfe9df --- /dev/null +++ b/src/LogExpert.Tests/TestData/app.2.log @@ -0,0 +1 @@ +app.2.log \ No newline at end of file