diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 5440792..a114d72 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -164,7 +164,7 @@ public static LoggerConfiguration File( /// Logger sink configuration. /// A formatter, such as , to convert the log events into /// text for the file. If control of regular text formatting is required, use the other - /// overload of + /// overload of /// and specify the outputTemplate parameter instead. /// /// Path to the file. @@ -236,6 +236,10 @@ public static LoggerConfiguration File( /// Must be greater than or equal to . /// Ignored if is . /// The default is to retain files indefinitely. + /// Log file interval multiplier. + /// Ignored if is . + /// Must be at least 1, or null. If null then 1 used. + /// /// Configuration object allowing method chaining. /// When is null /// When is null @@ -262,7 +266,8 @@ public static LoggerConfiguration File( int? retainedFileCountLimit = DefaultRetainedFileCountLimit, Encoding? encoding = null, FileLifecycleHooks? hooks = null, - TimeSpan? retainedFileTimeLimit = null) + TimeSpan? retainedFileTimeLimit = null, + int? rollingIntervalDuration = null) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); if (path == null) throw new ArgumentNullException(nameof(path)); @@ -271,7 +276,7 @@ public static LoggerConfiguration File( var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, - rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks, retainedFileTimeLimit); + rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks, retainedFileTimeLimit, rollingIntervalDuration); } /// @@ -280,7 +285,7 @@ public static LoggerConfiguration File( /// Logger sink configuration. /// A formatter, such as , to convert the log events into /// text for the file. If control of regular text formatting is required, use the other - /// overload of + /// overload of /// and specify the outputTemplate parameter instead. /// /// Path to the file. @@ -306,6 +311,10 @@ public static LoggerConfiguration File( /// Must be greater than or equal to . /// Ignored if is . /// The default is to retain files indefinitely. + /// Log file interval multiplier. + /// Ignored if is . + /// Must be at least 1, or null. If null then 1 used. + /// /// Configuration object allowing method chaining. /// When is null /// When is null @@ -331,7 +340,8 @@ public static LoggerConfiguration File( int? retainedFileCountLimit = DefaultRetainedFileCountLimit, Encoding? encoding = null, FileLifecycleHooks? hooks = null, - TimeSpan? retainedFileTimeLimit = null) + TimeSpan? retainedFileTimeLimit = null, + int? rollingIntervalDuration = null) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); if (formatter == null) throw new ArgumentNullException(nameof(formatter)); @@ -339,7 +349,7 @@ public static LoggerConfiguration File( return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, - retainedFileCountLimit, hooks, retainedFileTimeLimit); + retainedFileCountLimit, hooks, retainedFileTimeLimit, rollingIntervalDuration); } /// @@ -494,7 +504,7 @@ public static LoggerConfiguration File( if (path == null) throw new ArgumentNullException(nameof(path)); return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true, - false, null, encoding, RollingInterval.Infinite, false, null, hooks, null); + false, null, encoding, RollingInterval.Infinite, false, null, hooks, null, null); } static LoggerConfiguration ConfigureFile( @@ -513,7 +523,8 @@ static LoggerConfiguration ConfigureFile( bool rollOnFileSizeLimit, int? retainedFileCountLimit, FileLifecycleHooks? hooks, - TimeSpan? retainedFileTimeLimit) + TimeSpan? retainedFileTimeLimit, + int? rollingIntervalDuration) { if (addSink == null) throw new ArgumentNullException(nameof(addSink)); if (formatter == null) throw new ArgumentNullException(nameof(formatter)); @@ -523,6 +534,7 @@ static LoggerConfiguration ConfigureFile( if (retainedFileTimeLimit.HasValue && retainedFileTimeLimit < TimeSpan.Zero) throw new ArgumentException("Negative value provided; retained file time limit must be non-negative.", nameof(retainedFileTimeLimit)); if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); if (shared && hooks != null) throw new ArgumentException("File lifecycle hooks are not currently supported for shared log files.", nameof(hooks)); + if ((rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) && rollingIntervalDuration.HasValue && rollingIntervalDuration < 1) throw new ArgumentException("Zero or negative value provided; rolling interval duration must be at least 1, or null.", nameof(rollingIntervalDuration)); ILogEventSink sink; @@ -530,7 +542,7 @@ static LoggerConfiguration ConfigureFile( { if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) { - sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, hooks, retainedFileTimeLimit); + sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, hooks, retainedFileTimeLimit, rollingIntervalDuration); } else { diff --git a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs index e6773eb..dc64fca 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2016 Serilog Contributors +// Copyright 2013-2016 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,12 +28,16 @@ sealed class PathRoller readonly Regex _filenameMatcher; readonly RollingInterval _interval; + private readonly int _intervalDuration; readonly string _periodFormat; - public PathRoller(string path, RollingInterval interval) + public PathRoller(string path, RollingInterval interval, int intervalDuration) { if (path == null) throw new ArgumentNullException(nameof(path)); + if (interval != RollingInterval.Infinite && intervalDuration < 1) throw new ArgumentException(nameof(intervalDuration)); + _interval = interval; + _intervalDuration = intervalDuration; _periodFormat = interval.GetFormat(); var pathDirectory = Path.GetDirectoryName(path); @@ -109,5 +113,5 @@ public IEnumerable SelectMatches(IEnumerable filenames) public DateTime? GetCurrentCheckpoint(DateTime instant) => _interval.GetCurrentCheckpoint(instant); - public DateTime? GetNextCheckpoint(DateTime instant) => _interval.GetNextCheckpoint(instant); + public DateTime? GetNextCheckpoint(DateTime instant) => _interval.GetNextCheckpoint(instant, _intervalDuration); } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 6c55d44..f084cf2 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -49,14 +49,16 @@ public RollingFileSink(string path, RollingInterval rollingInterval, bool rollOnFileSizeLimit, FileLifecycleHooks? hooks, - TimeSpan? retainedFileTimeLimit) + TimeSpan? retainedFileTimeLimit, + int? rollingIntervalDuration) { if (path == null) throw new ArgumentNullException(nameof(path)); if (fileSizeLimitBytes is < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); if (retainedFileCountLimit is < 1) throw new ArgumentException("Zero or negative value provided; retained file count limit must be at least 1."); if (retainedFileTimeLimit.HasValue && retainedFileTimeLimit < TimeSpan.Zero) throw new ArgumentException("Negative value provided; retained file time limit must be non-negative.", nameof(retainedFileTimeLimit)); + if (rollingInterval != RollingInterval.Infinite && rollingIntervalDuration.HasValue && rollingIntervalDuration < 1) throw new ArgumentException("Zero or negative value provided; rolling interval duration must be at least 1.", nameof(rollingIntervalDuration)); - _roller = new PathRoller(path, rollingInterval); + _roller = new PathRoller(path, rollingInterval, rollingIntervalDuration ?? 1); _textFormatter = textFormatter; _fileSizeLimitBytes = fileSizeLimitBytes; _retainedFileCountLimit = retainedFileCountLimit; diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs index a469abe..5973358 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2017 Serilog Contributors +// Copyright 2017 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -44,19 +44,22 @@ public static string GetFormat(this RollingInterval interval) }; } - public static DateTime? GetNextCheckpoint(this RollingInterval interval, DateTime instant) + public static DateTime? GetNextCheckpoint(this RollingInterval interval, DateTime instant, int? intervalDuration = null) { var current = GetCurrentCheckpoint(interval, instant); if (current == null) return null; + if (!intervalDuration.HasValue || intervalDuration < 1) + intervalDuration = 1; + return interval switch { - RollingInterval.Year => current.Value.AddYears(1), - RollingInterval.Month => current.Value.AddMonths(1), - RollingInterval.Day => current.Value.AddDays(1), - RollingInterval.Hour => current.Value.AddHours(1), - RollingInterval.Minute => current.Value.AddMinutes(1), + RollingInterval.Year => current.Value.AddYears(intervalDuration.Value), + RollingInterval.Month => current.Value.AddMonths(intervalDuration.Value), + RollingInterval.Day => current.Value.AddDays(intervalDuration.Value), + RollingInterval.Hour => current.Value.AddHours(intervalDuration.Value), + RollingInterval.Minute => current.Value.AddMinutes(intervalDuration.Value), _ => throw new ArgumentException("Invalid rolling interval.") }; } diff --git a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs index e34c335..c0e1224 100644 --- a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs +++ b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs @@ -1,4 +1,4 @@ -using Xunit; +using Xunit; namespace Serilog.Sinks.File.Tests; @@ -7,7 +7,7 @@ public class PathRollerTests [Fact] public void TheLogFileIncludesDateToken() { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day, 1); var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); roller.GetLogFilePath(now, null, out var path); AssertEqualAbsolute(Path.Combine("Logs", "log-20130714.txt"), path); @@ -16,7 +16,7 @@ public void TheLogFileIncludesDateToken() [Fact] public void ANonZeroIncrementIsIncludedAndPadded() { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day, 1); var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); roller.GetLogFilePath(now, 12, out var path); AssertEqualAbsolute(Path.Combine("Logs", "log-20130714_012.txt"), path); @@ -32,14 +32,14 @@ static void AssertEqualAbsolute(string path1, string path2) [Fact] public void TheRollerReturnsTheLogFileDirectory() { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day, 1); AssertEqualAbsolute("Logs", roller.LogFileDirectory); } [Fact] public void TheLogFileIsNotRequiredToIncludeAnExtension() { - var roller = new PathRoller(Path.Combine("Logs", "log-"), RollingInterval.Day); + var roller = new PathRoller(Path.Combine("Logs", "log-"), RollingInterval.Day, 1); var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); roller.GetLogFilePath(now, null, out var path); AssertEqualAbsolute(Path.Combine("Logs", "log-20130714"), path); @@ -48,7 +48,7 @@ public void TheLogFileIsNotRequiredToIncludeAnExtension() [Fact] public void TheLogFileIsNotRequiredToIncludeADirectory() { - var roller = new PathRoller("log-", RollingInterval.Day); + var roller = new PathRoller("log-", RollingInterval.Day, 1); var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); roller.GetLogFilePath(now, null, out var path); AssertEqualAbsolute("log-20130714", path); @@ -57,7 +57,7 @@ public void TheLogFileIsNotRequiredToIncludeADirectory() [Fact] public void MatchingExcludesSimilarButNonMatchingFiles() { - var roller = new PathRoller("log-.txt", RollingInterval.Day); + var roller = new PathRoller("log-.txt", RollingInterval.Day, 1); const string similar1 = "log-0.txt"; const string similar2 = "log-hello.txt"; var matched = roller.SelectMatches(new[] { similar1, similar2 }); @@ -67,7 +67,7 @@ public void MatchingExcludesSimilarButNonMatchingFiles() [Fact] public void TheDirectorSearchPatternUsesWildcardInPlaceOfDate() { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day, 1); Assert.Equal("log-*.txt", roller.DirectorySearchPattern); } @@ -76,7 +76,7 @@ public void TheDirectorSearchPatternUsesWildcardInPlaceOfDate() [InlineData("log-.txt", "log-2013121013.txt", "log-2013121013_031.txt", RollingInterval.Hour)] public void MatchingSelectsFiles(string template, string zeroth, string thirtyFirst, RollingInterval interval) { - var roller = new PathRoller(template, interval); + var roller = new PathRoller(template, interval, 1); var matched = roller.SelectMatches(new[] { zeroth, thirtyFirst }).ToArray(); Assert.Equal(2, matched.Length); Assert.Null(matched[0].SequenceNumber); @@ -88,7 +88,7 @@ public void MatchingSelectsFiles(string template, string zeroth, string thirtyFi [InlineData("log-.txt", "log-2015010110.txt", "log-2015010109.txt", RollingInterval.Hour)] public void MatchingParsesSubstitutions(string template, string newer, string older, RollingInterval interval) { - var roller = new PathRoller(template, interval); + var roller = new PathRoller(template, interval, 1); var matched = roller.SelectMatches(new[] { older, newer }).OrderByDescending(m => m.DateTime).Select(m => m.Filename).ToArray(); Assert.Equal(new[] { newer, older }, matched); }