public static DateTime?GetCurrentCheckpoint(this PersistentFileRollingInterval interval, DateTime instant) { switch (interval) { case PersistentFileRollingInterval.Infinite: return(null); case PersistentFileRollingInterval.Year: return(new DateTime(instant.Year, 1, 1, 0, 0, 0, instant.Kind)); case PersistentFileRollingInterval.Month: return(new DateTime(instant.Year, instant.Month, 1, 0, 0, 0, instant.Kind)); case PersistentFileRollingInterval.Day: return(new DateTime(instant.Year, instant.Month, instant.Day, 0, 0, 0, instant.Kind)); case PersistentFileRollingInterval.Hour: return(new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, 0, 0, instant.Kind)); case PersistentFileRollingInterval.Minute: return(new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, instant.Minute, 0, instant.Kind)); default: throw new ArgumentException("Invalid rolling interval"); } }
public static DateTime?GetNextCheckpoint(this PersistentFileRollingInterval interval, DateTime instant) { var current = GetCurrentCheckpoint(interval, instant); if (current == null) { return(null); } switch (interval) { case PersistentFileRollingInterval.Year: return(current.Value.AddYears(1)); case PersistentFileRollingInterval.Month: return(current.Value.AddMonths(1)); case PersistentFileRollingInterval.Day: return(current.Value.AddDays(1)); case PersistentFileRollingInterval.Hour: return(current.Value.AddHours(1)); case PersistentFileRollingInterval.Minute: return(current.Value.AddMinutes(1)); default: throw new ArgumentException("Invalid rolling interval"); } }
public static string GetFormat(this PersistentFileRollingInterval interval) { switch (interval) { case PersistentFileRollingInterval.Infinite: return(""); case PersistentFileRollingInterval.Year: return("yyyy"); case PersistentFileRollingInterval.Month: return("yyyyMM"); case PersistentFileRollingInterval.Day: return("yyyyMMdd"); case PersistentFileRollingInterval.Hour: return("yyyyMMddHH"); case PersistentFileRollingInterval.Minute: return("yyyyMMddHHmm"); default: throw new ArgumentException("Invalid rolling interval"); } }
public PathRoller(string path, PersistentFileRollingInterval interval) { if (path == null) { throw new ArgumentNullException(nameof(path)); } _interval = interval; _periodFormat = interval.GetFormat(); var pathDirectory = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(pathDirectory)) { pathDirectory = Directory.GetCurrentDirectory(); } _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) + "$"); DirectorySearchPattern = $"{_filenamePrefix}*{_filenameSuffix}"; }
private static Logger CreateLogger(string cwd, string?logFilePath, bool verboseFlagExists) { string logPath = logFilePath ?? _configuration["Serilog:WriteTo:0:Args:configure:0:Args:path"] ?? cwd + "/sarotate.log"; string minimumLogLevelConfig = verboseFlagExists ? "Verbose" : _configuration["Serilog:WriteTo:0:Args:configure:0:Args:restrictedToMinimumLevel"] ?? "Information"; string rollingIntervalConfig = _configuration["Serilog:WriteTo:0:Args:configure:0:Args:rollingInterval"] ?? "Day"; int fileSizeLimitBytes = int.Parse(_configuration["Serilog:WriteTo:0:Args:configure:0:Args:fileSizeLimitBytes"] ?? "5000000"); int retainedFileCountLimit = int.Parse(_configuration["Serilog:WriteTo:0:Args:configure:0:Args:retainedFileCountLimit"] ?? "5"); LogEventLevel minimumLogEventLevel = ConvertMinimumLogLevelConfigToLogEventLevel(minimumLogLevelConfig); PersistentFileRollingInterval rollingInterval = ConvertRollingIntervalConfigValueToEnum(rollingIntervalConfig); Logger logger = new LoggerConfiguration() .Enrich.FromLogContext() .Enrich.WithProperty("Application", "SARotate") .Enrich.With <GenericLogEnricher>() .MinimumLevel.ControlledBy(new LoggingLevelSwitch(minimumLogEventLevel)) .WriteTo.PersistentFile(logPath, fileSizeLimitBytes: fileSizeLimitBytes, persistentFileRollingInterval: rollingInterval, retainedFileCountLimit: retainedFileCountLimit) .WriteTo.Async(a => a.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:j}{NewLine}{Exception}")) .CreateLogger(); Log.Logger = logger; return(logger); }
public void NextIntervalTests(PersistentFileRollingInterval interval, DateTime instant, DateTime?currentCheckpoint, DateTime?nextCheckpoint) { var current = interval.GetCurrentCheckpoint(instant); Assert.Equal(currentCheckpoint, current); var next = interval.GetNextCheckpoint(instant); Assert.Equal(nextCheckpoint, next); }
public static LoggerConfiguration PersistentFile( this LoggerSinkConfiguration sinkConfiguration, ITextFormatter formatter, string path, LogEventLevel restrictedToMinimumLevel, long?fileSizeLimitBytes, LoggingLevelSwitch levelSwitch, bool buffered, bool shared, TimeSpan?flushToDiskInterval, PersistentFileRollingInterval persistentFileRollingInterval, bool rollOnFileSizeLimit, int?retainedFileCountLimit, Encoding encoding) { return(PersistentFile(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, persistentFileRollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, null)); }
public RollingFileSink(string path, ITextFormatter textFormatter, long?fileSizeLimitBytes, int?retainedFileCountLimit, Encoding encoding, bool buffered, bool shared, PersistentFileRollingInterval persistentFileRollingInterval, bool rollOnFileSizeLimit, FileLifecycleHooks hooks, bool keepFilename = false, bool rollOnEachProcessRun = true, bool useLastWriteAsTimestamp = false) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) { throw new ArgumentException("Negative value provided; file size limit must be non-negative."); } if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) { throw new ArgumentException( "Zero or negative value provided; retained file count limit must be at least 1."); } _roller = new PathRoller(path, persistentFileRollingInterval); _textFormatter = textFormatter; _fileSizeLimitBytes = fileSizeLimitBytes; _retainedFileCountLimit = retainedFileCountLimit; _encoding = encoding; _buffered = buffered; _shared = shared; _rollOnFileSizeLimit = rollOnFileSizeLimit; _hooks = hooks; _keepFilename = keepFilename; _rollOnEachProcessRun = rollOnEachProcessRun; _useLastWriteAsTimestamp = useLastWriteAsTimestamp; }
/// <summary> /// Write log events to the specified file. /// </summary> /// <param name="sinkConfiguration">Logger sink configuration.</param> /// <param name="path">Path to the file.</param> /// <param name="restrictedToMinimumLevel">The minimum level for /// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param> /// <param name="levelSwitch">A switch allowing the pass-through minimum level /// to be changed at runtime.</param> /// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param> /// <param name="outputTemplate">A message template describing the format used to write to the sink. /// the default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".</param> /// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow. /// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit /// will be written in full even if it exceeds the limit.</param> /// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default /// is false.</param> /// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param> /// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param> /// <param name="persistentFileRollingInterval">The interval at which logging will roll over to a new file.</param> /// <param name="rollOnFileSizeLimit">If <code>true</code>, a new file will be created when the file size limit is reached. Filenames /// will have a number appended in the format <code>_NNN</code>, with the first filename given no number.</param> /// <param name="retainedFileCountLimit">The maximum number of log files that will be retained, /// including the current log file. For unlimited retention, pass null. The default is 31.</param> /// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param> /// <param name="hooks">Optionally enables hooking into log file lifecycle events.</param> /// <param name="preserveLogFilename">Avoid the log file name to change after each roll, on roll the log file is copied to a new file and the current file is restarted empty</param> /// <param name="rollOnEachProcessRun">Roll the name of the log file every time the process starts.</param> /// <param name="useLastWriteAsTimestamp">When the file is rolled, the last write timestamp of the log file is used instead of the current timestamp.</param> /// <returns>Configuration object allowing method chaining.</returns> public static LoggerConfiguration PersistentFile( this LoggerSinkConfiguration sinkConfiguration, string path, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, string outputTemplate = DefaultOutputTemplate, IFormatProvider formatProvider = null, long?fileSizeLimitBytes = DefaultFileSizeLimitBytes, LoggingLevelSwitch levelSwitch = null, bool buffered = false, bool shared = false, TimeSpan?flushToDiskInterval = null, PersistentFileRollingInterval persistentFileRollingInterval = PersistentFileRollingInterval.Infinite, bool rollOnFileSizeLimit = false, int?retainedFileCountLimit = DefaultRetainedFileCountLimit, Encoding encoding = null, FileLifecycleHooks hooks = null, bool preserveLogFilename = true, bool rollOnEachProcessRun = true, bool useLastWriteAsTimestamp = false) { if (sinkConfiguration == null) { throw new ArgumentNullException(nameof(sinkConfiguration)); } if (path == null) { throw new ArgumentNullException(nameof(path)); } if (outputTemplate == null) { throw new ArgumentNullException(nameof(outputTemplate)); } var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); return(PersistentFile(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, persistentFileRollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks, preserveLogFilename, rollOnEachProcessRun, useLastWriteAsTimestamp)); }
static LoggerConfiguration ConfigureFile( this Func <ILogEventSink, LogEventLevel, LoggingLevelSwitch, LoggerConfiguration> addSink, ITextFormatter formatter, string path, LogEventLevel restrictedToMinimumLevel, long?fileSizeLimitBytes, LoggingLevelSwitch levelSwitch, bool buffered, bool propagateExceptions, bool shared, TimeSpan?flushToDiskInterval, Encoding encoding, PersistentFileRollingInterval persistentFileRollingInterval, bool rollOnFileSizeLimit, int?retainedFileCountLimit, FileLifecycleHooks hooks, bool preserveLogFilename = true, bool rollOnEachProcessRun = true, bool useLastWriteAsTimestamp = false) { if (addSink == null) { throw new ArgumentNullException(nameof(addSink)); } if (formatter == null) { throw new ArgumentNullException(nameof(formatter)); } if (path == null) { throw new ArgumentNullException(nameof(path)); } if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) { throw new ArgumentException("Negative value provided; file size limit must be non-negative.", nameof(fileSizeLimitBytes)); } if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) { throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); } 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)); } ILogEventSink sink; if (rollOnFileSizeLimit || persistentFileRollingInterval != PersistentFileRollingInterval.Infinite) { sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, persistentFileRollingInterval, rollOnFileSizeLimit, hooks, preserveLogFilename, rollOnEachProcessRun, useLastWriteAsTimestamp); } else { try { if (shared) { #pragma warning disable 618 sink = new SharedFileSink(path, formatter, fileSizeLimitBytes, encoding); #pragma warning restore 618 } else { sink = new FileSink(path, formatter, fileSizeLimitBytes, encoding, buffered, hooks); } } catch (Exception ex) { SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex); if (propagateExceptions) { throw; } return(addSink(new NullSink(), LevelAlias.Maximum, null)); } } if (flushToDiskInterval.HasValue) { #pragma warning disable 618 sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value); #pragma warning restore 618 } return(addSink(sink, restrictedToMinimumLevel, levelSwitch)); }
public void MatchingSelectsFiles(string template, string zeroth, string thirtyFirst, PersistentFileRollingInterval interval) { var roller = new PathRoller(template, interval); var matched = roller.SelectMatches(new[] { zeroth, thirtyFirst }).ToArray(); Assert.Equal(2, matched.Length); Assert.Equal(null, matched[0].SequenceNumber); Assert.Equal(31, matched[1].SequenceNumber); }
public void MatchingParsesSubstitutions(string template, string newer, string older, PersistentFileRollingInterval interval) { var roller = new PathRoller(template, interval); var matched = roller.SelectMatches(new[] { older, newer }).OrderByDescending(m => m.DateTime).Select(m => m.Filename).ToArray(); Assert.Equal(new[] { newer, older }, matched); }