diff --git a/src/LogExpert.Configuration/LegacyPreferencesMigrator.cs b/src/LogExpert.Configuration/LegacyPreferencesMigrator.cs index cb31a16d..a7c08677 100644 --- a/src/LogExpert.Configuration/LegacyPreferencesMigrator.cs +++ b/src/LogExpert.Configuration/LegacyPreferencesMigrator.cs @@ -55,4 +55,5 @@ private static void MigrateToV1 (Settings settings) } #pragma warning restore CS0618 } + } diff --git a/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs b/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs index 3b8f1061..dd1c2087 100644 --- a/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs +++ b/src/LogExpert.Core/Classes/Log/RolloverFilenameBuilder.cs @@ -28,7 +28,6 @@ public class RolloverFilenameBuilder #region Fields private string _condContent; - private Group _condGroup; private string _currentFileName; private Group _dateGroup; @@ -41,6 +40,7 @@ public class RolloverFilenameBuilder private bool _hideZeroIndex; private Group _indexGroup; + private Group _indexPartGroup; private Regex _regex; #endregion @@ -89,7 +89,7 @@ public void SetFileName (string fileName) Index = _indexGroup.Value.Length > 0 ? int.Parse(_indexGroup.Value) : 0; } - _condGroup = match.Groups["cond"]; + _indexPartGroup = match.Groups["indexPart"]; } } @@ -116,15 +116,15 @@ public string BuildFileName () if (_indexGroup != null && _indexGroup.Success) { - fileName = fileName.Remove(_indexGroup.Index, _indexGroup.Length); + fileName = fileName.Remove(_indexPartGroup.Index, _indexPartGroup.Length); if (!_hideZeroIndex || Index > 0) { var format = "D" + _indexGroup.Length; - fileName = fileName.Insert(_indexGroup.Index, Index.ToString(format)); + fileName = fileName.Insert(_indexPartGroup.Index, Index.ToString(format)); if (_hideZeroIndex && _condContent != null) { - fileName = fileName.Insert(_indexGroup.Index, _condContent); + fileName = fileName.Insert(_indexPartGroup.Index, _condContent); } } } @@ -141,14 +141,14 @@ public string BuildFileName () private void ParseFormatString (string formatString) { var fmt = EscapeNonvarRegions(formatString); - var datePos = formatString.IndexOf("$D(", StringComparison.Ordinal); + var datePos = fmt.IndexOf("$D(", StringComparison.Ordinal); if (datePos != -1) { - var endPos = formatString.IndexOf(')', datePos); + var endPos = fmt.IndexOf(')', datePos); if (endPos != -1) { - _dateTimeFormat = formatString.Substring(datePos + 3, endPos - datePos - 3) + _dateTimeFormat = fmt.Substring(datePos + 3, endPos - datePos - 3) .ToUpperInvariant() .Replace('D', 'd') .Replace('Y', 'y'); @@ -176,12 +176,17 @@ private void ParseFormatString (string formatString) } } - fmt = fmt.Replace("*", ".*", StringComparison.Ordinal); + fmt = fmt.Replace("*", ".*?", StringComparison.Ordinal); _hideZeroIndex = fmt.Contains("$J", StringComparison.Ordinal); - fmt = fmt.Replace("$I", "(?'index'[\\d]+)", StringComparison.Ordinal); - fmt = fmt.Replace("$J", "(?'index'[\\d]*)", StringComparison.Ordinal); - - _regex = new Regex(fmt); + const string indexPattern = "(?'indexPart'(?'index'[\\d]+))"; + const string hiddenZeroIndexPattern = "(?'indexPart'(?'index'[\\d]*))"; + var conditionalIndexPattern = _condContent != null + ? $"(?'indexPart'(?:{Regex.Escape(_condContent)}(?'index'[\\d]+)|(?'index')))" + : hiddenZeroIndexPattern; + fmt = fmt.Replace("$I", indexPattern, StringComparison.Ordinal); + fmt = fmt.Replace("$J", conditionalIndexPattern, StringComparison.Ordinal); + + _regex = new Regex(fmt + @"\z"); } private string EscapeNonvarRegions (string formatString) @@ -199,8 +204,7 @@ private string EscapeNonvarRegions (string formatString) case 0: // looking for $ if (fmt[i] == '$') { - _ = result.Append(Regex.Escape(segment.ToString())); - segment = new StringBuilder(); + AppendEscapedSegment(result, segment); state = 1; } @@ -208,8 +212,7 @@ private string EscapeNonvarRegions (string formatString) break; case 1: // the char behind $ _ = segment.Append(fmt[i]); - _ = result.Append(segment); - segment = new StringBuilder(); + AppendSegment(result, segment); state = 2; break; case 2: // checking if ( or other char @@ -229,8 +232,7 @@ private string EscapeNonvarRegions (string formatString) _ = segment.Append(fmt[i]); if (fmt[i] == ')') { - _ = result.Append(segment); - segment = new StringBuilder(); + AppendSegment(result, segment); state = 0; } @@ -238,9 +240,22 @@ private string EscapeNonvarRegions (string formatString) } } + AppendEscapedSegment(result, segment); fmt = result.ToString().Replace('\xFFFD', '*'); return fmt; } + private static void AppendEscapedSegment (StringBuilder result, StringBuilder segment) + { + _ = result.Append(Regex.Escape(segment.ToString())); + _ = segment.Clear(); + } + + private static void AppendSegment (StringBuilder result, StringBuilder segment) + { + _ = result.Append(segment); + _ = segment.Clear(); + } + #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index a83dac05..973bb2f2 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 = Dateiindexnummer, versteckt wenn null +$J(<prefix>) = Wie $J, jedoch wird ein <prefix> hinzugefügt wenn 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 22abd07a..d10a9ce5 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 10233076..fef5c803 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..f198f7a7 100644 --- a/src/LogExpert.Tests/RollingNameTest.cs +++ b/src/LogExpert.Tests/RollingNameTest.cs @@ -32,6 +32,11 @@ 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.log.1", "engine.log.2", "*$J(.)")] + [TestCase("engine.1.log", "engine.2.log", "*$J(.).log")] + [TestCase("engine1.log", "engine2.log", "*$I.log")] + [TestCase("engine_2010-06-12.1.log", "engine_2010-06-12.2.log", "*$D(yyyy-MM-dd)$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 +50,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); @@ -54,6 +60,27 @@ public void TestFilenameAnd2(string fileName, string expectedResult, string form Assert.That(name, Is.EqualTo(expectedResult)); } + [Test] + public void TestFilenameDateAfterRegexMetacharacter () + { + RolloverFilenameBuilder fnb = new("app.$D(yyyy-MM-dd).log"); + fnb.SetFileName("app.2010-06-12.log"); + + fnb.IncrementDate(); + + Assert.That(fnb.BuildFileName(), Is.EqualTo("app.2010-06-13.log")); + } + + [Test] + public void BuildFileName_FullPathWithNonWildcardMask_IncrementsIndex () + { + RolloverFilenameBuilder fnb = new("engine$J.log"); + fnb.SetFileName(@"C:\logs\engine1.log"); + + fnb.Index += 1; + + Assert.That(fnb.BuildFileName(), Is.EqualTo(@"C:\logs\engine2.log")); + } [Test] [TestCase("engine1.log", "engine.log","engine$J.log")] @@ -65,4 +92,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..df03440e 100644 --- a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -68,6 +68,24 @@ public void SingleFileCtor_MultiFileTrue_ExpandsRollover () }); } + [Test] + public void SingleFileCtor_MultiFileTrue_LoadsIndexBeforeExtension () + { + var options = new MultiFileOptions { FormatPattern = "*$J(.).log" }; + 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")); + 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.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 diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs index c7d64109..9da5eea6 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.designer.cs @@ -269,9 +269,7 @@ private void InitializeComponent () // // multiFileEnabledStripMenuItem // - multiFileEnabledStripMenuItem.BackColor = SystemColors.Control; multiFileEnabledStripMenuItem.CheckOnClick = true; - multiFileEnabledStripMenuItem.ForeColor = SystemColors.ControlDarkDark; multiFileEnabledStripMenuItem.Name = "multiFileEnabledStripMenuItem"; multiFileEnabledStripMenuItem.Size = new Size(165, 22); multiFileEnabledStripMenuItem.Text = "Enable MultiFile"; @@ -279,8 +277,6 @@ private void InitializeComponent () // // multifileMaskToolStripMenuItem // - 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 + }