diff --git a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs index e6773eb..5cff740 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs @@ -43,16 +43,9 @@ public PathRoller(string path, RollingInterval interval) _directory = Path.GetFullPath(pathDirectory); _filenamePrefix = Path.GetFileNameWithoutExtension(path); _filenameSuffix = Path.GetExtension(path); - _filenameMatcher = new Regex( - "^" + - Regex.Escape(_filenamePrefix) + - "(?<" + PeriodMatchGroup + ">\\d{" + _periodFormat.Length + "})" + - "(?<" + SequenceNumberMatchGroup + ">_[0-9]{3,}){0,1}" + - Regex.Escape(_filenameSuffix) + - "$", - RegexOptions.Compiled); + _filenameMatcher = GetFileNameMatcher(); - DirectorySearchPattern = $"{_filenamePrefix}*{_filenameSuffix}"; + DirectorySearchPattern = GetDirectorySearchPattern(); } public string LogFileDirectory => _directory; @@ -68,7 +61,12 @@ public void GetLogFilePath(DateTime date, int? sequenceNumber, out string path) if (sequenceNumber != null) tok += "_" + sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture); - path = Path.Combine(_directory, _filenamePrefix + tok + _filenameSuffix); + string fileNameReplaced = ReplaceDatePlaceholder(_filenamePrefix, tok); + + if (string.Equals(_filenamePrefix, fileNameReplaced, StringComparison.InvariantCultureIgnoreCase)) + fileNameReplaced += tok; + + path = Path.Combine(_directory, fileNameReplaced + _filenameSuffix); } public IEnumerable SelectMatches(IEnumerable filenames) @@ -110,4 +108,40 @@ public IEnumerable SelectMatches(IEnumerable filenames) public DateTime? GetCurrentCheckpoint(DateTime instant) => _interval.GetCurrentCheckpoint(instant); public DateTime? GetNextCheckpoint(DateTime instant) => _interval.GetNextCheckpoint(instant); + + Regex GetFileNameMatcher() + { + if (_filenamePrefix.StartsWith("{Date}", StringComparison.InvariantCultureIgnoreCase)) + { + return new Regex( + "^" + + "(?<" + PeriodMatchGroup + ">\\d{" + _periodFormat.Length + "})" + + "(?<" + SequenceNumberMatchGroup + ">_[0-9]{3,}){0,1}" + + Regex.Escape(ReplaceDatePlaceholder(_filenamePrefix, string.Empty)) + + Regex.Escape(_filenameSuffix) + + "$", + RegexOptions.Compiled); + } + + return new Regex( + "^" + + Regex.Escape(_filenamePrefix) + + "(?<" + PeriodMatchGroup + ">\\d{" + _periodFormat.Length + "})" + + "(?<" + SequenceNumberMatchGroup + ">_[0-9]{3,}){0,1}" + + Regex.Escape(_filenameSuffix) + + "$", + RegexOptions.Compiled); + } + + string GetDirectorySearchPattern() + { + return _filenamePrefix.StartsWith("{Date}", StringComparison.InvariantCultureIgnoreCase) + ? $"*{ReplaceDatePlaceholder(_filenamePrefix, string.Empty)}{_filenameSuffix}" + : $"{_filenamePrefix}*{_filenameSuffix}"; + } + + static string ReplaceDatePlaceholder(string filenamePrefix, string dateString) + { + return Regex.Replace(filenamePrefix, @"\{Date}", dateString, RegexOptions.IgnoreCase); + } } diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index 830af11..10e15c0 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -29,6 +29,23 @@ public void LogEventsAreEmittedToTheFileNamedAccordingToTheEventTimestamp() TestRollingEventSequence(Some.InformationEvent()); } + [Fact] + public void ShouldCreateFileWithDatePlaceHolder() + { + var config = new LoggerConfiguration() + .WriteTo.File("{Date}-log.txt", rollingInterval: RollingInterval.Day); + var log = config.CreateLogger(); + + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + + Clock.SetTestDateTimeNow(now); + log.Write(Some.InformationEvent()); + + var path = Path.Combine(Directory.GetCurrentDirectory(), "20130714-log.txt"); + + Assert.True(System.IO.File.Exists(path)); + } + [Fact] public void EventsAreWrittenWhenSharingIsEnabled() { @@ -172,7 +189,7 @@ public void WhenFirstOpeningFailedWithLockRetryDelayedUntilNextCheckpoint() e3 = Some.InformationEvent(e1.Timestamp.AddMinutes(5)), e4 = Some.InformationEvent(e1.Timestamp.AddMinutes(31)); LogEvent[] logEvents = new[] { e1, e2, e3, e4 }; - + foreach (var logEvent in logEvents) { Clock.SetTestDateTimeNow(logEvent.Timestamp.DateTime); @@ -210,7 +227,7 @@ public void WhenFirstOpeningFailedWithLockRetryDelayed30Minutes() e3 = Some.InformationEvent(e1.Timestamp.AddMinutes(5)), e4 = Some.InformationEvent(e1.Timestamp.AddMinutes(31)); LogEvent[] logEvents = new[] { e1, e2, e3, e4 }; - + SelfLog.Enable(_testOutputHelper.WriteLine); foreach (var logEvent in logEvents) { @@ -247,7 +264,7 @@ public void WhenFirstOpeningFailedWithoutLockRetryDelayed30Minutes() e3 = Some.InformationEvent(e1.Timestamp.AddMinutes(5)), e4 = Some.InformationEvent(e1.Timestamp.AddMinutes(31)); LogEvent[] logEvents = new[] { e1, e2, e3, e4 }; - + SelfLog.Enable(_testOutputHelper.WriteLine); foreach (var logEvent in logEvents) { diff --git a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs index e34c335..7ff029c 100644 --- a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs +++ b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs @@ -1,9 +1,42 @@ -using Xunit; +using System.Globalization; +using Xunit; namespace Serilog.Sinks.File.Tests; public class PathRollerTests { + [Theory] + [InlineData(null)] + [InlineData(2)] + public void GetLogFilePath_WhenPathContainsDateTokenAtStart_ShouldReplaceDateToken(int? sequenceNumber) + { + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + string expectedName = sequenceNumber is null + ? $"{now:yyyyMMdd}-logs.txt" + : $"{now:yyyyMMdd}_{sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture)}-logs.txt"; + + var roller = new PathRoller(Path.Combine("Logs", "{date}-logs.txt"), RollingInterval.Day); + roller.GetLogFilePath(now, sequenceNumber, out string actualName); + + AssertEqualAbsolute(Path.Combine("Logs", expectedName), actualName); + } + + [Theory] + [InlineData(null)] + [InlineData(2)] + public void GetLogFilePath_WhenPathContainsDateTokenAtEnd_ShouldReplaceDateToken(int? sequenceNumber) + { + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + string expectedName = sequenceNumber is null + ? $"logs-{now:yyyyMMdd}.txt" + : $"logs-{now:yyyyMMdd}_{sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture)}.txt"; + + var roller = new PathRoller(Path.Combine("Logs", "logs-{Date}.txt"), RollingInterval.Day); + roller.GetLogFilePath(now, sequenceNumber, out string actualName); + + AssertEqualAbsolute(Path.Combine("Logs", expectedName), actualName); + } + [Fact] public void TheLogFileIncludesDateToken() { @@ -22,6 +55,15 @@ public void ANonZeroIncrementIsIncludedAndPadded() AssertEqualAbsolute(Path.Combine("Logs", "log-20130714_012.txt"), path); } + [Fact] + public void ANonZeroIncrementIsIncludedAndPaddedWhenDatePlaceHolderExists() + { + var roller = new PathRoller(Path.Combine("Logs", "{Date}-log.txt"), RollingInterval.Day); + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + roller.GetLogFilePath(now, 12, out var path); + AssertEqualAbsolute(Path.Combine("Logs", "20130714_012-log.txt"), path); + } + static void AssertEqualAbsolute(string path1, string path2) { var abs1 = Path.GetFullPath(path1); @@ -36,6 +78,13 @@ public void TheRollerReturnsTheLogFileDirectory() AssertEqualAbsolute("Logs", roller.LogFileDirectory); } + [Fact] + public void TheRollerReturnsTheLogFileDirectoryWhenDatePlaceHolderExists() + { + var roller = new PathRoller(Path.Combine("Logs", "{Date}-log.txt"), RollingInterval.Day); + AssertEqualAbsolute("Logs", roller.LogFileDirectory); + } + [Fact] public void TheLogFileIsNotRequiredToIncludeAnExtension() { @@ -45,6 +94,15 @@ public void TheLogFileIsNotRequiredToIncludeAnExtension() AssertEqualAbsolute(Path.Combine("Logs", "log-20130714"), path); } + [Fact] + public void TheLogFileIsNotRequiredToIncludeAnExtensionWhenDatePlaceHolderExists() + { + var roller = new PathRoller(Path.Combine("Logs", "{Date}-log"), RollingInterval.Day); + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + roller.GetLogFilePath(now, null, out var path); + AssertEqualAbsolute(Path.Combine("Logs", "20130714-log"), path); + } + [Fact] public void TheLogFileIsNotRequiredToIncludeADirectory() { @@ -54,13 +112,24 @@ public void TheLogFileIsNotRequiredToIncludeADirectory() AssertEqualAbsolute("log-20130714", path); } + [Fact] + public void TheLogFileIsNotRequiredToIncludeADirectoryWhenDatePlaceHolderExists() + { + var roller = new PathRoller("{Date}-log", RollingInterval.Day); + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + roller.GetLogFilePath(now, null, out var path); + AssertEqualAbsolute("20130714-log", path); + } + [Fact] public void MatchingExcludesSimilarButNonMatchingFiles() { var roller = new PathRoller("log-.txt", RollingInterval.Day); const string similar1 = "log-0.txt"; const string similar2 = "log-hello.txt"; - var matched = roller.SelectMatches(new[] { similar1, similar2 }); + const string similar3 = "0-log.txt"; + const string similar4 = "hello-log.txt"; + var matched = roller.SelectMatches(new[] { similar1, similar2, similar3, similar4 }); Assert.Empty(matched); } @@ -71,9 +140,17 @@ public void TheDirectorSearchPatternUsesWildcardInPlaceOfDate() Assert.Equal("log-*.txt", roller.DirectorySearchPattern); } + [Fact] + public void TheDirectorSearchPatternUsesWildcardInPlaceOfDateWhenDatePlaceHolderExists() + { + var roller = new PathRoller(Path.Combine("Logs", "{Date}-log.txt"), RollingInterval.Day); + Assert.Equal("*-log.txt", roller.DirectorySearchPattern); + } + [Theory] [InlineData("log-.txt", "log-20131210.txt", "log-20131210_031.txt", RollingInterval.Day)] [InlineData("log-.txt", "log-2013121013.txt", "log-2013121013_031.txt", RollingInterval.Hour)] + [InlineData("{Date}-log.txt", "2013121013-log.txt", "2013121013_031-log.txt", RollingInterval.Hour)] public void MatchingSelectsFiles(string template, string zeroth, string thirtyFirst, RollingInterval interval) { var roller = new PathRoller(template, interval); @@ -86,6 +163,7 @@ public void MatchingSelectsFiles(string template, string zeroth, string thirtyFi [Theory] [InlineData("log-.txt", "log-20150101.txt", "log-20141231.txt", RollingInterval.Day)] [InlineData("log-.txt", "log-2015010110.txt", "log-2015010109.txt", RollingInterval.Hour)] + [InlineData("{Date}-log.txt", "2015010110-log.txt", "2015010109-log.txt", RollingInterval.Hour)] public void MatchingParsesSubstitutions(string template, string newer, string older, RollingInterval interval) { var roller = new PathRoller(template, interval);