diff --git a/.editorconfig b/.editorconfig index 102e19f..a853ae9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 +charset = utf-8 [*.{csproj,json,config,yml,props}] indent_size = 2 @@ -14,3 +15,8 @@ end_of_line = lf [*.{cmd, bat}] end_of_line = crlf + +# C# formatting settings - Namespace options +csharp_style_namespace_declarations = file_scoped:suggestion + +csharp_style_prefer_switch_expression = true:suggestion diff --git a/Build.ps1 b/Build.ps1 index 3c41f46..e0eebae 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -16,7 +16,7 @@ $commitHash = $(git rev-parse --short HEAD) $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] echo "build: Package version suffix is $suffix" -echo "build: Build version suffix is $buildSuffix" +echo "build: Build version suffix is $buildSuffix" foreach ($src in ls src/*) { Push-Location $src @@ -29,7 +29,7 @@ foreach ($src in ls src/*) { } else { & dotnet pack -c Release -o ..\..\artifacts --no-build } - if($LASTEXITCODE -ne 0) { exit 1 } + if($LASTEXITCODE -ne 0) { exit 1 } Pop-Location } diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d54992d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,16 @@ + + + latest + True + true + true + $(MSBuildThisFileDirectory)assets/Serilog.snk + enable + enable + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..6132714 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/build.sh b/build.sh index 3bb5fec..cf241dc 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/bin/bash -set -e +set -e dotnet --info dotnet --list-sdks dotnet restore diff --git a/example/Sample/Program.cs b/example/Sample/Program.cs index 89e6c95..d980bd8 100644 --- a/example/Sample/Program.cs +++ b/example/Sample/Program.cs @@ -1,38 +1,35 @@ -using System; -using System.IO; using Serilog; using Serilog.Debugging; -namespace Sample +namespace Sample; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - SelfLog.Enable(Console.Out); + SelfLog.Enable(Console.Out); - var sw = System.Diagnostics.Stopwatch.StartNew(); + var sw = System.Diagnostics.Stopwatch.StartNew(); - Log.Logger = new LoggerConfiguration() - .WriteTo.File("log.txt") - .CreateLogger(); + Log.Logger = new LoggerConfiguration() + .WriteTo.File("log.txt") + .CreateLogger(); - for (var i = 0; i < 1000000; ++i) - { - Log.Information("Hello, file logger!"); - } + for (var i = 0; i < 1000000; ++i) + { + Log.Information("Hello, file logger!"); + } - Log.CloseAndFlush(); + Log.CloseAndFlush(); - sw.Stop(); + sw.Stop(); - Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms"); - Console.WriteLine($"Size: {new FileInfo("log.txt").Length}"); + Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms"); + Console.WriteLine($"Size: {new FileInfo("log.txt").Length}"); - Console.WriteLine("Press any key to delete the temporary log file..."); - Console.ReadKey(true); + Console.WriteLine("Press any key to delete the temporary log file..."); + Console.ReadKey(true); - File.Delete("log.txt"); - } + File.Delete("log.txt"); } } diff --git a/example/Sample/Sample.csproj b/example/Sample/Sample.csproj index 5e5c833..a05ad36 100644 --- a/example/Sample/Sample.csproj +++ b/example/Sample/Sample.csproj @@ -1,12 +1,8 @@ - + net48;net6.0 - 8.0 - enable - Sample Exe - Sample true diff --git a/serilog-sinks-file.sln b/serilog-sinks-file.sln index 8d76bf7..b996c93 100644 --- a/serilog-sinks-file.sln +++ b/serilog-sinks-file.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.15 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33424.131 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}" EndProject @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5 appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 build.sh = build.sh + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets README.md = README.md assets\Serilog.snk = assets\Serilog.snk EndProjectSection diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 3518322..42e21cb 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2017 Serilog Contributors +// Copyright 2013-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. @@ -12,9 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; using System.ComponentModel; -using System.IO; using System.Text; using Serilog.Configuration; using Serilog.Core; @@ -27,546 +25,545 @@ // ReSharper disable RedundantArgumentDefaultValue, MethodOverloadWithOptionalParameter -namespace Serilog +namespace Serilog; + +/// Extends with methods to add file sinks. +public static class FileLoggerConfigurationExtensions { - /// Extends with methods to add file sinks. - public static class FileLoggerConfigurationExtensions - { - const int DefaultRetainedFileCountLimit = 31; // A long month of logs - const long DefaultFileSizeLimitBytes = 1L * 1024 * 1024 * 1024; // 1GB - const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"; + const int DefaultRetainedFileCountLimit = 31; // A long month of logs + const long DefaultFileSizeLimitBytes = 1L * 1024 * 1024 * 1024; // 1GB + const string DefaultOutputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"; - /// - /// Write log events to the specified file. - /// - /// Logger sink configuration. - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Supplies culture-specific formatting information, or null. - /// 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}". - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerSinkConfiguration sinkConfiguration, - string path, - LogEventLevel restrictedToMinimumLevel, - string outputTemplate, - IFormatProvider formatProvider, - long? fileSizeLimitBytes, - LoggingLevelSwitch levelSwitch, - bool buffered, - bool shared, - TimeSpan? flushToDiskInterval) - { - return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, - levelSwitch, buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null, null); - } + /// + /// Write log events to the specified file. + /// + /// Logger sink configuration. + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Supplies culture-specific formatting information, or null. + /// 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}". + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// Configuration object allowing method chaining. + /// The file will be written using the UTF-8 character set. + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerSinkConfiguration sinkConfiguration, + string path, + LogEventLevel restrictedToMinimumLevel, + string outputTemplate, + IFormatProvider formatProvider, + long? fileSizeLimitBytes, + LoggingLevelSwitch levelSwitch, + bool buffered, + bool shared, + TimeSpan? flushToDiskInterval) + { + return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, + levelSwitch, buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null, null); + } - /// - /// Write log events to the specified 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 - /// and specify the outputTemplate parameter instead. - /// - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerSinkConfiguration sinkConfiguration, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel, - long? fileSizeLimitBytes, - LoggingLevelSwitch levelSwitch, - bool buffered, - bool shared, - TimeSpan? flushToDiskInterval) - { - return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, - buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null, null); - } + /// + /// Write log events to the specified 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 + /// and specify the outputTemplate parameter instead. + /// + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// Configuration object allowing method chaining. + /// The file will be written using the UTF-8 character set. + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel, + long? fileSizeLimitBytes, + LoggingLevelSwitch levelSwitch, + bool buffered, + bool shared, + TimeSpan? flushToDiskInterval) + { + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, + buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null, null); + } - /// - /// Write log events to the specified file. - /// - /// Logger sink configuration. - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Supplies culture-specific formatting information, or null. - /// 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}". - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// The interval at which logging will roll over to a new file. - /// If true, a new file will be created when the file size limit is reached. Filenames - /// will have a number appended in the format _NNN, with the first filename given no number. - /// The maximum number of log files that will be retained, - /// including the current log file. For unlimited retention, pass null. The default is 31. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Configuration object allowing method chaining. - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerSinkConfiguration sinkConfiguration, - string path, - LogEventLevel restrictedToMinimumLevel, - string outputTemplate, - IFormatProvider formatProvider, - long? fileSizeLimitBytes, - LoggingLevelSwitch levelSwitch, - bool buffered, - bool shared, - TimeSpan? flushToDiskInterval, - RollingInterval rollingInterval, - bool rollOnFileSizeLimit, - int? retainedFileCountLimit, - Encoding encoding) - { - return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, levelSwitch, buffered, - shared, flushToDiskInterval, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, null); - } + /// + /// Write log events to the specified file. + /// + /// Logger sink configuration. + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Supplies culture-specific formatting information, or null. + /// 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}". + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// The interval at which logging will roll over to a new file. + /// If true, a new file will be created when the file size limit is reached. Filenames + /// will have a number appended in the format _NNN, with the first filename given no number. + /// The maximum number of log files that will be retained, + /// including the current log file. For unlimited retention, pass null. The default is 31. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Configuration object allowing method chaining. + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerSinkConfiguration sinkConfiguration, + string path, + LogEventLevel restrictedToMinimumLevel, + string outputTemplate, + IFormatProvider formatProvider, + long? fileSizeLimitBytes, + LoggingLevelSwitch levelSwitch, + bool buffered, + bool shared, + TimeSpan? flushToDiskInterval, + RollingInterval rollingInterval, + bool rollOnFileSizeLimit, + int? retainedFileCountLimit, + Encoding encoding) + { + return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, levelSwitch, buffered, + shared, flushToDiskInterval, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, null); + } - /// - /// Write log events to the specified 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 - /// and specify the outputTemplate parameter instead. - /// - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// The interval at which logging will roll over to a new file. - /// If true, a new file will be created when the file size limit is reached. Filenames - /// will have a number appended in the format _NNN, with the first filename given no number. - /// The maximum number of log files that will be retained, - /// including the current log file. For unlimited retention, pass null. The default is 31. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Configuration object allowing method chaining. - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerSinkConfiguration sinkConfiguration, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel, - long? fileSizeLimitBytes, - LoggingLevelSwitch levelSwitch, - bool buffered, - bool shared, - TimeSpan? flushToDiskInterval, - RollingInterval rollingInterval, - bool rollOnFileSizeLimit, - int? retainedFileCountLimit, - Encoding encoding) - { - return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, - shared, flushToDiskInterval, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, null); - } + /// + /// Write log events to the specified 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 + /// and specify the outputTemplate parameter instead. + /// + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// The interval at which logging will roll over to a new file. + /// If true, a new file will be created when the file size limit is reached. Filenames + /// will have a number appended in the format _NNN, with the first filename given no number. + /// The maximum number of log files that will be retained, + /// including the current log file. For unlimited retention, pass null. The default is 31. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Configuration object allowing method chaining. + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel, + long? fileSizeLimitBytes, + LoggingLevelSwitch levelSwitch, + bool buffered, + bool shared, + TimeSpan? flushToDiskInterval, + RollingInterval rollingInterval, + bool rollOnFileSizeLimit, + int? retainedFileCountLimit, + Encoding encoding) + { + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, + shared, flushToDiskInterval, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, null); + } - /// - /// Write log events to the specified file. - /// - /// Logger sink configuration. - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Supplies culture-specific formatting information, or null. - /// 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}". - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// The interval at which logging will roll over to a new file. - /// If true, a new file will be created when the file size limit is reached. Filenames - /// will have a number appended in the format _NNN, with the first filename given no number. - /// The maximum number of log files that will be retained, - /// including the current log file. For unlimited retention, pass null. The default is 31. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Optionally enables hooking into log file lifecycle events. - /// The maximum time after the end of an interval that a rolling log file will be retained. - /// Must be greater than or equal to . - /// Ignored if is . - /// The default is to retain files indefinitely. - /// Configuration object allowing method chaining. - /// When is null - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public static LoggerConfiguration File( - 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, - RollingInterval rollingInterval = RollingInterval.Infinite, - bool rollOnFileSizeLimit = false, - int? retainedFileCountLimit = DefaultRetainedFileCountLimit, - Encoding? encoding = null, - FileLifecycleHooks? hooks = null, - TimeSpan? retainedFileTimeLimit = null) - { - if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); - if (path == null) throw new ArgumentNullException(nameof(path)); - if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate)); + /// + /// Write log events to the specified file. + /// + /// Logger sink configuration. + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Supplies culture-specific formatting information, or null. + /// 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}". + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// The interval at which logging will roll over to a new file. + /// If true, a new file will be created when the file size limit is reached. Filenames + /// will have a number appended in the format _NNN, with the first filename given no number. + /// The maximum number of log files that will be retained, + /// including the current log file. For unlimited retention, pass null. The default is 31. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Optionally enables hooking into log file lifecycle events. + /// The maximum time after the end of an interval that a rolling log file will be retained. + /// Must be greater than or equal to . + /// Ignored if is . + /// The default is to retain files indefinitely. + /// Configuration object allowing method chaining. + /// When is null + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public static LoggerConfiguration File( + 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, + RollingInterval rollingInterval = RollingInterval.Infinite, + bool rollOnFileSizeLimit = false, + int? retainedFileCountLimit = DefaultRetainedFileCountLimit, + Encoding? encoding = null, + FileLifecycleHooks? hooks = null, + TimeSpan? retainedFileTimeLimit = null) + { + 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 File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, - levelSwitch, buffered, shared, flushToDiskInterval, - rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks, retainedFileTimeLimit); - } + var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, + levelSwitch, buffered, shared, flushToDiskInterval, + rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks, retainedFileTimeLimit); + } - /// - /// Write log events to the specified 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 - /// and specify the outputTemplate parameter instead. - /// - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// 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. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Allow the log file to be shared by multiple processes. The default is false. - /// If provided, a full disk flush will be performed periodically at the specified interval. - /// The interval at which logging will roll over to a new file. - /// If true, a new file will be created when the file size limit is reached. Filenames - /// will have a number appended in the format _NNN, with the first filename given no number. - /// The maximum number of log files that will be retained, - /// including the current log file. For unlimited retention, pass null. The default is 31. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Optionally enables hooking into log file lifecycle events. - /// The maximum time after the end of an interval that a rolling log file will be retained. - /// Must be greater than or equal to . - /// Ignored if is . - /// The default is to retain files indefinitely. - /// Configuration object allowing method chaining. - /// When is null - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public static LoggerConfiguration File( - this LoggerSinkConfiguration sinkConfiguration, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - long? fileSizeLimitBytes = DefaultFileSizeLimitBytes, - LoggingLevelSwitch? levelSwitch = null, - bool buffered = false, - bool shared = false, - TimeSpan? flushToDiskInterval = null, - RollingInterval rollingInterval = RollingInterval.Infinite, - bool rollOnFileSizeLimit = false, - int? retainedFileCountLimit = DefaultRetainedFileCountLimit, - Encoding? encoding = null, - FileLifecycleHooks? hooks = null, - TimeSpan? retainedFileTimeLimit = null) - { - if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); - if (formatter == null) throw new ArgumentNullException(nameof(formatter)); - if (path == null) throw new ArgumentNullException(nameof(path)); + /// + /// Write log events to the specified 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 + /// and specify the outputTemplate parameter instead. + /// + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// 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. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Allow the log file to be shared by multiple processes. The default is false. + /// If provided, a full disk flush will be performed periodically at the specified interval. + /// The interval at which logging will roll over to a new file. + /// If true, a new file will be created when the file size limit is reached. Filenames + /// will have a number appended in the format _NNN, with the first filename given no number. + /// The maximum number of log files that will be retained, + /// including the current log file. For unlimited retention, pass null. The default is 31. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Optionally enables hooking into log file lifecycle events. + /// The maximum time after the end of an interval that a rolling log file will be retained. + /// Must be greater than or equal to . + /// Ignored if is . + /// The default is to retain files indefinitely. + /// Configuration object allowing method chaining. + /// When is null + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public static LoggerConfiguration File( + this LoggerSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + long? fileSizeLimitBytes = DefaultFileSizeLimitBytes, + LoggingLevelSwitch? levelSwitch = null, + bool buffered = false, + bool shared = false, + TimeSpan? flushToDiskInterval = null, + RollingInterval rollingInterval = RollingInterval.Infinite, + bool rollOnFileSizeLimit = false, + int? retainedFileCountLimit = DefaultRetainedFileCountLimit, + Encoding? encoding = null, + FileLifecycleHooks? hooks = null, + TimeSpan? retainedFileTimeLimit = null) + { + if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); + if (formatter == null) throw new ArgumentNullException(nameof(formatter)); + if (path == null) throw new ArgumentNullException(nameof(path)); - return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, - buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, - retainedFileCountLimit, hooks, retainedFileTimeLimit); - } + return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, + buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, + retainedFileCountLimit, hooks, retainedFileTimeLimit); + } - /// - /// Write log events to the specified file. - /// - /// Logger sink configuration. - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Supplies culture-specific formatting information, or null. - /// 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}". - /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerAuditSinkConfiguration sinkConfiguration, - string path, - LogEventLevel restrictedToMinimumLevel, - string outputTemplate, - IFormatProvider formatProvider, - LoggingLevelSwitch levelSwitch) - { - return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, levelSwitch, null, null); - } + /// + /// Write log events to the specified file. + /// + /// Logger sink configuration. + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Supplies culture-specific formatting information, or null. + /// 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}". + /// Configuration object allowing method chaining. + /// The file will be written using the UTF-8 character set. + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerAuditSinkConfiguration sinkConfiguration, + string path, + LogEventLevel restrictedToMinimumLevel, + string outputTemplate, + IFormatProvider formatProvider, + LoggingLevelSwitch levelSwitch) + { + return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, levelSwitch, null, null); + } - /// - /// Write log events to the specified 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 - /// and specify the outputTemplate parameter instead. - /// - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. - /// When is null - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] - public static LoggerConfiguration File( - this LoggerAuditSinkConfiguration sinkConfiguration, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel, - LoggingLevelSwitch levelSwitch) - { - return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, null, null); - } + /// + /// Write log events to the specified 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 + /// and specify the outputTemplate parameter instead. + /// + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Configuration object allowing method chaining. + /// The file will be written using the UTF-8 character set. + /// When is null + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + [Obsolete("New code should not be compiled against this obsolete overload"), EditorBrowsable(EditorBrowsableState.Never)] + public static LoggerConfiguration File( + this LoggerAuditSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel, + LoggingLevelSwitch levelSwitch) + { + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, null, null); + } - /// - /// Write audit log events to the specified file. - /// - /// Logger sink configuration. - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Supplies culture-specific formatting information, or null. - /// 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}". - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Optionally enables hooking into log file lifecycle events. - /// Configuration object allowing method chaining. - /// When is null - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public static LoggerConfiguration File( - this LoggerAuditSinkConfiguration sinkConfiguration, - string path, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - string outputTemplate = DefaultOutputTemplate, - IFormatProvider? formatProvider = null, - LoggingLevelSwitch? levelSwitch = null, - Encoding? encoding = null, - FileLifecycleHooks? hooks = null) - { - if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); - if (path == null) throw new ArgumentNullException(nameof(path)); - if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate)); + /// + /// Write audit log events to the specified file. + /// + /// Logger sink configuration. + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Supplies culture-specific formatting information, or null. + /// 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}". + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Optionally enables hooking into log file lifecycle events. + /// Configuration object allowing method chaining. + /// When is null + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public static LoggerConfiguration File( + this LoggerAuditSinkConfiguration sinkConfiguration, + string path, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + string outputTemplate = DefaultOutputTemplate, + IFormatProvider? formatProvider = null, + LoggingLevelSwitch? levelSwitch = null, + Encoding? encoding = null, + FileLifecycleHooks? hooks = null) + { + 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 File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, encoding, hooks); - } + var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, encoding, hooks); + } - /// - /// Write audit log events to the specified 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 - /// and specify the outputTemplate parameter instead. - /// - /// Path to the file. - /// The minimum level for - /// events passed through the sink. Ignored when is specified. - /// A switch allowing the pass-through minimum level - /// to be changed at runtime. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Optionally enables hooking into log file lifecycle events. - /// Configuration object allowing method chaining. - /// When is null - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public static LoggerConfiguration File( - this LoggerAuditSinkConfiguration sinkConfiguration, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - LoggingLevelSwitch? levelSwitch = null, - Encoding? encoding = null, - FileLifecycleHooks? hooks = null) - { - if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); - if (formatter == null) throw new ArgumentNullException(nameof(formatter)); - if (path == null) throw new ArgumentNullException(nameof(path)); + /// + /// Write audit log events to the specified 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 + /// and specify the outputTemplate parameter instead. + /// + /// Path to the file. + /// The minimum level for + /// events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level + /// to be changed at runtime. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Optionally enables hooking into log file lifecycle events. + /// Configuration object allowing method chaining. + /// When is null + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public static LoggerConfiguration File( + this LoggerAuditSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch? levelSwitch = null, + Encoding? encoding = null, + FileLifecycleHooks? hooks = null) + { + if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); + if (formatter == null) throw new ArgumentNullException(nameof(formatter)); + 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); - } + return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true, + false, null, encoding, RollingInterval.Infinite, false, null, hooks, null); + } - static LoggerConfiguration ConfigureFile( - this Func addSink, - ITextFormatter formatter, - string path, - LogEventLevel restrictedToMinimumLevel, - long? fileSizeLimitBytes, - LoggingLevelSwitch? levelSwitch, - bool buffered, - bool propagateExceptions, - bool shared, - TimeSpan? flushToDiskInterval, - Encoding? encoding, - RollingInterval rollingInterval, - bool rollOnFileSizeLimit, - int? retainedFileCountLimit, - FileLifecycleHooks? hooks, - TimeSpan? retainedFileTimeLimit) - { - 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 < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null.", nameof(fileSizeLimitBytes)); - if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); - 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)); + static LoggerConfiguration ConfigureFile( + this Func addSink, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel, + long? fileSizeLimitBytes, + LoggingLevelSwitch? levelSwitch, + bool buffered, + bool propagateExceptions, + bool shared, + TimeSpan? flushToDiskInterval, + Encoding? encoding, + RollingInterval rollingInterval, + bool rollOnFileSizeLimit, + int? retainedFileCountLimit, + FileLifecycleHooks? hooks, + TimeSpan? retainedFileTimeLimit) + { + 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 < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null.", nameof(fileSizeLimitBytes)); + if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); + 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)); - ILogEventSink sink; + ILogEventSink sink; - try + try + { + if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) + { + sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, hooks, retainedFileTimeLimit); + } + else { - if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) + if (shared) { - sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, hooks, retainedFileTimeLimit); +#pragma warning disable 618 + sink = new SharedFileSink(path, formatter, fileSizeLimitBytes, encoding); +#pragma warning restore 618 } else { - 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); - } - + sink = new FileSink(path, formatter, fileSizeLimitBytes, encoding, buffered, hooks); } + } - catch (Exception ex) - { - SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex); + } + catch (Exception ex) + { + SelfLog.WriteLine("Unable to open file sink for {0}: {1}", path, ex); - if (propagateExceptions) - throw; + if (propagateExceptions) + throw; - return addSink(new NullSink(), LevelAlias.Maximum, null); - } + return addSink(new NullSink(), LevelAlias.Maximum, null); + } - if (flushToDiskInterval.HasValue) - { + if (flushToDiskInterval.HasValue) + { #pragma warning disable 618 - sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value); + sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value); #pragma warning restore 618 - } - - return addSink(sink, restrictedToMinimumLevel, levelSwitch); } + + return addSink(sink, restrictedToMinimumLevel, levelSwitch); } } diff --git a/src/Serilog.Sinks.File/Properties/AssemblyInfo.cs b/src/Serilog.Sinks.File/Properties/AssemblyInfo.cs index 93017cb..b431c64 100644 --- a/src/Serilog.Sinks.File/Properties/AssemblyInfo.cs +++ b/src/Serilog.Sinks.File/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ -using System; -using System.Reflection; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly: CLSCompliant(true)] diff --git a/src/Serilog.Sinks.File/RollingInterval.cs b/src/Serilog.Sinks.File/RollingInterval.cs index 9fac848..55582ec 100644 --- a/src/Serilog.Sinks.File/RollingInterval.cs +++ b/src/Serilog.Sinks.File/RollingInterval.cs @@ -12,41 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog +namespace Serilog; + +/// +/// Specifies the frequency at which the log file should roll. +/// +public enum RollingInterval { /// - /// Specifies the frequency at which the log file should roll. + /// The log file will never roll; no time period information will be appended to the log file name. /// - public enum RollingInterval - { - /// - /// The log file will never roll; no time period information will be appended to the log file name. - /// - Infinite, + Infinite, - /// - /// Roll every year. Filenames will have a four-digit year appended in the pattern yyyy. - /// - Year, + /// + /// Roll every year. Filenames will have a four-digit year appended in the pattern yyyy. + /// + Year, - /// - /// Roll every calendar month. Filenames will have yyyyMM appended. - /// - Month, + /// + /// Roll every calendar month. Filenames will have yyyyMM appended. + /// + Month, - /// - /// Roll every day. Filenames will have yyyyMMdd appended. - /// - Day, + /// + /// Roll every day. Filenames will have yyyyMMdd appended. + /// + Day, - /// - /// Roll every hour. Filenames will have yyyyMMddHH appended. - /// - Hour, + /// + /// Roll every hour. Filenames will have yyyyMMddHH appended. + /// + Hour, - /// - /// Roll every minute. Filenames will have yyyyMMddHHmm appended. - /// - Minute - } + /// + /// Roll every minute. Filenames will have yyyyMMddHHmm appended. + /// + Minute } diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 3eb3c1e..e9edc97 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -5,12 +5,7 @@ 5.0.1 Serilog Contributors net45;netstandard1.3;netstandard2.0;netstandard2.1;net5.0;net6.0 - 8.0 - enable true - ../../assets/Serilog.snk - true - true serilog;file images\icon.png https://serilog.net/images/serilog-sink-nuget.png @@ -19,7 +14,6 @@ https://github.com/serilog/serilog-sinks-file git Serilog - true true false true @@ -29,9 +23,9 @@ - + - + diff --git a/src/Serilog.Sinks.File/Sinks/File/Clock.cs b/src/Serilog.Sinks.File/Sinks/File/Clock.cs index b7cf3cc..7624fb4 100644 --- a/src/Serilog.Sinks.File/Sinks/File/Clock.cs +++ b/src/Serilog.Sinks.File/Sinks/File/Clock.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. @@ -12,27 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; +namespace Serilog.Sinks.File; -namespace Serilog.Sinks.File +static class Clock { - static class Clock - { - static Func _dateTimeNow = () => DateTime.Now; + static Func _dateTimeNow = () => DateTime.Now; - [ThreadStatic] - static DateTime _testDateTimeNow; + [ThreadStatic] + static DateTime _testDateTimeNow; - public static DateTime DateTimeNow => _dateTimeNow(); + public static DateTime DateTimeNow => _dateTimeNow(); - // Time is set per thread to support parallel - // If any thread uses the clock in test mode, all threads - // must use it in test mode; once set to test mode only - // terminating the application returns it to normal use. - public static void SetTestDateTimeNow(DateTime now) - { - _testDateTimeNow = now; - _dateTimeNow = () => _testDateTimeNow; - } + // Time is set per thread to support parallel + // If any thread uses the clock in test mode, all threads + // must use it in test mode; once set to test mode only + // terminating the application returns it to normal use. + public static void SetTestDateTimeNow(DateTime now) + { + _testDateTimeNow = now; + _dateTimeNow = () => _testDateTimeNow; } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs index cf27bfe..2c3034c 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs @@ -12,35 +12,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.IO; using System.Text; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +class FileLifeCycleHookChain : FileLifecycleHooks { - class FileLifeCycleHookChain : FileLifecycleHooks - { - private readonly FileLifecycleHooks _first; - private readonly FileLifecycleHooks _second; + private readonly FileLifecycleHooks _first; + private readonly FileLifecycleHooks _second; - public FileLifeCycleHookChain(FileLifecycleHooks first, FileLifecycleHooks second) - { - _first = first ?? throw new ArgumentNullException(nameof(first)); - _second = second ?? throw new ArgumentNullException(nameof(second)); - } + public FileLifeCycleHookChain(FileLifecycleHooks first, FileLifecycleHooks second) + { + _first = first ?? throw new ArgumentNullException(nameof(first)); + _second = second ?? throw new ArgumentNullException(nameof(second)); + } - public override Stream OnFileOpened(string path, Stream underlyingStream, Encoding encoding) - { - var firstStreamResult = _first.OnFileOpened(path, underlyingStream, encoding); - var secondStreamResult = _second.OnFileOpened(path, firstStreamResult, encoding); + public override Stream OnFileOpened(string path, Stream underlyingStream, Encoding encoding) + { + var firstStreamResult = _first.OnFileOpened(path, underlyingStream, encoding); + var secondStreamResult = _second.OnFileOpened(path, firstStreamResult, encoding); - return secondStreamResult; - } + return secondStreamResult; + } - public override void OnFileDeleting(string path) - { - _first.OnFileDeleting(path); - _second.OnFileDeleting(path); - } + public override void OnFileDeleting(string path) + { + _first.OnFileDeleting(path); + _second.OnFileDeleting(path); } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index 26fd1e2..ac6df00 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -12,68 +12,66 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.IO; using System.Text; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Enables hooking into log file lifecycle events. +/// Hooks run synchronously and therefore may affect responsiveness of the application if long operations are performed. +/// +public abstract class FileLifecycleHooks { /// - /// Enables hooking into log file lifecycle events. - /// Hooks run synchronously and therefore may affect responsiveness of the application if long operations are performed. + /// Initialize or wrap the opened on the log file. This can be used to write + /// file headers, or wrap the stream in another that adds buffering, compression, encryption, etc. The underlying + /// file may or may not be empty when this method is called. /// - public abstract class FileLifecycleHooks - { - /// - /// Initialize or wrap the opened on the log file. This can be used to write - /// file headers, or wrap the stream in another that adds buffering, compression, encryption, etc. The underlying - /// file may or may not be empty when this method is called. - /// - /// - /// A value must be returned from overrides of this method. Serilog will flush and/or dispose the returned value, but will not - /// dispose the stream initially passed in unless it is itself returned. - /// - /// The full path to the log file. - /// The underlying opened on the log file. - /// The encoding to use when reading/writing to the stream. - /// The Serilog should use when writing events to the log file. - public virtual Stream OnFileOpened(string path, Stream underlyingStream, Encoding encoding) => OnFileOpened(underlyingStream, encoding); + /// + /// A value must be returned from overrides of this method. Serilog will flush and/or dispose the returned value, but will not + /// dispose the stream initially passed in unless it is itself returned. + /// + /// The full path to the log file. + /// The underlying opened on the log file. + /// The encoding to use when reading/writing to the stream. + /// The Serilog should use when writing events to the log file. + public virtual Stream OnFileOpened(string path, Stream underlyingStream, Encoding encoding) => OnFileOpened(underlyingStream, encoding); - /// - /// Initialize or wrap the opened on the log file. This can be used to write - /// file headers, or wrap the stream in another that adds buffering, compression, encryption, etc. The underlying - /// file may or may not be empty when this method is called. - /// - /// - /// A value must be returned from overrides of this method. Serilog will flush and/or dispose the returned value, but will not - /// dispose the stream initially passed in unless it is itself returned. - /// - /// The underlying opened on the log file. - /// The encoding to use when reading/writing to the stream. - /// The Serilog should use when writing events to the log file. - public virtual Stream OnFileOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; + /// + /// Initialize or wrap the opened on the log file. This can be used to write + /// file headers, or wrap the stream in another that adds buffering, compression, encryption, etc. The underlying + /// file may or may not be empty when this method is called. + /// + /// + /// A value must be returned from overrides of this method. Serilog will flush and/or dispose the returned value, but will not + /// dispose the stream initially passed in unless it is itself returned. + /// + /// The underlying opened on the log file. + /// The encoding to use when reading/writing to the stream. + /// The Serilog should use when writing events to the log file. + public virtual Stream OnFileOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; - /// - /// Called before an obsolete (rolling) log file is deleted. - /// This can be used to copy old logs to an archive location or send to a backup server. - /// - /// The full path to the file being deleted. - public virtual void OnFileDeleting(string path) {} + /// + /// Called before an obsolete (rolling) log file is deleted. + /// This can be used to copy old logs to an archive location or send to a backup server. + /// + /// The full path to the file being deleted. + public virtual void OnFileDeleting(string path) {} - /// - /// Creates a chain of that have their methods called sequentially - /// Can be used to compose together; e.g. add header information to each log file and - /// compress it. - /// - /// - /// - /// var hooks = new GZipHooks().Then(new HeaderWriter("File Header")); - /// - /// - /// The next to have its methods called in the chain - /// - public FileLifecycleHooks Then(FileLifecycleHooks next) - { - return new FileLifeCycleHookChain(this, next); - } + /// + /// Creates a chain of that have their methods called sequentially + /// Can be used to compose together; e.g. add header information to each log file and + /// compress it. + /// + /// + /// + /// var hooks = new GZipHooks().Then(new HeaderWriter("File Header")); + /// + /// + /// The next to have its methods called in the chain + /// + public FileLifecycleHooks Then(FileLifecycleHooks next) + { + return new FileLifeCycleHookChain(this, next); } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 611b45d..9e39892 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -12,140 +12,137 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.IO; using System.Text; using Serilog.Events; using Serilog.Formatting; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Write log events to a disk file. +/// +public sealed class FileSink : IFileSink, IDisposable { - /// - /// Write log events to a disk file. - /// - public sealed class FileSink : IFileSink, IDisposable + readonly TextWriter _output; + readonly FileStream _underlyingStream; + readonly ITextFormatter _textFormatter; + readonly long? _fileSizeLimitBytes; + readonly bool _buffered; + readonly object _syncRoot = new(); + readonly WriteCountingStream? _countingStreamWrapper; + + /// Construct a . + /// Path to the file. + /// Formatter used to convert log events to text. + /// 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. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Indicates if flushing to the output file can be buffered or not. The default + /// is false. + /// Configuration object allowing method chaining. + /// This constructor preserves compatibility with early versions of the public API. New code should not depend on this type. + /// When is null + /// When is null or less than 0 + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + [Obsolete("This type and constructor will be removed from the public API in a future version; use `WriteTo.File()` instead.")] + public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null, bool buffered = false) + : this(path, textFormatter, fileSizeLimitBytes, encoding, buffered, null) { - readonly TextWriter _output; - readonly FileStream _underlyingStream; - readonly ITextFormatter _textFormatter; - readonly long? _fileSizeLimitBytes; - readonly bool _buffered; - readonly object _syncRoot = new object(); - readonly WriteCountingStream? _countingStreamWrapper; - - /// Construct a . - /// Path to the file. - /// Formatter used to convert log events to text. - /// 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. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Indicates if flushing to the output file can be buffered or not. The default - /// is false. - /// Configuration object allowing method chaining. - /// This constructor preserves compatibility with early versions of the public API. New code should not depend on this type. - /// When is null - /// When is null or less than 0 - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - [Obsolete("This type and constructor will be removed from the public API in a future version; use `WriteTo.File()` instead.")] - public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null, bool buffered = false) - : this(path, textFormatter, fileSizeLimitBytes, encoding, buffered, null) - { - } + } - // This overload should be used internally; the overload above maintains compatibility with the earlier public API. - internal FileSink( - string path, - ITextFormatter textFormatter, - long? fileSizeLimitBytes, - Encoding? encoding, - bool buffered, - FileLifecycleHooks? hooks) + // This overload should be used internally; the overload above maintains compatibility with the earlier public API. + internal FileSink( + string path, + ITextFormatter textFormatter, + long? fileSizeLimitBytes, + Encoding? encoding, + bool buffered, + FileLifecycleHooks? hooks) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); + _fileSizeLimitBytes = fileSizeLimitBytes; + _buffered = buffered; + + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); - _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); - _fileSizeLimitBytes = fileSizeLimitBytes; - _buffered = buffered; - - var directory = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } + Directory.CreateDirectory(directory); + } - Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); - outputStream.Seek(0, SeekOrigin.End); + Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); + outputStream.Seek(0, SeekOrigin.End); - if (_fileSizeLimitBytes != null) - { - outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); - } - - // Parameter reassignment. - encoding = encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + if (_fileSizeLimitBytes != null) + { + outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); + } - if (hooks != null) - { - outputStream = hooks.OnFileOpened(path, outputStream, encoding) ?? - throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnFileOpened)}(...)` returned `null`."); - } + // Parameter reassignment. + encoding ??= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - _output = new StreamWriter(outputStream, encoding); + if (hooks != null) + { + outputStream = hooks.OnFileOpened(path, outputStream, encoding) ?? + throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnFileOpened)}(...)` returned `null`."); } - bool IFileSink.EmitOrOverflow(LogEvent logEvent) + _output = new StreamWriter(outputStream, encoding); + } + + bool IFileSink.EmitOrOverflow(LogEvent logEvent) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + lock (_syncRoot) { - if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - lock (_syncRoot) + if (_fileSizeLimitBytes != null) { - if (_fileSizeLimitBytes != null) - { - if (_countingStreamWrapper!.CountedLength >= _fileSizeLimitBytes.Value) - return false; - } + if (_countingStreamWrapper!.CountedLength >= _fileSizeLimitBytes.Value) + return false; + } - _textFormatter.Format(logEvent, _output); - if (!_buffered) - _output.Flush(); + _textFormatter.Format(logEvent, _output); + if (!_buffered) + _output.Flush(); - return true; - } + return true; } + } - /// - /// Emit the provided log event to the sink. - /// - /// The log event to write. - /// When is null - public void Emit(LogEvent logEvent) - { - ((IFileSink) this).EmitOrOverflow(logEvent); - } + /// + /// Emit the provided log event to the sink. + /// + /// The log event to write. + /// When is null + public void Emit(LogEvent logEvent) + { + ((IFileSink) this).EmitOrOverflow(logEvent); + } - /// - public void Dispose() + /// + public void Dispose() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _output.Dispose(); - } + _output.Dispose(); } + } - /// - public void FlushToDisk() + /// + public void FlushToDisk() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _output.Flush(); - _underlyingStream.Flush(true); - } + _output.Flush(); + _underlyingStream.Flush(true); } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/IFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/IFileSink.cs index 89268ab..f1e2d7e 100644 --- a/src/Serilog.Sinks.File/Sinks/File/IFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/IFileSink.cs @@ -15,15 +15,14 @@ using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Exists only for the convenience of , which +/// switches implementations based on sharing. Would refactor, but preserving +/// backwards compatibility. +/// +interface IFileSink : ILogEventSink, IFlushableFileSink { - /// - /// Exists only for the convenience of , which - /// switches implementations based on sharing. Would refactor, but preserving - /// backwards compatibility. - /// - interface IFileSink : ILogEventSink, IFlushableFileSink - { - bool EmitOrOverflow(LogEvent logEvent); - } + bool EmitOrOverflow(LogEvent logEvent); } diff --git a/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs index 75d6e52..ad9e407 100644 --- a/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/IFlushableFileSink.cs @@ -12,16 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Supported by (file-based) sinks that can be explicitly flushed. +/// +public interface IFlushableFileSink { /// - /// Supported by (file-based) sinks that can be explicitly flushed. + /// Flush buffered contents to disk. /// - public interface IFlushableFileSink - { - /// - /// Flush buffered contents to disk. - /// - void FlushToDisk(); - } + void FlushToDisk(); } diff --git a/src/Serilog.Sinks.File/Sinks/File/IOErrors.cs b/src/Serilog.Sinks.File/Sinks/File/IOErrors.cs index 36fe8bc..faa121c 100644 --- a/src/Serilog.Sinks.File/Sinks/File/IOErrors.cs +++ b/src/Serilog.Sinks.File/Sinks/File/IOErrors.cs @@ -12,20 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System.IO; +namespace Serilog.Sinks.File; -namespace Serilog.Sinks.File +static class IOErrors { - static class IOErrors + public static bool IsLockedFile(IOException ex) { - public static bool IsLockedFile(IOException ex) - { #if HRESULTS - var errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex) & ((1 << 16) - 1); - return errorCode == 32 || errorCode == 33; + var errorCode = System.Runtime.InteropServices.Marshal.GetHRForException(ex) & ((1 << 16) - 1); + return errorCode == 32 || errorCode == 33; #else - return true; + return true; #endif - } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/NullSink.cs b/src/Serilog.Sinks.File/Sinks/File/NullSink.cs index 5e9bb2c..8992197 100644 --- a/src/Serilog.Sinks.File/Sinks/File/NullSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/NullSink.cs @@ -15,16 +15,15 @@ using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// An instance of this sink may be substituted when an instance of the +/// is unable to be constructed. +/// +class NullSink : ILogEventSink { - /// - /// An instance of this sink may be substituted when an instance of the - /// is unable to be constructed. - /// - class NullSink : ILogEventSink + public void Emit(LogEvent logEvent) { - public void Emit(LogEvent logEvent) - { - } } } \ No newline at end of file diff --git a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs index 79a6915..922682d 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs @@ -12,106 +12,102 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text.RegularExpressions; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +class PathRoller { - class PathRoller - { - const string PeriodMatchGroup = "period"; - const string SequenceNumberMatchGroup = "sequence"; + const string PeriodMatchGroup = "period"; + const string SequenceNumberMatchGroup = "sequence"; - readonly string _directory; - readonly string _filenamePrefix; - readonly string _filenameSuffix; - readonly Regex _filenameMatcher; + readonly string _directory; + readonly string _filenamePrefix; + readonly string _filenameSuffix; + readonly Regex _filenameMatcher; - readonly RollingInterval _interval; - readonly string _periodFormat; + readonly RollingInterval _interval; + readonly string _periodFormat; - public PathRoller(string path, RollingInterval 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) + - "$", - RegexOptions.Compiled); - - DirectorySearchPattern = $"{_filenamePrefix}*{_filenameSuffix}"; - } + public PathRoller(string path, RollingInterval 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) + + "$", + RegexOptions.Compiled); + + DirectorySearchPattern = $"{_filenamePrefix}*{_filenameSuffix}"; + } - public string LogFileDirectory => _directory; + public string LogFileDirectory => _directory; - public string DirectorySearchPattern { get; } + public string DirectorySearchPattern { get; } - public void GetLogFilePath(DateTime date, int? sequenceNumber, out string path) - { - var currentCheckpoint = GetCurrentCheckpoint(date); + public void GetLogFilePath(DateTime date, int? sequenceNumber, out string path) + { + var currentCheckpoint = GetCurrentCheckpoint(date); - var tok = currentCheckpoint?.ToString(_periodFormat, CultureInfo.InvariantCulture) ?? ""; + var tok = currentCheckpoint?.ToString(_periodFormat, CultureInfo.InvariantCulture) ?? ""; - if (sequenceNumber != null) - tok += "_" + sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture); + if (sequenceNumber != null) + tok += "_" + sequenceNumber.Value.ToString("000", CultureInfo.InvariantCulture); - path = Path.Combine(_directory, _filenamePrefix + tok + _filenameSuffix); - } + path = Path.Combine(_directory, _filenamePrefix + tok + _filenameSuffix); + } - public IEnumerable SelectMatches(IEnumerable filenames) + public IEnumerable SelectMatches(IEnumerable filenames) + { + foreach (var filename in filenames) { - foreach (var filename in filenames) - { - var match = _filenameMatcher.Match(filename); - if (!match.Success) - continue; + var match = _filenameMatcher.Match(filename); + if (!match.Success) + continue; - int? inc = null; - var incGroup = match.Groups[SequenceNumberMatchGroup]; - if (incGroup.Captures.Count != 0) - { - var incPart = incGroup.Captures[0].Value.Substring(1); - inc = int.Parse(incPart, CultureInfo.InvariantCulture); - } + int? inc = null; + var incGroup = match.Groups[SequenceNumberMatchGroup]; + if (incGroup.Captures.Count != 0) + { + var incPart = incGroup.Captures[0].Value.Substring(1); + inc = int.Parse(incPart, CultureInfo.InvariantCulture); + } - DateTime? period = null; - var periodGroup = match.Groups[PeriodMatchGroup]; - if (periodGroup.Captures.Count != 0) + DateTime? period = null; + var periodGroup = match.Groups[PeriodMatchGroup]; + if (periodGroup.Captures.Count != 0) + { + var dateTimePart = periodGroup.Captures[0].Value; + if (DateTime.TryParseExact( + dateTimePart, + _periodFormat, + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var dateTime)) { - var dateTimePart = periodGroup.Captures[0].Value; - if (DateTime.TryParseExact( - dateTimePart, - _periodFormat, - CultureInfo.InvariantCulture, - DateTimeStyles.None, - out var dateTime)) - { - period = dateTime; - } + period = dateTime; } - - yield return new RollingLogFile(filename, period, inc); } + + yield return new RollingLogFile(filename, period, inc); } + } - public DateTime? GetCurrentCheckpoint(DateTime instant) => _interval.GetCurrentCheckpoint(instant); + public DateTime? GetCurrentCheckpoint(DateTime instant) => _interval.GetCurrentCheckpoint(instant); - public DateTime? GetNextCheckpoint(DateTime instant) => _interval.GetNextCheckpoint(instant); - } + public DateTime? GetNextCheckpoint(DateTime instant) => _interval.GetNextCheckpoint(instant); } diff --git a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs index 1b931c9..c4272d9 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs @@ -12,75 +12,72 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.Threading; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// A sink wrapper that periodically flushes the wrapped sink to disk. +/// +[Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(flushToDiskInterval:)` instead.")] +public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable { + readonly ILogEventSink _sink; + readonly Timer _timer; + int _flushRequired; + /// - /// A sink wrapper that periodically flushes the wrapped sink to disk. + /// Construct a that wraps + /// and flushes it at the specified . /// - [Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(flushToDiskInterval:)` instead.")] - public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable + /// The sink to wrap. + /// The interval at which to flush the underlying sink. + /// When is null + public PeriodicFlushToDiskSink(ILogEventSink sink, TimeSpan flushInterval) { - readonly ILogEventSink _sink; - readonly Timer _timer; - int _flushRequired; + _sink = sink ?? throw new ArgumentNullException(nameof(sink)); - /// - /// Construct a that wraps - /// and flushes it at the specified . - /// - /// The sink to wrap. - /// The interval at which to flush the underlying sink. - /// When is null - public PeriodicFlushToDiskSink(ILogEventSink sink, TimeSpan flushInterval) + if (sink is IFlushableFileSink flushable) { - _sink = sink ?? throw new ArgumentNullException(nameof(sink)); - - if (sink is IFlushableFileSink flushable) - { - _timer = new Timer(_ => FlushToDisk(flushable), null, flushInterval, flushInterval); - } - else - { - _timer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); - SelfLog.WriteLine("{0} configured to flush {1}, but {2} not implemented", typeof(PeriodicFlushToDiskSink), sink, nameof(IFlushableFileSink)); - } + _timer = new Timer(_ => FlushToDisk(flushable), null, flushInterval, flushInterval); } - - /// - public void Emit(LogEvent logEvent) + else { - _sink.Emit(logEvent); - Interlocked.Exchange(ref _flushRequired, 1); + _timer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + SelfLog.WriteLine("{0} configured to flush {1}, but {2} not implemented", typeof(PeriodicFlushToDiskSink), sink, nameof(IFlushableFileSink)); } + } - /// - public void Dispose() - { - _timer.Dispose(); - (_sink as IDisposable)?.Dispose(); - } + /// + public void Emit(LogEvent logEvent) + { + _sink.Emit(logEvent); + Interlocked.Exchange(ref _flushRequired, 1); + } + + /// + public void Dispose() + { + _timer.Dispose(); + (_sink as IDisposable)?.Dispose(); + } - void FlushToDisk(IFlushableFileSink flushable) + void FlushToDisk(IFlushableFileSink flushable) + { + try { - try - { - if (Interlocked.CompareExchange(ref _flushRequired, 0, 1) == 1) - { - // May throw ObjectDisposedException, since we're not trying to synchronize - // anything here in the wrapper. - flushable.FlushToDisk(); - } - } - catch (Exception ex) + if (Interlocked.CompareExchange(ref _flushRequired, 0, 1) == 1) { - SelfLog.WriteLine("{0} could not flush the underlying sink to disk: {1}", typeof(PeriodicFlushToDiskSink), ex); + // May throw ObjectDisposedException, since we're not trying to synchronize + // anything here in the wrapper. + flushable.FlushToDisk(); } } + catch (Exception ex) + { + SelfLog.WriteLine("{0} could not flush the underlying sink to disk: {1}", typeof(PeriodicFlushToDiskSink), ex); + } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index dccb802..373a97e 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2017 Serilog Contributors +// Copyright 2013-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. @@ -12,248 +12,244 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.IO; -using System.Linq; using System.Text; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; using Serilog.Formatting; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable { - sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable + readonly PathRoller _roller; + readonly ITextFormatter _textFormatter; + readonly long? _fileSizeLimitBytes; + readonly int? _retainedFileCountLimit; + readonly TimeSpan? _retainedFileTimeLimit; + readonly Encoding? _encoding; + readonly bool _buffered; + readonly bool _shared; + readonly bool _rollOnFileSizeLimit; + readonly FileLifecycleHooks? _hooks; + + readonly object _syncRoot = new(); + bool _isDisposed; + DateTime? _nextCheckpoint; + IFileSink? _currentFile; + int? _currentFileSequence; + + public RollingFileSink(string path, + ITextFormatter textFormatter, + long? fileSizeLimitBytes, + int? retainedFileCountLimit, + Encoding? encoding, + bool buffered, + bool shared, + RollingInterval rollingInterval, + bool rollOnFileSizeLimit, + FileLifecycleHooks? hooks, + TimeSpan? retainedFileTimeLimit) { - readonly PathRoller _roller; - readonly ITextFormatter _textFormatter; - readonly long? _fileSizeLimitBytes; - readonly int? _retainedFileCountLimit; - readonly TimeSpan? _retainedFileTimeLimit; - readonly Encoding? _encoding; - readonly bool _buffered; - readonly bool _shared; - readonly bool _rollOnFileSizeLimit; - readonly FileLifecycleHooks? _hooks; - - readonly object _syncRoot = new object(); - bool _isDisposed; - DateTime? _nextCheckpoint; - IFileSink? _currentFile; - int? _currentFileSequence; - - public RollingFileSink(string path, - ITextFormatter textFormatter, - long? fileSizeLimitBytes, - int? retainedFileCountLimit, - Encoding? encoding, - bool buffered, - bool shared, - RollingInterval rollingInterval, - bool rollOnFileSizeLimit, - FileLifecycleHooks? hooks, - TimeSpan? retainedFileTimeLimit) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); - if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 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)); - - _roller = new PathRoller(path, rollingInterval); - _textFormatter = textFormatter; - _fileSizeLimitBytes = fileSizeLimitBytes; - _retainedFileCountLimit = retainedFileCountLimit; - _retainedFileTimeLimit = retainedFileTimeLimit; - _encoding = encoding; - _buffered = buffered; - _shared = shared; - _rollOnFileSizeLimit = rollOnFileSizeLimit; - _hooks = hooks; - } + if (path == null) throw new ArgumentNullException(nameof(path)); + if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); + if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 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)); + + _roller = new PathRoller(path, rollingInterval); + _textFormatter = textFormatter; + _fileSizeLimitBytes = fileSizeLimitBytes; + _retainedFileCountLimit = retainedFileCountLimit; + _retainedFileTimeLimit = retainedFileTimeLimit; + _encoding = encoding; + _buffered = buffered; + _shared = shared; + _rollOnFileSizeLimit = rollOnFileSizeLimit; + _hooks = hooks; + } - public void Emit(LogEvent logEvent) - { - if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + public void Emit(LogEvent logEvent) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - lock (_syncRoot) - { - if (_isDisposed) throw new ObjectDisposedException("The log file has been disposed."); + lock (_syncRoot) + { + if (_isDisposed) throw new ObjectDisposedException("The log file has been disposed."); - var now = Clock.DateTimeNow; - AlignCurrentFileTo(now); + var now = Clock.DateTimeNow; + AlignCurrentFileTo(now); - while (_currentFile?.EmitOrOverflow(logEvent) == false && _rollOnFileSizeLimit) - { - AlignCurrentFileTo(now, nextSequence: true); - } + while (_currentFile?.EmitOrOverflow(logEvent) == false && _rollOnFileSizeLimit) + { + AlignCurrentFileTo(now, nextSequence: true); } } + } - void AlignCurrentFileTo(DateTime now, bool nextSequence = false) + void AlignCurrentFileTo(DateTime now, bool nextSequence = false) + { + if (!_nextCheckpoint.HasValue) + { + OpenFile(now); + } + else if (nextSequence || now >= _nextCheckpoint.Value) { - if (!_nextCheckpoint.HasValue) + int? minSequence = null; + if (nextSequence) { - OpenFile(now); + if (_currentFileSequence == null) + minSequence = 1; + else + minSequence = _currentFileSequence.Value + 1; } - else if (nextSequence || now >= _nextCheckpoint.Value) - { - int? minSequence = null; - if (nextSequence) - { - if (_currentFileSequence == null) - minSequence = 1; - else - minSequence = _currentFileSequence.Value + 1; - } - CloseFile(); - OpenFile(now, minSequence); - } + CloseFile(); + OpenFile(now, minSequence); } + } - void OpenFile(DateTime now, int? minSequence = null) - { - var currentCheckpoint = _roller.GetCurrentCheckpoint(now); + void OpenFile(DateTime now, int? minSequence = null) + { + var currentCheckpoint = _roller.GetCurrentCheckpoint(now); - // We only try periodically because repeated failures - // to open log files REALLY slow an app down. - _nextCheckpoint = _roller.GetNextCheckpoint(now) ?? now.AddMinutes(30); + // We only try periodically because repeated failures + // to open log files REALLY slow an app down. + _nextCheckpoint = _roller.GetNextCheckpoint(now) ?? now.AddMinutes(30); - var existingFiles = Enumerable.Empty(); - try + var existingFiles = Enumerable.Empty(); + try + { + if (Directory.Exists(_roller.LogFileDirectory)) { - if (Directory.Exists(_roller.LogFileDirectory)) - { - existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) - .Select(f => Path.GetFileName(f)); - } + existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) + .Select(f => Path.GetFileName(f)); } - catch (DirectoryNotFoundException) { } + } + catch (DirectoryNotFoundException) { } - var latestForThisCheckpoint = _roller - .SelectMatches(existingFiles) - .Where(m => m.DateTime == currentCheckpoint) - .OrderByDescending(m => m.SequenceNumber) - .FirstOrDefault(); + var latestForThisCheckpoint = _roller + .SelectMatches(existingFiles) + .Where(m => m.DateTime == currentCheckpoint) + .OrderByDescending(m => m.SequenceNumber) + .FirstOrDefault(); - var sequence = latestForThisCheckpoint?.SequenceNumber; - if (minSequence != null) - { - if (sequence == null || sequence.Value < minSequence.Value) - sequence = minSequence; - } + var sequence = latestForThisCheckpoint?.SequenceNumber; + if (minSequence != null) + { + if (sequence == null || sequence.Value < minSequence.Value) + sequence = minSequence; + } - const int maxAttempts = 3; - for (var attempt = 0; attempt < maxAttempts; attempt++) - { - _roller.GetLogFilePath(now, sequence, out var path); + const int maxAttempts = 3; + for (var attempt = 0; attempt < maxAttempts; attempt++) + { + _roller.GetLogFilePath(now, sequence, out var path); - try - { - _currentFile = _shared ? + try + { + _currentFile = _shared ? #pragma warning disable 618 - (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : + (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : #pragma warning restore 618 - new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); + new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); - _currentFileSequence = sequence; - } - catch (IOException ex) + _currentFileSequence = sequence; + } + catch (IOException ex) + { + if (IOErrors.IsLockedFile(ex)) { - if (IOErrors.IsLockedFile(ex)) - { - SelfLog.WriteLine("File target {0} was locked, attempting to open next in sequence (attempt {1})", path, attempt + 1); - sequence = (sequence ?? 0) + 1; - continue; - } - - throw; + SelfLog.WriteLine("File target {0} was locked, attempting to open next in sequence (attempt {1})", path, attempt + 1); + sequence = (sequence ?? 0) + 1; + continue; } - ApplyRetentionPolicy(path, now); - return; + throw; } + + ApplyRetentionPolicy(path, now); + return; } + } - void ApplyRetentionPolicy(string currentFilePath, DateTime now) - { - if (_retainedFileCountLimit == null && _retainedFileTimeLimit == null) return; + void ApplyRetentionPolicy(string currentFilePath, DateTime now) + { + if (_retainedFileCountLimit == null && _retainedFileTimeLimit == null) return; - var currentFileName = Path.GetFileName(currentFilePath); + var currentFileName = Path.GetFileName(currentFilePath); - // We consider the current file to exist, even if nothing's been written yet, - // because files are only opened on response to an event being processed. - var potentialMatches = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) - .Select(f => Path.GetFileName(f)) - .Union(new[] { currentFileName }); + // We consider the current file to exist, even if nothing's been written yet, + // because files are only opened on response to an event being processed. + var potentialMatches = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) + .Select(f => Path.GetFileName(f)) + .Union(new[] { currentFileName }); - var newestFirst = _roller - .SelectMatches(potentialMatches) - .OrderByDescending(m => m.DateTime) - .ThenByDescending(m => m.SequenceNumber); + var newestFirst = _roller + .SelectMatches(potentialMatches) + .OrderByDescending(m => m.DateTime) + .ThenByDescending(m => m.SequenceNumber); - var toRemove = newestFirst - .Where(n => StringComparer.OrdinalIgnoreCase.Compare(currentFileName, n.Filename) != 0) - .SkipWhile((f, i) => ShouldRetainFile(f, i, now)) - .Select(x => x.Filename) - .ToList(); + var toRemove = newestFirst + .Where(n => StringComparer.OrdinalIgnoreCase.Compare(currentFileName, n.Filename) != 0) + .SkipWhile((f, i) => ShouldRetainFile(f, i, now)) + .Select(x => x.Filename) + .ToList(); - foreach (var obsolete in toRemove) + foreach (var obsolete in toRemove) + { + var fullPath = Path.Combine(_roller.LogFileDirectory, obsolete); + try { - var fullPath = Path.Combine(_roller.LogFileDirectory, obsolete); - try - { - _hooks?.OnFileDeleting(fullPath); - System.IO.File.Delete(fullPath); - } - catch (Exception ex) - { - SelfLog.WriteLine("Error {0} while processing obsolete log file {1}", ex, fullPath); - } + _hooks?.OnFileDeleting(fullPath); + System.IO.File.Delete(fullPath); } - } - - bool ShouldRetainFile(RollingLogFile file, int index, DateTime now) - { - if (_retainedFileCountLimit.HasValue && index >= _retainedFileCountLimit.Value - 1) - return false; - - if (_retainedFileTimeLimit.HasValue && file.DateTime.HasValue && - file.DateTime.Value < now.Subtract(_retainedFileTimeLimit.Value)) + catch (Exception ex) { - return false; + SelfLog.WriteLine("Error {0} while processing obsolete log file {1}", ex, fullPath); } - - return true; } + } - public void Dispose() + bool ShouldRetainFile(RollingLogFile file, int index, DateTime now) + { + if (_retainedFileCountLimit.HasValue && index >= _retainedFileCountLimit.Value - 1) + return false; + + if (_retainedFileTimeLimit.HasValue && file.DateTime.HasValue && + file.DateTime.Value < now.Subtract(_retainedFileTimeLimit.Value)) { - lock (_syncRoot) - { - if (_currentFile == null) return; - CloseFile(); - _isDisposed = true; - } + return false; } - void CloseFile() + return true; + } + + public void Dispose() + { + lock (_syncRoot) { - if (_currentFile != null) - { - (_currentFile as IDisposable)?.Dispose(); - _currentFile = null; - } + if (_currentFile == null) return; + CloseFile(); + _isDisposed = true; + } + } - _nextCheckpoint = null; + void CloseFile() + { + if (_currentFile != null) + { + (_currentFile as IDisposable)?.Dispose(); + _currentFile = null; } - public void FlushToDisk() + _nextCheckpoint = null; + } + + public void FlushToDisk() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _currentFile?.FlushToDisk(); - } + _currentFile?.FlushToDisk(); } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs index 2c9e2fd..98ffb42 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs @@ -12,75 +12,72 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; +namespace Serilog.Sinks.File; -namespace Serilog.Sinks.File +static class RollingIntervalExtensions { - static class RollingIntervalExtensions + public static string GetFormat(this RollingInterval interval) { - public static string GetFormat(this RollingInterval interval) + switch (interval) { - switch (interval) - { - case RollingInterval.Infinite: - return ""; - case RollingInterval.Year: - return "yyyy"; - case RollingInterval.Month: - return "yyyyMM"; - case RollingInterval.Day: - return "yyyyMMdd"; - case RollingInterval.Hour: - return "yyyyMMddHH"; - case RollingInterval.Minute: - return "yyyyMMddHHmm"; - default: - throw new ArgumentException("Invalid rolling interval"); - } + case RollingInterval.Infinite: + return ""; + case RollingInterval.Year: + return "yyyy"; + case RollingInterval.Month: + return "yyyyMM"; + case RollingInterval.Day: + return "yyyyMMdd"; + case RollingInterval.Hour: + return "yyyyMMddHH"; + case RollingInterval.Minute: + return "yyyyMMddHHmm"; + default: + throw new ArgumentException("Invalid rolling interval"); } + } - public static DateTime? GetCurrentCheckpoint(this RollingInterval interval, DateTime instant) + public static DateTime? GetCurrentCheckpoint(this RollingInterval interval, DateTime instant) + { + switch (interval) { - switch (interval) - { - case RollingInterval.Infinite: - return null; - case RollingInterval.Year: - return new DateTime(instant.Year, 1, 1, 0, 0, 0, instant.Kind); - case RollingInterval.Month: - return new DateTime(instant.Year, instant.Month, 1, 0, 0, 0, instant.Kind); - case RollingInterval.Day: - return new DateTime(instant.Year, instant.Month, instant.Day, 0, 0, 0, instant.Kind); - case RollingInterval.Hour: - return new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, 0, 0, instant.Kind); - case RollingInterval.Minute: - return new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, instant.Minute, 0, instant.Kind); - default: - throw new ArgumentException("Invalid rolling interval"); - } + case RollingInterval.Infinite: + return null; + case RollingInterval.Year: + return new DateTime(instant.Year, 1, 1, 0, 0, 0, instant.Kind); + case RollingInterval.Month: + return new DateTime(instant.Year, instant.Month, 1, 0, 0, 0, instant.Kind); + case RollingInterval.Day: + return new DateTime(instant.Year, instant.Month, instant.Day, 0, 0, 0, instant.Kind); + case RollingInterval.Hour: + return new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, 0, 0, instant.Kind); + case RollingInterval.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 RollingInterval interval, DateTime instant) - { - var current = GetCurrentCheckpoint(interval, instant); - if (current == null) - return null; + public static DateTime? GetNextCheckpoint(this RollingInterval interval, DateTime instant) + { + var current = GetCurrentCheckpoint(interval, instant); + if (current == null) + return null; - switch (interval) - { - case RollingInterval.Year: - return current.Value.AddYears(1); - case RollingInterval.Month: - return current.Value.AddMonths(1); - case RollingInterval.Day: - return current.Value.AddDays(1); - case RollingInterval.Hour: - return current.Value.AddHours(1); - case RollingInterval.Minute: - return current.Value.AddMinutes(1); - default: - throw new ArgumentException("Invalid rolling interval"); - } + switch (interval) + { + case RollingInterval.Year: + return current.Value.AddYears(1); + case RollingInterval.Month: + return current.Value.AddMonths(1); + case RollingInterval.Day: + return current.Value.AddDays(1); + case RollingInterval.Hour: + return current.Value.AddHours(1); + case RollingInterval.Minute: + return current.Value.AddMinutes(1); + default: + throw new ArgumentException("Invalid rolling interval"); } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs b/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs index be64c4e..6441627 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs @@ -11,24 +11,23 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -using System; -namespace Serilog.Sinks.File + +namespace Serilog.Sinks.File; + +class RollingLogFile { - class RollingLogFile + public RollingLogFile(string filename, DateTime? dateTime, int? sequenceNumber) { - public RollingLogFile(string filename, DateTime? dateTime, int? sequenceNumber) - { - Filename = filename; - DateTime = dateTime; - SequenceNumber = sequenceNumber; - } + Filename = filename; + DateTime = dateTime; + SequenceNumber = sequenceNumber; + } - public string Filename { get; } + public string Filename { get; } - public DateTime? DateTime { get; } + public DateTime? DateTime { get; } + + public int? SequenceNumber { get; } +} - public int? SequenceNumber { get; } - } -} diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs index b8a07db..0038c6b 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2019 Serilog Contributors +// Copyright 2013-2019 Serilog Contributors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,152 +22,151 @@ using System.Threading; using Serilog.Debugging; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Write log events to a disk file. +/// +[Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(shared: true)` instead.")] +public sealed class SharedFileSink : IFileSink, IDisposable { - /// - /// Write log events to a disk file. - /// - [Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(shared: true)` instead.")] - public sealed class SharedFileSink : IFileSink, IDisposable + readonly TextWriter _output; + readonly FileStream _underlyingStream; + readonly ITextFormatter _textFormatter; + readonly long? _fileSizeLimitBytes; + readonly object _syncRoot = new(); + + const string MutexNameSuffix = ".serilog"; + const int MutexWaitTimeout = 10000; + readonly Mutex _mutex; + + /// Construct a . + /// Path to the file. + /// Formatter used to convert log events to text. + /// 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. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Configuration object allowing method chaining. + /// The file will be written using the UTF-8 character set. + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null) { - readonly TextWriter _output; - readonly FileStream _underlyingStream; - readonly ITextFormatter _textFormatter; - readonly long? _fileSizeLimitBytes; - readonly object _syncRoot = new object(); - - const string MutexNameSuffix = ".serilog"; - const int MutexWaitTimeout = 10000; - readonly Mutex _mutex; - - /// Construct a . - /// Path to the file. - /// Formatter used to convert log events to text. - /// 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. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null) + if (path == null) throw new ArgumentNullException(nameof(path)); + if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) + throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); + _fileSizeLimitBytes = fileSizeLimitBytes; + + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) - throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); - _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); - _fileSizeLimitBytes = fileSizeLimitBytes; - - var directory = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - var mutexName = Path.GetFullPath(path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix; - _mutex = new Mutex(false, mutexName); - _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); - _output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + Directory.CreateDirectory(directory); } - bool IFileSink.EmitOrOverflow(LogEvent logEvent) + var mutexName = Path.GetFullPath(path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix; + _mutex = new Mutex(false, mutexName); + _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + _output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + bool IFileSink.EmitOrOverflow(LogEvent logEvent) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + + lock (_syncRoot) { - if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + if (!TryAcquireMutex()) + return true; // We didn't overflow, but, roll-on-size should not be attempted - lock (_syncRoot) + try { - if (!TryAcquireMutex()) - return true; // We didn't overflow, but, roll-on-size should not be attempted - - try + _underlyingStream.Seek(0, SeekOrigin.End); + if (_fileSizeLimitBytes != null) { - _underlyingStream.Seek(0, SeekOrigin.End); - if (_fileSizeLimitBytes != null) - { - if (_underlyingStream.Length >= _fileSizeLimitBytes.Value) - return false; - } - - _textFormatter.Format(logEvent, _output); - _output.Flush(); - _underlyingStream.Flush(); - return true; - } - finally - { - ReleaseMutex(); + if (_underlyingStream.Length >= _fileSizeLimitBytes.Value) + return false; } + + _textFormatter.Format(logEvent, _output); + _output.Flush(); + _underlyingStream.Flush(); + return true; + } + finally + { + ReleaseMutex(); } } + } + + /// + /// Emit the provided log event to the sink. + /// + /// The log event to write. + /// When is null + public void Emit(LogEvent logEvent) + { + ((IFileSink)this).EmitOrOverflow(logEvent); + } - /// - /// Emit the provided log event to the sink. - /// - /// The log event to write. - /// When is null - public void Emit(LogEvent logEvent) + /// + public void Dispose() + { + lock (_syncRoot) { - ((IFileSink)this).EmitOrOverflow(logEvent); + _output.Dispose(); + _mutex.Dispose(); } + } - /// - public void Dispose() + /// + public void FlushToDisk() + { + lock (_syncRoot) { - lock (_syncRoot) + if (!TryAcquireMutex()) + return; + + try { - _output.Dispose(); - _mutex.Dispose(); + _underlyingStream.Flush(true); } - } - - /// - public void FlushToDisk() - { - lock (_syncRoot) + finally { - if (!TryAcquireMutex()) - return; - - try - { - _underlyingStream.Flush(true); - } - finally - { - ReleaseMutex(); - } + ReleaseMutex(); } } + } - bool TryAcquireMutex() + bool TryAcquireMutex() + { + try { - try - { - if (!_mutex.WaitOne(MutexWaitTimeout)) - { - SelfLog.WriteLine("Shared file mutex could not be acquired within {0} ms", MutexWaitTimeout); - return false; - } - } - catch (AbandonedMutexException) + if (!_mutex.WaitOne(MutexWaitTimeout)) { - SelfLog.WriteLine("Inherited shared file mutex after abandonment by another process"); + SelfLog.WriteLine("Shared file mutex could not be acquired within {0} ms", MutexWaitTimeout); + return false; } - - return true; } - - void ReleaseMutex() + catch (AbandonedMutexException) { - _mutex.ReleaseMutex(); + SelfLog.WriteLine("Inherited shared file mutex after abandonment by another process"); } + + return true; + } + + void ReleaseMutex() + { + _mutex.ReleaseMutex(); } } diff --git a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs index e247144..e95d80f 100644 --- a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs +++ b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs @@ -12,69 +12,65 @@ // See the License for the specific language governing permissions and // limitations under the License. -using System; -using System.IO; +namespace Serilog.Sinks.File; -namespace Serilog.Sinks.File +sealed class WriteCountingStream : Stream { - sealed class WriteCountingStream : Stream + readonly Stream _stream; + + public WriteCountingStream(Stream stream) { - readonly Stream _stream; + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + CountedLength = stream.Length; + } - public WriteCountingStream(Stream stream) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - CountedLength = stream.Length; - } + public long CountedLength { get; private set; } - public long CountedLength { get; private set; } + protected override void Dispose(bool disposing) + { + if (disposing) + _stream.Dispose(); - protected override void Dispose(bool disposing) - { - if (disposing) - _stream.Dispose(); + base.Dispose(disposing); + } - base.Dispose(disposing); - } + public override void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + CountedLength += count; + } - public override void Write(byte[] buffer, int offset, int count) - { - _stream.Write(buffer, offset, count); - CountedLength += count; - } + public override void Flush() => _stream.Flush(); + public override bool CanRead => false; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => true; + public override long Length => _stream.Length; - public override void Flush() => _stream.Flush(); - public override bool CanRead => false; - public override bool CanSeek => _stream.CanSeek; - public override bool CanWrite => true; - public override long Length => _stream.Length; + public override long Position + { + get => _stream.Position; + set => throw new NotSupportedException(); + } - public override long Position - { - get => _stream.Position; - set => throw new NotSupportedException(); - } + public override long Seek(long offset, SeekOrigin origin) + { + throw new InvalidOperationException($"Seek operations are not available through `{nameof(WriteCountingStream)}`."); + } - public override long Seek(long offset, SeekOrigin origin) - { - throw new InvalidOperationException($"Seek operations are not available through `{nameof(WriteCountingStream)}`."); - } + public override void SetLength(long value) + { + _stream.SetLength(value); - public override void SetLength(long value) + if (value < CountedLength) { - _stream.SetLength(value); - - if (value < CountedLength) - { - // File is now shorter and our position has changed to _stream.Length - CountedLength = _stream.Length; - } + // File is now shorter and our position has changed to _stream.Length + CountedLength = _stream.Length; } + } - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); } } diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index 3dde37a..9c32200 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -1,120 +1,116 @@ -using System; -using System.Threading; using Serilog.Sinks.File.Tests.Support; using Serilog.Tests.Support; using Xunit; -using System.IO; using System.Text; -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class FileLoggerConfigurationExtensionsTests { - public class FileLoggerConfigurationExtensionsTests + static readonly string InvalidPath = new(Path.GetInvalidPathChars()); + + [Fact] + public void WhenWritingCreationExceptionsAreSuppressed() { - static readonly string InvalidPath = new string(Path.GetInvalidPathChars()); + new LoggerConfiguration() + .WriteTo.File(InvalidPath) + .CreateLogger(); + } - [Fact] - public void WhenWritingCreationExceptionsAreSuppressed() - { + [Fact] + public void WhenAuditingCreationExceptionsPropagate() + { + Assert.Throws(() => new LoggerConfiguration() - .WriteTo.File(InvalidPath) - .CreateLogger(); - } + .AuditTo.File(InvalidPath) + .CreateLogger()); + } - [Fact] - public void WhenAuditingCreationExceptionsPropagate() + [Fact] + public void WhenWritingLoggingExceptionsAreSuppressed() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) + .CreateLogger()) { - Assert.Throws(() => - new LoggerConfiguration() - .AuditTo.File(InvalidPath) - .CreateLogger()); + log.Information("Hello"); } + } - [Fact] - public void WhenWritingLoggingExceptionsAreSuppressed() + [Fact] + public void WhenAuditingLoggingExceptionsPropagate() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .AuditTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) + .CreateLogger()) { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() - .WriteTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) - .CreateLogger()) - { - log.Information("Hello"); - } + var ex = Assert.Throws(() => log.Information("Hello")); + Assert.IsType(ex.GetBaseException()); } + } - [Fact] - public void WhenAuditingLoggingExceptionsPropagate() + [Fact] + public void WhenFlushingToDiskReportedFileSinkCanBeCreatedAndDisposed() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(tmp.AllocateFilename(), flushToDiskInterval: TimeSpan.FromMilliseconds(500)) + .CreateLogger()) { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() - .AuditTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) - .CreateLogger()) - { - var ex = Assert.Throws(() => log.Information("Hello")); - Assert.IsType(ex.GetBaseException()); - } + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); } + } - [Fact] - public void WhenFlushingToDiskReportedFileSinkCanBeCreatedAndDisposed() + [Fact] + public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() + { + using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(tmp.AllocateFilename(), shared: true, flushToDiskInterval: TimeSpan.FromMilliseconds(500)) + .CreateLogger()) { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() - .WriteTo.File(tmp.AllocateFilename(), flushToDiskInterval: TimeSpan.FromMilliseconds(500)) - .CreateLogger()) - { - log.Information("Hello"); - Thread.Sleep(TimeSpan.FromSeconds(1)); - } + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); } + } - [Fact] - public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() - { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() - .WriteTo.File(tmp.AllocateFilename(), shared: true, flushToDiskInterval: TimeSpan.FromMilliseconds(500)) - .CreateLogger()) - { - log.Information("Hello"); - Thread.Sleep(TimeSpan.FromSeconds(1)); - } - } + [Fact] + public void BufferingIsNotAvailableWhenSharingEnabled() + { + Assert.Throws(() => + new LoggerConfiguration() + .WriteTo.File("logs", buffered: true, shared: true)); + } - [Fact] - public void BufferingIsNotAvailableWhenSharingEnabled() - { - Assert.Throws(() => - new LoggerConfiguration() - .WriteTo.File("logs", buffered: true, shared: true)); - } + [Fact] + public void HooksAreNotAvailableWhenSharingEnabled() + { + Assert.Throws(() => + new LoggerConfiguration() + .WriteTo.File("logs", shared: true, hooks: new GZipHooks())); + } - [Fact] - public void HooksAreNotAvailableWhenSharingEnabled() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpecifiedEncodingIsPropagated(bool shared) + { + using (var tmp = TempFolder.ForCaller()) { - Assert.Throws(() => - new LoggerConfiguration() - .WriteTo.File("logs", shared: true, hooks: new GZipHooks())); - } + var filename = tmp.AllocateFilename("txt"); - [Theory] - [InlineData(false)] - [InlineData(true)] - public void SpecifiedEncodingIsPropagated(bool shared) - { - using (var tmp = TempFolder.ForCaller()) + using (var log = new LoggerConfiguration() + .WriteTo.File(filename, outputTemplate: "{Message}", encoding: Encoding.Unicode, shared: shared) + .CreateLogger()) { - var filename = tmp.AllocateFilename("txt"); - - using (var log = new LoggerConfiguration() - .WriteTo.File(filename, outputTemplate: "{Message}", encoding: Encoding.Unicode, shared: shared) - .CreateLogger()) - { - log.Information("ten chars."); - } - - // Don't forget the two-byte BOM :-) - Assert.Equal(22, System.IO.File.ReadAllBytes(filename).Length); + log.Information("ten chars."); } + + // Don't forget the two-byte BOM :-) + Assert.Equal(22, System.IO.File.ReadAllBytes(filename).Length); } } } diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index a33261f..af5c8f5 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; +using System.IO.Compression; using System.Text; using Xunit; using Serilog.Formatting.Json; @@ -9,272 +7,271 @@ #pragma warning disable 618 -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class FileSinkTests { - public class FileSinkTests + [Fact] + public void FileIsWrittenIfNonexistent() { - [Fact] - public void FileIsWrittenIfNonexistent() + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var nonexistent = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(nonexistent, new JsonFormatter(), null)) - { - sink.Emit(evt); - } - - var lines = System.IO.File.ReadAllLines(nonexistent); - Assert.Contains("Hello, world!", lines[0]); + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); } + } - [Fact] - public void FileIsAppendedToWhenAlreadyCreated() + [Fact] + public void FileIsAppendedToWhenAlreadyCreated() + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); - - using (var sink = new FileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + using (var sink = new FileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); + } - var lines = System.IO.File.ReadAllLines(path); - Assert.Contains("Hello, world!", lines[0]); - Assert.Contains("Hello, world!", lines[1]); + using (var sink = new FileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(path); + Assert.Contains("Hello, world!", lines[0]); + Assert.Contains("Hello, world!", lines[1]); } + } + + [Fact] + public void WhenLimitIsSpecifiedFileSizeIsRestricted() + { + const int maxBytes = 5000; + const int eventsToLimit = 10; - [Fact] - public void WhenLimitIsSpecifiedFileSizeIsRestricted() + using (var tmp = TempFolder.ForCaller()) { - const int maxBytes = 5000; - const int eventsToLimit = 10; + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var tmp = TempFolder.ForCaller()) + using (var sink = new FileSink(path, new JsonFormatter(), maxBytes)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - - using (var sink = new FileSink(path, new JsonFormatter(), maxBytes)) + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes); - Assert.True(size < maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes); + Assert.True(size < maxBytes * 2); } + } + + [Fact] + public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() + { + const int maxBytes = 5000; + const int eventsToLimit = 10; - [Fact] - public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() + using (var tmp = TempFolder.ForCaller()) { - const int maxBytes = 5000; - const int eventsToLimit = 10; + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var tmp = TempFolder.ForCaller()) + using (var sink = new FileSink(path, new JsonFormatter(), null)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - - using (var sink = new FileSink(path, new JsonFormatter(), null)) + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes * 2); } + } - [Fact] - public void WhenLimitIsSpecifiedAndEncodingHasPreambleDataIsCorrectlyAppendedToFileSink() - { - long? maxBytes = 5000; - var encoding = Encoding.UTF8; + [Fact] + public void WhenLimitIsSpecifiedAndEncodingHasPreambleDataIsCorrectlyAppendedToFileSink() + { + long? maxBytes = 5000; + var encoding = Encoding.UTF8; - Assert.True(encoding.GetPreamble().Length > 0); - WriteTwoEventsAndCheckOutputFileLength(maxBytes, encoding); - } + Assert.True(encoding.GetPreamble().Length > 0); + WriteTwoEventsAndCheckOutputFileLength(maxBytes, encoding); + } - [Fact] - public void WhenLimitIsNotSpecifiedAndEncodingHasPreambleDataIsCorrectlyAppendedToFileSink() - { - var encoding = Encoding.UTF8; + [Fact] + public void WhenLimitIsNotSpecifiedAndEncodingHasPreambleDataIsCorrectlyAppendedToFileSink() + { + var encoding = Encoding.UTF8; - Assert.True(encoding.GetPreamble().Length > 0); - WriteTwoEventsAndCheckOutputFileLength(null, encoding); - } + Assert.True(encoding.GetPreamble().Length > 0); + WriteTwoEventsAndCheckOutputFileLength(null, encoding); + } - [Fact] - public void WhenLimitIsSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppendedToFileSink() - { - long? maxBytes = 5000; - var encoding = new UTF8Encoding(false); + [Fact] + public void WhenLimitIsSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppendedToFileSink() + { + long? maxBytes = 5000; + var encoding = new UTF8Encoding(false); - Assert.Empty(encoding.GetPreamble()); - WriteTwoEventsAndCheckOutputFileLength(maxBytes, encoding); - } + Assert.Empty(encoding.GetPreamble()); + WriteTwoEventsAndCheckOutputFileLength(maxBytes, encoding); + } - [Fact] - public void WhenLimitIsNotSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppendedToFileSink() - { - var encoding = new UTF8Encoding(false); + [Fact] + public void WhenLimitIsNotSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppendedToFileSink() + { + var encoding = new UTF8Encoding(false); - Assert.Empty(encoding.GetPreamble()); - WriteTwoEventsAndCheckOutputFileLength(null, encoding); - } + Assert.Empty(encoding.GetPreamble()); + WriteTwoEventsAndCheckOutputFileLength(null, encoding); + } + + [Fact] + public void OnOpenedLifecycleHookCanWrapUnderlyingStream() + { + var gzipWrapper = new GZipHooks(); - [Fact] - public void OnOpenedLifecycleHookCanWrapUnderlyingStream() + using (var tmp = TempFolder.ForCaller()) { - var gzipWrapper = new GZipHooks(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var tmp = TempFolder.ForCaller()) + using (var sink = new FileSink(path, new JsonFormatter(), null, null, false, gzipWrapper)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); - - using (var sink = new FileSink(path, new JsonFormatter(), null, null, false, gzipWrapper)) - { - sink.Emit(evt); - sink.Emit(evt); - } + sink.Emit(evt); + sink.Emit(evt); + } - // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against - // what we wrote - List lines; - using (var textStream = new MemoryStream()) + // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against + // what we wrote + List lines; + using (var textStream = new MemoryStream()) + { + using (var fs = System.IO.File.OpenRead(path)) + using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) { - using (var fs = System.IO.File.OpenRead(path)) - using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) - { - decompressStream.CopyTo(textStream); - } - - textStream.Position = 0; - lines = textStream.ReadAllLines(); + decompressStream.CopyTo(textStream); } - Assert.Equal(2, lines.Count); - Assert.Contains("Hello, world!", lines[0]); + textStream.Position = 0; + lines = textStream.ReadAllLines(); } + + Assert.Equal(2, lines.Count); + Assert.Contains("Hello, world!", lines[0]); } + } - [Fact] - public static void OnOpenedLifecycleHookCanWriteFileHeader() + [Fact] + public static void OnOpenedLifecycleHookCanWriteFileHeader() + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var headerWriter = new FileHeaderWriter("This is the file header"); + var headerWriter = new FileHeaderWriter("This is the file header"); - var path = tmp.AllocateFilename("txt"); - using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) - { - // Open and write header - } + var path = tmp.AllocateFilename("txt"); + using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) + { + // Open and write header + } - using (var sink = new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) - { - // Length check should prevent duplicate header here - sink.Emit(Some.LogEvent()); - } + using (var sink = new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) + { + // Length check should prevent duplicate header here + sink.Emit(Some.LogEvent()); + } - var lines = System.IO.File.ReadAllLines(path); + var lines = System.IO.File.ReadAllLines(path); - Assert.Equal(2, lines.Length); - Assert.Equal(headerWriter.Header, lines[0]); - Assert.Equal('{', lines[1][0]); - } + Assert.Equal(2, lines.Length); + Assert.Equal(headerWriter.Header, lines[0]); + Assert.Equal('{', lines[1][0]); } + } - [Fact] - public static void OnOpenedLifecycleHookCanCaptureFilePath() + [Fact] + public static void OnOpenedLifecycleHookCanCaptureFilePath() + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var capturePath = new CaptureFilePathHook(); - - var path = tmp.AllocateFilename("txt"); - using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, capturePath)) - { - // Open and capture the log file path - } + var capturePath = new CaptureFilePathHook(); - Assert.Equal(path, capturePath.Path); + var path = tmp.AllocateFilename("txt"); + using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, capturePath)) + { + // Open and capture the log file path } + + Assert.Equal(path, capturePath.Path); } + } - [Fact] - public static void OnOpenedLifecycleHookCanEmptyTheFileContents() + [Fact] + public static void OnOpenedLifecycleHookCanEmptyTheFileContents() + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var emptyFileHook = new TruncateFileHook(); + var emptyFileHook = new TruncateFileHook(); - var path = tmp.AllocateFilename("txt"); - using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false)) - { - sink.Emit(Some.LogEvent()); - } + var path = tmp.AllocateFilename("txt"); + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false)) + { + sink.Emit(Some.LogEvent()); + } - using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook)) - { - // Hook will clear the contents of the file before emitting the log events - sink.Emit(Some.LogEvent()); - } + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook)) + { + // Hook will clear the contents of the file before emitting the log events + sink.Emit(Some.LogEvent()); + } - var lines = System.IO.File.ReadAllLines(path); + var lines = System.IO.File.ReadAllLines(path); - Assert.Single(lines); - Assert.Equal('{', lines[0][0]); - } + Assert.Single(lines); + Assert.Equal('{', lines[0][0]); } + } - static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) + static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Irrelevant as it will be replaced by the formatter"); - const string actualEventOutput = "x"; - var formatter = new FixedOutputFormatter(actualEventOutput); - var eventOuputLength = encoding.GetByteCount(actualEventOutput); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Irrelevant as it will be replaced by the formatter"); + const string actualEventOutput = "x"; + var formatter = new FixedOutputFormatter(actualEventOutput); + var eventOuputLength = encoding.GetByteCount(actualEventOutput); - using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) - { - sink.Emit(evt); - } - var size = new FileInfo(path).Length; - Assert.Equal(encoding.GetPreamble().Length + eventOuputLength, size); - - //write a second event to the same file - using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) - { - sink.Emit(evt); - } + using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) + { + sink.Emit(evt); + } + var size = new FileInfo(path).Length; + Assert.Equal(encoding.GetPreamble().Length + eventOuputLength, size); - size = new FileInfo(path).Length; - Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size); + //write a second event to the same file + using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) + { + sink.Emit(evt); } + + size = new FileInfo(path).Length; + Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size); } } } diff --git a/test/Serilog.Sinks.File.Tests/Properties/launchSettings.json b/test/Serilog.Sinks.File.Tests/Properties/launchSettings.json deleted file mode 100644 index 3ab0635..0000000 --- a/test/Serilog.Sinks.File.Tests/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "test": { - "commandName": "test" - }, - "test-dnxcore50": { - "commandName": "test", - "sdkVersion": "dnx-coreclr-win-x86.1.0.0-rc1-final" - } - } -} \ No newline at end of file diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index 9232bde..5739983 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -1,306 +1,295 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.IO.Compression; -using System.Linq; using Xunit; using Serilog.Events; using Serilog.Sinks.File.Tests.Support; using Serilog.Configuration; using Serilog.Core; -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class RollingFileSinkTests { - public class RollingFileSinkTests + [Fact] + public void LogEventsAreEmittedToTheFileNamedAccordingToTheEventTimestamp() { - [Fact] - public void LogEventsAreEmittedToTheFileNamedAccordingToTheEventTimestamp() - { - TestRollingEventSequence(Some.InformationEvent()); - } + TestRollingEventSequence(Some.InformationEvent()); + } - [Fact] - public void EventsAreWrittenWhenSharingIsEnabled() - { - TestRollingEventSequence( - (pf, wt) => wt.File(pf, shared: true, rollingInterval: RollingInterval.Day), - new[] { Some.InformationEvent() }); - } + [Fact] + public void EventsAreWrittenWhenSharingIsEnabled() + { + TestRollingEventSequence( + (pf, wt) => wt.File(pf, shared: true, rollingInterval: RollingInterval.Day), + new[] { Some.InformationEvent() }); + } - [Fact] - public void EventsAreWrittenWhenBufferingIsEnabled() - { - TestRollingEventSequence( - (pf, wt) => wt.File(pf, buffered: true, rollingInterval: RollingInterval.Day), - new[] { Some.InformationEvent() }); - } + [Fact] + public void EventsAreWrittenWhenBufferingIsEnabled() + { + TestRollingEventSequence( + (pf, wt) => wt.File(pf, buffered: true, rollingInterval: RollingInterval.Day), + new[] { Some.InformationEvent() }); + } - [Fact] - public void EventsAreWrittenWhenDiskFlushingIsEnabled() - { - // Doesn't test flushing, but ensures we haven't broken basic logging - TestRollingEventSequence( - (pf, wt) => wt.File(pf, flushToDiskInterval: TimeSpan.FromMilliseconds(50), rollingInterval: RollingInterval.Day), - new[] { Some.InformationEvent() }); - } + [Fact] + public void EventsAreWrittenWhenDiskFlushingIsEnabled() + { + // Doesn't test flushing, but ensures we haven't broken basic logging + TestRollingEventSequence( + (pf, wt) => wt.File(pf, flushToDiskInterval: TimeSpan.FromMilliseconds(50), rollingInterval: RollingInterval.Day), + new[] { Some.InformationEvent() }); + } - [Fact] - public void WhenTheDateChangesTheCorrectFileIsWritten() - { - var e1 = Some.InformationEvent(); - var e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)); - TestRollingEventSequence(e1, e2); - } + [Fact] + public void WhenTheDateChangesTheCorrectFileIsWritten() + { + var e1 = Some.InformationEvent(); + var e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)); + TestRollingEventSequence(e1, e2); + } - [Fact] - public void WhenRetentionCountIsSetOldFilesAreDeleted() - { - LogEvent e1 = Some.InformationEvent(), - e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)), - e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + [Fact] + public void WhenRetentionCountIsSetOldFilesAreDeleted() + { + LogEvent e1 = Some.InformationEvent(), + e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)), + e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, rollingInterval: RollingInterval.Day), + new[] {e1, e2, e3}, + files => + { + Assert.Equal(3, files.Count); + Assert.True(!System.IO.File.Exists(files[0])); + Assert.True(System.IO.File.Exists(files[1])); + Assert.True(System.IO.File.Exists(files[2])); + }); + } - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, rollingInterval: RollingInterval.Day), - new[] {e1, e2, e3}, - files => - { - Assert.Equal(3, files.Count); - Assert.True(!System.IO.File.Exists(files[0])); - Assert.True(System.IO.File.Exists(files[1])); - Assert.True(System.IO.File.Exists(files[2])); - }); - } + [Fact] + public void WhenRetentionTimeIsSetOldFilesAreDeleted() + { + LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), + e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), + e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileTimeLimit: TimeSpan.FromDays(1), rollingInterval: RollingInterval.Day), + new[] {e1, e2, e3}, + files => + { + Assert.Equal(3, files.Count); + Assert.True(!System.IO.File.Exists(files[0])); + Assert.True(!System.IO.File.Exists(files[1])); + Assert.True(System.IO.File.Exists(files[2])); + }); + } - [Fact] - public void WhenRetentionTimeIsSetOldFilesAreDeleted() - { - LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), - e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), - e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + [Fact] + public void WhenRetentionCountAndTimeIsSetOldFilesAreDeletedByTime() + { + LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), + e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), + e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, retainedFileTimeLimit: TimeSpan.FromDays(1), rollingInterval: RollingInterval.Day), + new[] {e1, e2, e3}, + files => + { + Assert.Equal(3, files.Count); + Assert.True(!System.IO.File.Exists(files[0])); + Assert.True(!System.IO.File.Exists(files[1])); + Assert.True(System.IO.File.Exists(files[2])); + }); + } - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileTimeLimit: TimeSpan.FromDays(1), rollingInterval: RollingInterval.Day), - new[] {e1, e2, e3}, - files => - { - Assert.Equal(3, files.Count); - Assert.True(!System.IO.File.Exists(files[0])); - Assert.True(!System.IO.File.Exists(files[1])); - Assert.True(System.IO.File.Exists(files[2])); - }); - } + [Fact] + public void WhenRetentionCountAndTimeIsSetOldFilesAreDeletedByCount() + { + LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), + e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), + e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, retainedFileTimeLimit: TimeSpan.FromDays(10), rollingInterval: RollingInterval.Day), + new[] {e1, e2, e3}, + files => + { + Assert.Equal(3, files.Count); + Assert.True(!System.IO.File.Exists(files[0])); + Assert.True(System.IO.File.Exists(files[1])); + Assert.True(System.IO.File.Exists(files[2])); + }); + } - [Fact] - public void WhenRetentionCountAndTimeIsSetOldFilesAreDeletedByTime() - { - LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), - e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), + [Fact] + public void WhenRetentionCountAndArchivingHookIsSetOldFilesAreCopiedAndOriginalDeleted() + { + const string archiveDirectory = "OldLogs"; + LogEvent e1 = Some.InformationEvent(), + e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)), e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, retainedFileTimeLimit: TimeSpan.FromDays(1), rollingInterval: RollingInterval.Day), - new[] {e1, e2, e3}, - files => - { - Assert.Equal(3, files.Count); - Assert.True(!System.IO.File.Exists(files[0])); - Assert.True(!System.IO.File.Exists(files[1])); - Assert.True(System.IO.File.Exists(files[2])); - }); - } + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, rollingInterval: RollingInterval.Day, hooks: new ArchiveOldLogsHook(archiveDirectory)), + new[] {e1, e2, e3}, + files => + { + Assert.Equal(3, files.Count); + Assert.False(System.IO.File.Exists(files[0])); + Assert.True(System.IO.File.Exists(files[1])); + Assert.True(System.IO.File.Exists(files[2])); + Assert.True(System.IO.File.Exists(ArchiveOldLogsHook.AddTopDirectory(files[0], archiveDirectory))); + }); + } - [Fact] - public void WhenRetentionCountAndTimeIsSetOldFilesAreDeletedByCount() - { - LogEvent e1 = Some.InformationEvent(DateTime.Today.AddDays(-5)), - e2 = Some.InformationEvent(e1.Timestamp.AddDays(2)), - e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); + [Fact] + public void WhenSizeLimitIsBreachedNewFilesCreated() + { + var fileName = Some.String() + ".txt"; + using var temp = new TempFolder(); + using var log = new LoggerConfiguration() + .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1) + .CreateLogger(); + LogEvent e1 = Some.InformationEvent(), + e2 = Some.InformationEvent(e1.Timestamp), + e3 = Some.InformationEvent(e1.Timestamp); + + log.Write(e1); log.Write(e2); log.Write(e3); + + var files = Directory.GetFiles(temp.Path) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + Assert.Equal(3, files.Length); + Assert.True(files[0].EndsWith(fileName), files[0]); + Assert.True(files[1].EndsWith("_001.txt"), files[1]); + Assert.True(files[2].EndsWith("_002.txt"), files[2]); + } - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, retainedFileTimeLimit: TimeSpan.FromDays(10), rollingInterval: RollingInterval.Day), - new[] {e1, e2, e3}, - files => - { - Assert.Equal(3, files.Count); - Assert.True(!System.IO.File.Exists(files[0])); - Assert.True(System.IO.File.Exists(files[1])); - Assert.True(System.IO.File.Exists(files[2])); - }); - } + [Fact] + public void WhenStreamWrapperSpecifiedIsUsedForRolledFiles() + { + var gzipWrapper = new GZipHooks(); + var fileName = Some.String() + ".txt"; - [Fact] - public void WhenRetentionCountAndArchivingHookIsSetOldFilesAreCopiedAndOriginalDeleted() + using var temp = new TempFolder(); + string[] files; + var logEvents = new[] { - const string archiveDirectory = "OldLogs"; - LogEvent e1 = Some.InformationEvent(), - e2 = Some.InformationEvent(e1.Timestamp.AddDays(1)), - e3 = Some.InformationEvent(e2.Timestamp.AddDays(5)); - - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileCountLimit: 2, rollingInterval: RollingInterval.Day, hooks: new ArchiveOldLogsHook(archiveDirectory)), - new[] {e1, e2, e3}, - files => - { - Assert.Equal(3, files.Count); - Assert.False(System.IO.File.Exists(files[0])); - Assert.True(System.IO.File.Exists(files[1])); - Assert.True(System.IO.File.Exists(files[2])); - Assert.True(System.IO.File.Exists(ArchiveOldLogsHook.AddTopDirectory(files[0], archiveDirectory))); - }); - } - - [Fact] - public void WhenSizeLimitIsBreachedNewFilesCreated() + Some.InformationEvent(), + Some.InformationEvent(), + Some.InformationEvent() + }; + + using (var log = new LoggerConfiguration() + .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1, hooks: gzipWrapper) + .CreateLogger()) { - var fileName = Some.String() + ".txt"; - using (var temp = new TempFolder()) - using (var log = new LoggerConfiguration() - .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1) - .CreateLogger()) - { - LogEvent e1 = Some.InformationEvent(), - e2 = Some.InformationEvent(e1.Timestamp), - e3 = Some.InformationEvent(e1.Timestamp); - log.Write(e1); log.Write(e2); log.Write(e3); + foreach (var logEvent in logEvents) + { + log.Write(logEvent); + } - var files = Directory.GetFiles(temp.Path) - .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) - .ToArray(); + files = Directory.GetFiles(temp.Path) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) + .ToArray(); - Assert.Equal(3, files.Length); - Assert.True(files[0].EndsWith(fileName), files[0]); - Assert.True(files[1].EndsWith("_001.txt"), files[1]); - Assert.True(files[2].EndsWith("_002.txt"), files[2]); - } + Assert.Equal(3, files.Length); + Assert.True(files[0].EndsWith(fileName), files[0]); + Assert.True(files[1].EndsWith("_001.txt"), files[1]); + Assert.True(files[2].EndsWith("_002.txt"), files[2]); } - [Fact] - public void WhenStreamWrapperSpecifiedIsUsedForRolledFiles() + // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against + // what we wrote + for (var i = 0; i < files.Length; i++) { - var gzipWrapper = new GZipHooks(); - var fileName = Some.String() + ".txt"; - - using (var temp = new TempFolder()) + using var textStream = new MemoryStream(); + using (var fs = System.IO.File.OpenRead(files[i])) + using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) { - string[] files; - var logEvents = new[] - { - Some.InformationEvent(), - Some.InformationEvent(), - Some.InformationEvent() - }; - - using (var log = new LoggerConfiguration() - .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1, hooks: gzipWrapper) - .CreateLogger()) - { - - foreach (var logEvent in logEvents) - { - log.Write(logEvent); - } - - files = Directory.GetFiles(temp.Path) - .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) - .ToArray(); - - Assert.Equal(3, files.Length); - Assert.True(files[0].EndsWith(fileName), files[0]); - Assert.True(files[1].EndsWith("_001.txt"), files[1]); - Assert.True(files[2].EndsWith("_002.txt"), files[2]); - } - - // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against - // what we wrote - for (var i = 0; i < files.Length; i++) - { - using (var textStream = new MemoryStream()) - { - using (var fs = System.IO.File.OpenRead(files[i])) - using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) - { - decompressStream.CopyTo(textStream); - } - - textStream.Position = 0; - var lines = textStream.ReadAllLines(); - - Assert.Single(lines); - Assert.EndsWith(logEvents[i].MessageTemplate.Text, lines[0]); - } - } + decompressStream.CopyTo(textStream); } + + textStream.Position = 0; + var lines = textStream.ReadAllLines(); + + Assert.Single(lines); + Assert.EndsWith(logEvents[i].MessageTemplate.Text, lines[0]); } + } - [Fact] - public void IfTheLogFolderDoesNotExistItWillBeCreated() - { - var fileName = Some.String() + "-{Date}.txt"; - var temp = Some.TempFolderPath(); - var folder = Path.Combine(temp, Guid.NewGuid().ToString()); - var pathFormat = Path.Combine(folder, fileName); + [Fact] + public void IfTheLogFolderDoesNotExistItWillBeCreated() + { + var fileName = Some.String() + "-{Date}.txt"; + var temp = Some.TempFolderPath(); + var folder = Path.Combine(temp, Guid.NewGuid().ToString()); + var pathFormat = Path.Combine(folder, fileName); - Logger? log = null; + Logger? log = null; - try - { - log = new LoggerConfiguration() - .WriteTo.File(pathFormat, retainedFileCountLimit: 3, rollingInterval: RollingInterval.Day) - .CreateLogger(); + try + { + log = new LoggerConfiguration() + .WriteTo.File(pathFormat, retainedFileCountLimit: 3, rollingInterval: RollingInterval.Day) + .CreateLogger(); - log.Write(Some.InformationEvent()); + log.Write(Some.InformationEvent()); - Assert.True(Directory.Exists(folder)); - } - finally - { - log?.Dispose(); - Directory.Delete(temp, true); - } + Assert.True(Directory.Exists(folder)); } - - static void TestRollingEventSequence(params LogEvent[] events) + finally { - TestRollingEventSequence( - (pf, wt) => wt.File(pf, retainedFileCountLimit: null, rollingInterval: RollingInterval.Day), - events); + log?.Dispose(); + Directory.Delete(temp, true); } + } - static void TestRollingEventSequence( - Action configureFile, - IEnumerable events, - Action>? verifyWritten = null) - { - var fileName = Some.String() + "-.txt"; - var folder = Some.TempFolderPath(); - var pathFormat = Path.Combine(folder, fileName); + static void TestRollingEventSequence(params LogEvent[] events) + { + TestRollingEventSequence( + (pf, wt) => wt.File(pf, retainedFileCountLimit: null, rollingInterval: RollingInterval.Day), + events); + } + + static void TestRollingEventSequence( + Action configureFile, + IEnumerable events, + Action>? verifyWritten = null) + { + var fileName = Some.String() + "-.txt"; + var folder = Some.TempFolderPath(); + var pathFormat = Path.Combine(folder, fileName); - var config = new LoggerConfiguration(); - configureFile(pathFormat, config.WriteTo); - var log = config.CreateLogger(); + var config = new LoggerConfiguration(); + configureFile(pathFormat, config.WriteTo); + var log = config.CreateLogger(); - var verified = new List(); + var verified = new List(); - try + try + { + foreach (var @event in events) { - foreach (var @event in events) - { - Clock.SetTestDateTimeNow(@event.Timestamp.DateTime); - log.Write(@event); + Clock.SetTestDateTimeNow(@event.Timestamp.DateTime); + log.Write(@event); - var expected = pathFormat.Replace(".txt", @event.Timestamp.ToString("yyyyMMdd") + ".txt"); - Assert.True(System.IO.File.Exists(expected)); + var expected = pathFormat.Replace(".txt", @event.Timestamp.ToString("yyyyMMdd") + ".txt"); + Assert.True(System.IO.File.Exists(expected)); - verified.Add(expected); - } - } - finally - { - log.Dispose(); - verifyWritten?.Invoke(verified); - Directory.Delete(folder, true); + verified.Add(expected); } } + finally + { + log.Dispose(); + verifyWritten?.Invoke(verified); + Directory.Delete(folder, true); + } } } diff --git a/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs index 404d5b4..a7b307b 100644 --- a/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs @@ -1,34 +1,32 @@ -using System; -using Xunit; +using Xunit; -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class RollingIntervalExtensionsTests { - public class RollingIntervalExtensionsTests + public static object?[][] IntervalInstantCurrentNextCheckpoint => new[] { - public static object?[][] IntervalInstantCurrentNextCheckpoint => new[] - { - new object?[]{ RollingInterval.Infinite, new DateTime(2018, 01, 01), null, null }, - new object?[]{ RollingInterval.Year, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, - new object?[]{ RollingInterval.Year, new DateTime(2018, 06, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, - new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, - new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 14), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, - new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, - new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01, 12, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, - new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, - new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 30, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, - new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) }, - new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 30), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) } - }; + new object?[]{ RollingInterval.Infinite, new DateTime(2018, 01, 01), null, null }, + new object?[]{ RollingInterval.Year, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, + new object?[]{ RollingInterval.Year, new DateTime(2018, 06, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, + new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, + new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 14), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, + new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, + new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01, 12, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, + new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, + new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 30, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, + new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) }, + new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 30), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) } + }; - [Theory] - [MemberData(nameof(IntervalInstantCurrentNextCheckpoint))] - public void NextIntervalTests(RollingInterval interval, DateTime instant, DateTime? currentCheckpoint, DateTime? nextCheckpoint) - { - var current = interval.GetCurrentCheckpoint(instant); - Assert.Equal(currentCheckpoint, current); + [Theory] + [MemberData(nameof(IntervalInstantCurrentNextCheckpoint))] + public void NextIntervalTests(RollingInterval 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); - } + var next = interval.GetNextCheckpoint(instant); + Assert.Equal(nextCheckpoint, next); } } diff --git a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj index 9fd79c8..5d5af8c 100644 --- a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj +++ b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj @@ -1,15 +1,7 @@ - + - net48;net6.0 - 8.0 - enable - true - Serilog.Sinks.File.Tests - ../../assets/Serilog.snk - true - true true @@ -18,16 +10,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index 565be9b..ec90f25 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -1,103 +1,101 @@ -using System.IO; -using Xunit; +using Xunit; using Serilog.Formatting.Json; using Serilog.Sinks.File.Tests.Support; #pragma warning disable 618 -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class SharedFileSinkTests { - public class SharedFileSinkTests + [Fact] + public void FileIsWrittenIfNonexistent() { - [Fact] - public void FileIsWrittenIfNonexistent() + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var nonexistent = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new SharedFileSink(nonexistent, new JsonFormatter(), null)) - { - sink.Emit(evt); - } - - var lines = System.IO.File.ReadAllLines(nonexistent); - Assert.Contains("Hello, world!", lines[0]); + using (var sink = new SharedFileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); } + } - [Fact] - public void FileIsAppendedToWhenAlreadyCreated() + [Fact] + public void FileIsAppendedToWhenAlreadyCreated() + { + using (var tmp = TempFolder.ForCaller()) { - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } - - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); + } - var lines = System.IO.File.ReadAllLines(path); - Assert.Contains("Hello, world!", lines[0]); - Assert.Contains("Hello, world!", lines[1]); + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(path); + Assert.Contains("Hello, world!", lines[0]); + Assert.Contains("Hello, world!", lines[1]); } + } - [Fact] - public void WhenLimitIsSpecifiedFileSizeIsRestricted() + [Fact] + public void WhenLimitIsSpecifiedFileSizeIsRestricted() + { + const int maxBytes = 5000; + const int eventsToLimit = 10; + + using (var tmp = TempFolder.ForCaller()) { - const int maxBytes = 5000; - const int eventsToLimit = 10; + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var tmp = TempFolder.ForCaller()) + using (var sink = new SharedFileSink(path, new JsonFormatter(), maxBytes)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - - using (var sink = new SharedFileSink(path, new JsonFormatter(), maxBytes)) + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes); - Assert.True(size < maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes); + Assert.True(size < maxBytes * 2); } + } + + [Fact] + public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() + { + const int maxBytes = 5000; + const int eventsToLimit = 10; - [Fact] - public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() + using (var tmp = TempFolder.ForCaller()) { - const int maxBytes = 5000; - const int eventsToLimit = 10; + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var tmp = TempFolder.ForCaller()) + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes * 2); } } } diff --git a/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs index 96dcb8c..566b5ba 100644 --- a/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs +++ b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs @@ -1,35 +1,30 @@ -using System; -using System.IO; -using System.Text; +namespace Serilog.Sinks.File.Tests.Support; -namespace Serilog.Sinks.File.Tests.Support +internal class ArchiveOldLogsHook : FileLifecycleHooks { - internal class ArchiveOldLogsHook : FileLifecycleHooks + private readonly string _relativeArchiveDir; + + public ArchiveOldLogsHook(string relativeArchiveDir) { - private readonly string _relativeArchiveDir; + _relativeArchiveDir = relativeArchiveDir; + } - public ArchiveOldLogsHook(string relativeArchiveDir) - { - _relativeArchiveDir = relativeArchiveDir; - } + public override void OnFileDeleting(string path) + { + base.OnFileDeleting(path); + var newFile = AddTopDirectory(path, _relativeArchiveDir, true); + System.IO.File.Copy(path, newFile, false); + } - public override void OnFileDeleting(string path) - { - base.OnFileDeleting(path); - var newFile = AddTopDirectory(path, _relativeArchiveDir, true); - System.IO.File.Copy(path, newFile, false); - } + public static string AddTopDirectory(string path, string directoryToAdd, bool createOnNonExist = false) + { + string file = Path.GetFileName(path); + string directory = Path.Combine(Path.GetDirectoryName(path) ?? throw new InvalidOperationException(), directoryToAdd); - public static string AddTopDirectory(string path, string directoryToAdd, bool createOnNonExist = false) + if (createOnNonExist && !Directory.Exists(directory)) { - string file = Path.GetFileName(path); - string directory = Path.Combine(Path.GetDirectoryName(path) ?? throw new InvalidOperationException(), directoryToAdd); - - if (createOnNonExist && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - return Path.Combine(directory, file); + Directory.CreateDirectory(directory); } + return Path.Combine(directory, file); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/CaptureFilePathHook.cs b/test/Serilog.Sinks.File.Tests/Support/CaptureFilePathHook.cs index 65857d1..14fa452 100644 --- a/test/Serilog.Sinks.File.Tests/Support/CaptureFilePathHook.cs +++ b/test/Serilog.Sinks.File.Tests/Support/CaptureFilePathHook.cs @@ -1,20 +1,18 @@ -using System.IO; -using System.Text; +using System.Text; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +/// +/// +/// Demonstrates the use of , by capturing the log file path +/// +class CaptureFilePathHook : FileLifecycleHooks { - /// - /// - /// Demonstrates the use of , by capturing the log file path - /// - class CaptureFilePathHook : FileLifecycleHooks - { - public string? Path { get; private set; } + public string? Path { get; private set; } - public override Stream OnFileOpened(string path, Stream _, Encoding __) - { - Path = path; - return base.OnFileOpened(path, _, __); - } + public override Stream OnFileOpened(string path, Stream _, Encoding __) + { + Path = path; + return base.OnFileOpened(path, _, __); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/CollectingSink.cs b/test/Serilog.Sinks.File.Tests/Support/CollectingSink.cs index 244ae5c..323aba8 100644 --- a/test/Serilog.Sinks.File.Tests/Support/CollectingSink.cs +++ b/test/Serilog.Sinks.File.Tests/Support/CollectingSink.cs @@ -1,21 +1,18 @@ -using System.Collections.Generic; -using System.Linq; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +class CollectingSink : ILogEventSink { - class CollectingSink : ILogEventSink - { - readonly List _events = new List(); + readonly List _events = new(); - public List Events { get { return _events; } } + public List Events { get { return _events; } } - public LogEvent SingleEvent { get { return _events.Single(); } } - - public void Emit(LogEvent logEvent) - { - _events.Add(logEvent); - } + public LogEvent SingleEvent { get { return _events.Single(); } } + + public void Emit(LogEvent logEvent) + { + _events.Add(logEvent); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs b/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs index 3ac9974..3386a4f 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs @@ -1,24 +1,21 @@ -using System; +namespace Serilog.Sinks.File.Tests.Support; -namespace Serilog.Sinks.File.Tests.Support +public class DelegateDisposable : IDisposable { - public class DelegateDisposable : IDisposable - { - private readonly Action _disposeAction; - private bool _disposed; + private readonly Action _disposeAction; + private bool _disposed; - public DelegateDisposable(Action disposeAction) - { - _disposeAction = disposeAction; - } + public DelegateDisposable(Action disposeAction) + { + _disposeAction = disposeAction; + } - public void Dispose() - { - if (_disposed) - return; + public void Dispose() + { + if (_disposed) + return; - _disposeAction(); - _disposed = true; - } + _disposeAction(); + _disposed = true; } } diff --git a/test/Serilog.Sinks.File.Tests/Support/DelegatingEnricher.cs b/test/Serilog.Sinks.File.Tests/Support/DelegatingEnricher.cs index 0a480fb..67b9758 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DelegatingEnricher.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DelegatingEnricher.cs @@ -1,22 +1,20 @@ -using System; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +class DelegatingEnricher : ILogEventEnricher { - class DelegatingEnricher : ILogEventEnricher - { - readonly Action _enrich; + readonly Action _enrich; - public DelegatingEnricher(Action enrich) - { - if (enrich == null) throw new ArgumentNullException(nameof(enrich)); - _enrich = enrich; - } + public DelegatingEnricher(Action enrich) + { + if (enrich == null) throw new ArgumentNullException(nameof(enrich)); + _enrich = enrich; + } - public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) - { - _enrich(logEvent, propertyFactory); - } + public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) + { + _enrich(logEvent, propertyFactory); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/DelegatingSink.cs b/test/Serilog.Sinks.File.Tests/Support/DelegatingSink.cs index 12b7f3d..aee1615 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DelegatingSink.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DelegatingSink.cs @@ -1,33 +1,31 @@ -using System; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +public class DelegatingSink : ILogEventSink { - public class DelegatingSink : ILogEventSink - { - readonly Action _write; + readonly Action _write; - public DelegatingSink(Action write) - { - if (write == null) throw new ArgumentNullException(nameof(write)); - _write = write; - } + public DelegatingSink(Action write) + { + if (write == null) throw new ArgumentNullException(nameof(write)); + _write = write; + } - public void Emit(LogEvent logEvent) - { - _write(logEvent); - } + public void Emit(LogEvent logEvent) + { + _write(logEvent); + } - public static LogEvent GetLogEvent(Action writeAction) - { - LogEvent? result = null; - var l = new LoggerConfiguration() - .WriteTo.Sink(new DelegatingSink(le => result = le)) - .CreateLogger(); + public static LogEvent GetLogEvent(Action writeAction) + { + LogEvent? result = null; + var l = new LoggerConfiguration() + .WriteTo.Sink(new DelegatingSink(le => result = le)) + .CreateLogger(); - writeAction(l); - return result!; - } + writeAction(l); + return result!; } } diff --git a/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs b/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs index befcbd4..ff8cf35 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs @@ -1,422 +1,419 @@ -using System; -using System.Collections.Generic; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +public class DisposableLogger : ILogger, IDisposable { - public class DisposableLogger : ILogger, IDisposable - { - public bool Disposed { get; set; } - - public void Dispose() - { - Disposed = true; - } - - public ILogger ForContext(ILogEventEnricher enricher) - { - throw new NotImplementedException(); - } - - public ILogger ForContext(IEnumerable enrichers) - { - throw new NotImplementedException(); - } - - public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) - { - throw new NotImplementedException(); - } - - public ILogger ForContext() - { - throw new NotImplementedException(); - } - - public ILogger ForContext(Type source) - { - throw new NotImplementedException(); - } - - public void Write(LogEvent logEvent) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public bool IsEnabled(LogEventLevel level) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, - out IEnumerable boundProperties) - { - throw new NotImplementedException(); - } - - public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) - { - throw new NotImplementedException(); - } + public bool Disposed { get; set; } + + public void Dispose() + { + Disposed = true; + } + + public ILogger ForContext(ILogEventEnricher enricher) + { + throw new NotImplementedException(); + } + + public ILogger ForContext(IEnumerable enrichers) + { + throw new NotImplementedException(); + } + + public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) + { + throw new NotImplementedException(); + } + + public ILogger ForContext() + { + throw new NotImplementedException(); + } + + public ILogger ForContext(Type source) + { + throw new NotImplementedException(); + } + + public void Write(LogEvent logEvent) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, + T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public bool IsEnabled(LogEventLevel level) + { + throw new NotImplementedException(); + } + + public void Verbose(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Verbose(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Verbose(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Verbose(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Verbose(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Debug(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Debug(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Debug(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Debug(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Debug(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Information(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Information(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Information(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Information(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Information(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Information(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Warning(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Warning(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Warning(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Warning(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Warning(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Error(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Error(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Error(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Error(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Error(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Error(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Fatal(string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Fatal(string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Fatal(string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public void Fatal(Exception exception, string messageTemplate) + { + throw new NotImplementedException(); + } + + public void Fatal(Exception exception, string messageTemplate, T propertyValue) + { + throw new NotImplementedException(); + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) + { + throw new NotImplementedException(); + } + + public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, + T2 propertyValue2) + { + throw new NotImplementedException(); + } + + public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) + { + throw new NotImplementedException(); + } + + public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, + out IEnumerable boundProperties) + { + throw new NotImplementedException(); + } + + public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) + { + throw new NotImplementedException(); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/DisposeTrackingSink.cs b/test/Serilog.Sinks.File.Tests/Support/DisposeTrackingSink.cs index 29cac56..c4e2559 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DisposeTrackingSink.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DisposeTrackingSink.cs @@ -1,20 +1,18 @@ -using System; -using Serilog.Core; +using Serilog.Core; using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +class DisposeTrackingSink : ILogEventSink, IDisposable { - class DisposeTrackingSink : ILogEventSink, IDisposable - { - public bool IsDisposed { get; set; } + public bool IsDisposed { get; set; } - public void Emit(LogEvent logEvent) - { - } + public void Emit(LogEvent logEvent) + { + } - public void Dispose() - { - IsDisposed = true; - } + public void Dispose() + { + IsDisposed = true; } } diff --git a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs index a048353..3fd7a15 100644 --- a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs +++ b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs @@ -1,30 +1,27 @@ -using System.Collections.Generic; -using System.IO; -using Serilog.Events; +using Serilog.Events; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +public static class Extensions { - public static class Extensions + public static object LiteralValue(this LogEventPropertyValue @this) { - public static object LiteralValue(this LogEventPropertyValue @this) - { - return ((ScalarValue)@this).Value; - } + return ((ScalarValue)@this).Value; + } - public static List ReadAllLines(this Stream @this) - { - var lines = new List(); + public static List ReadAllLines(this Stream @this) + { + var lines = new List(); - using (var reader = new StreamReader(@this)) + using (var reader = new StreamReader(@this)) + { + string? line; + while ((line = reader.ReadLine()) != null) { - string? line; - while ((line = reader.ReadLine()) != null) - { - lines.Add(line); - } + lines.Add(line); } - - return lines; } + + return lines; } } diff --git a/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs index ae90604..9e92294 100644 --- a/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs +++ b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs @@ -1,28 +1,26 @@ -using System.IO; -using System.Text; +using System.Text; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +class FileHeaderWriter : FileLifecycleHooks { - class FileHeaderWriter : FileLifecycleHooks + public string Header { get; } + + public FileHeaderWriter(string header) { - public string Header { get; } + Header = header; + } - public FileHeaderWriter(string header) + public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) + { + if (underlyingStream.Length == 0) { - Header = header; + var writer = new StreamWriter(underlyingStream, encoding); + writer.WriteLine(Header); + writer.Flush(); + underlyingStream.Flush(); } - public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) - { - if (underlyingStream.Length == 0) - { - var writer = new StreamWriter(underlyingStream, encoding); - writer.WriteLine(Header); - writer.Flush(); - underlyingStream.Flush(); - } - - return base.OnFileOpened(underlyingStream, encoding); - } + return base.OnFileOpened(underlyingStream, encoding); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/FixedOutputFormatter.cs b/test/Serilog.Sinks.File.Tests/Support/FixedOutputFormatter.cs index 24b70da..650a2e2 100644 --- a/test/Serilog.Sinks.File.Tests/Support/FixedOutputFormatter.cs +++ b/test/Serilog.Sinks.File.Tests/Support/FixedOutputFormatter.cs @@ -1,21 +1,19 @@ using Serilog.Formatting; using Serilog.Events; -using System.IO; -namespace Serilog.Tests.Support +namespace Serilog.Tests.Support; + +public class FixedOutputFormatter : ITextFormatter { - public class FixedOutputFormatter : ITextFormatter - { - string _substitutionText; + string _substitutionText; - public FixedOutputFormatter(string substitutionText) - { - _substitutionText = substitutionText; - } + public FixedOutputFormatter(string substitutionText) + { + _substitutionText = substitutionText; + } - public void Format(LogEvent logEvent, TextWriter output) - { - output.Write(_substitutionText); - } + public void Format(LogEvent logEvent, TextWriter output) + { + output.Write(_substitutionText); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs index 40a77bb..7191db1 100644 --- a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs +++ b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs @@ -1,26 +1,24 @@ -using System.IO; using System.IO.Compression; using System.Text; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +/// +/// +/// Demonstrates the use of , by compressing log output using GZip +/// +public class GZipHooks : FileLifecycleHooks { - /// - /// - /// Demonstrates the use of , by compressing log output using GZip - /// - public class GZipHooks : FileLifecycleHooks - { - readonly int _bufferSize; + readonly int _bufferSize; - public GZipHooks(int bufferSize = 1024 * 32) - { - _bufferSize = bufferSize; - } + public GZipHooks(int bufferSize = 1024 * 32) + { + _bufferSize = bufferSize; + } - public override Stream OnFileOpened(Stream underlyingStream, Encoding _) - { - var compressStream = new GZipStream(underlyingStream, CompressionMode.Compress); - return new BufferedStream(compressStream, _bufferSize); - } + public override Stream OnFileOpened(Stream underlyingStream, Encoding _) + { + var compressStream = new GZipStream(underlyingStream, CompressionMode.Compress); + return new BufferedStream(compressStream, _bufferSize); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/Some.cs b/test/Serilog.Sinks.File.Tests/Support/Some.cs index 4209102..05ddc11 100644 --- a/test/Serilog.Sinks.File.Tests/Support/Some.cs +++ b/test/Serilog.Sinks.File.Tests/Support/Some.cs @@ -1,102 +1,97 @@ -using System; -using System.IO; -using System.Linq; -using System.Threading; -using Serilog.Events; +using Serilog.Events; using Serilog.Parsing; using Xunit.Sdk; // ReSharper disable UnusedMember.Global -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +static class Some { - static class Some - { - static int _counter; + static int _counter; - public static int Int() - { - return Interlocked.Increment(ref _counter); - } + public static int Int() + { + return Interlocked.Increment(ref _counter); + } - public static decimal Decimal() - { - return Int() + 0.123m; - } + public static decimal Decimal() + { + return Int() + 0.123m; + } - public static string String(string? tag = null) - { - return (tag ?? "") + "__" + Int(); - } + public static string String(string? tag = null) + { + return (tag ?? "") + "__" + Int(); + } - public static TimeSpan TimeSpan() - { - return System.TimeSpan.FromMinutes(Int()); - } + public static TimeSpan TimeSpan() + { + return System.TimeSpan.FromMinutes(Int()); + } - public static DateTime Instant() - { - return new DateTime(2012, 10, 28) + TimeSpan(); - } + public static DateTime Instant() + { + return new DateTime(2012, 10, 28) + TimeSpan(); + } - public static DateTimeOffset OffsetInstant() - { - return new DateTimeOffset(Instant()); - } + public static DateTimeOffset OffsetInstant() + { + return new DateTimeOffset(Instant()); + } - public static LogEvent LogEvent(string messageTemplate, params object[] propertyValues) - { - var log = new LoggerConfiguration().CreateLogger(); + public static LogEvent LogEvent(string messageTemplate, params object[] propertyValues) + { + var log = new LoggerConfiguration().CreateLogger(); #pragma warning disable Serilog004 // Constant MessageTemplate verifier - if (!log.BindMessageTemplate(messageTemplate, propertyValues, out var template, out var properties)) + if (!log.BindMessageTemplate(messageTemplate, propertyValues, out var template, out var properties)) #pragma warning restore Serilog004 // Constant MessageTemplate verifier - { - throw new XunitException("Template could not be bound."); - } - return new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, template, properties); - } - - public static LogEvent LogEvent(DateTimeOffset? timestamp = null, LogEventLevel level = LogEventLevel.Information) { - return new LogEvent(timestamp ?? OffsetInstant(), level, - null, MessageTemplate(), Enumerable.Empty()); + throw new XunitException("Template could not be bound."); } + return new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, template, properties); + } - public static LogEvent InformationEvent(DateTimeOffset? timestamp = null) - { - return LogEvent(timestamp); - } + public static LogEvent LogEvent(DateTimeOffset? timestamp = null, LogEventLevel level = LogEventLevel.Information) + { + return new LogEvent(timestamp ?? OffsetInstant(), level, + null, MessageTemplate(), Enumerable.Empty()); + } - public static LogEvent DebugEvent(DateTimeOffset? timestamp = null) - { - return LogEvent(timestamp, LogEventLevel.Debug); - } + public static LogEvent InformationEvent(DateTimeOffset? timestamp = null) + { + return LogEvent(timestamp); + } - public static LogEventProperty LogEventProperty() - { - return new LogEventProperty(String(), new ScalarValue(Int())); - } + public static LogEvent DebugEvent(DateTimeOffset? timestamp = null) + { + return LogEvent(timestamp, LogEventLevel.Debug); + } - public static string NonexistentTempFilePath() - { - return Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".txt"); - } + public static LogEventProperty LogEventProperty() + { + return new LogEventProperty(String(), new ScalarValue(Int())); + } - public static string TempFilePath() - { - return Path.GetTempFileName(); - } + public static string NonexistentTempFilePath() + { + return Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".txt"); + } - public static string TempFolderPath() - { - var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(dir); - return dir; - } + public static string TempFilePath() + { + return Path.GetTempFileName(); + } - public static MessageTemplate MessageTemplate() - { - return new MessageTemplateParser().Parse(String()); - } + public static string TempFolderPath() + { + var dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(dir); + return dir; + } + + public static MessageTemplate MessageTemplate() + { + return new MessageTemplateParser().Parse(String()); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/TempFolder.cs b/test/Serilog.Sinks.File.Tests/Support/TempFolder.cs index 29682e0..10d751e 100644 --- a/test/Serilog.Sinks.File.Tests/Support/TempFolder.cs +++ b/test/Serilog.Sinks.File.Tests/Support/TempFolder.cs @@ -1,55 +1,52 @@ -using System; -using System.Diagnostics; -using System.IO; +using System.Diagnostics; using System.Runtime.CompilerServices; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +class TempFolder : IDisposable { - class TempFolder : IDisposable - { - static readonly Guid Session = Guid.NewGuid(); + static readonly Guid Session = Guid.NewGuid(); - readonly string _tempFolder; + readonly string _tempFolder; - public TempFolder(string? name = null) - { - _tempFolder = System.IO.Path.Combine( - Environment.GetEnvironmentVariable("TMP") ?? Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp", - "Serilog.Sinks.File.Tests", - Session.ToString("n"), - name ?? Guid.NewGuid().ToString("n")); + public TempFolder(string? name = null) + { + _tempFolder = System.IO.Path.Combine( + Environment.GetEnvironmentVariable("TMP") ?? Environment.GetEnvironmentVariable("TMPDIR") ?? "/tmp", + "Serilog.Sinks.File.Tests", + Session.ToString("n"), + name ?? Guid.NewGuid().ToString("n")); - Directory.CreateDirectory(_tempFolder); - } + Directory.CreateDirectory(_tempFolder); + } - public string Path => _tempFolder; + public string Path => _tempFolder; - public void Dispose() + public void Dispose() + { + try { - try - { - if (Directory.Exists(_tempFolder)) - Directory.Delete(_tempFolder, true); - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } + if (Directory.Exists(_tempFolder)) + Directory.Delete(_tempFolder, true); } - - public static TempFolder ForCaller([CallerMemberName] string? caller = null, [CallerFilePath] string sourceFileName = "") + catch (Exception ex) { - if (caller == null) throw new ArgumentNullException(nameof(caller)); - if (sourceFileName == null) throw new ArgumentNullException(nameof(sourceFileName)); - - var folderName = System.IO.Path.GetFileNameWithoutExtension(sourceFileName) + "_" + caller; - - return new TempFolder(folderName); + Debug.WriteLine(ex); } + } - public string AllocateFilename(string? ext = null) - { - return System.IO.Path.Combine(Path, Guid.NewGuid().ToString("n") + "." + (ext ?? "tmp")); - } + public static TempFolder ForCaller([CallerMemberName] string? caller = null, [CallerFilePath] string sourceFileName = "") + { + if (caller == null) throw new ArgumentNullException(nameof(caller)); + if (sourceFileName == null) throw new ArgumentNullException(nameof(sourceFileName)); + + var folderName = System.IO.Path.GetFileNameWithoutExtension(sourceFileName) + "_" + caller; + + return new TempFolder(folderName); + } + + public string AllocateFilename(string? ext = null) + { + return System.IO.Path.Combine(Path, Guid.NewGuid().ToString("n") + "." + (ext ?? "tmp")); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/ThrowingLogEventFormatter.cs b/test/Serilog.Sinks.File.Tests/Support/ThrowingLogEventFormatter.cs index 9170ef5..10de763 100644 --- a/test/Serilog.Sinks.File.Tests/Support/ThrowingLogEventFormatter.cs +++ b/test/Serilog.Sinks.File.Tests/Support/ThrowingLogEventFormatter.cs @@ -1,15 +1,12 @@ -using System; -using System.IO; -using Serilog.Events; +using Serilog.Events; using Serilog.Formatting; -namespace Serilog.Tests.Support +namespace Serilog.Tests.Support; + +public class ThrowingLogEventFormatter : ITextFormatter { - public class ThrowingLogEventFormatter : ITextFormatter + public void Format(LogEvent logEvent, TextWriter output) { - public void Format(LogEvent logEvent, TextWriter output) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs b/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs index 63f8497..aecc413 100644 --- a/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs +++ b/test/Serilog.Sinks.File.Tests/Support/TruncateFileHook.cs @@ -1,18 +1,16 @@ -using System.IO; using System.Text; -namespace Serilog.Sinks.File.Tests.Support +namespace Serilog.Sinks.File.Tests.Support; + +/// +/// +/// Demonstrates the use of , by emptying the file before it's written to +/// +public class TruncateFileHook : FileLifecycleHooks { - /// - /// - /// Demonstrates the use of , by emptying the file before it's written to - /// - public class TruncateFileHook : FileLifecycleHooks + public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) { - public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) - { - underlyingStream.SetLength(0); - return base.OnFileOpened(underlyingStream, encoding); - } + underlyingStream.SetLength(0); + return base.OnFileOpened(underlyingStream, encoding); } } diff --git a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs index 65a974c..e34c335 100644 --- a/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs +++ b/test/Serilog.Sinks.File.Tests/TemplatedPathRollerTests.cs @@ -1,100 +1,96 @@ -using System; -using System.IO; -using System.Linq; -using Xunit; +using Xunit; -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class PathRollerTests { - public class PathRollerTests + [Fact] + public void TheLogFileIncludesDateToken() { - [Fact] - public void TheLogFileIncludesDateToken() - { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); - 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); - } + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + 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); + } - [Fact] - public void ANonZeroIncrementIsIncludedAndPadded() - { - var roller = new PathRoller(Path.Combine("Logs", "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", "log-20130714_012.txt"), path); - } + [Fact] + public void ANonZeroIncrementIsIncludedAndPadded() + { + var roller = new PathRoller(Path.Combine("Logs", "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", "log-20130714_012.txt"), path); + } - static void AssertEqualAbsolute(string path1, string path2) - { - var abs1 = Path.GetFullPath(path1); - var abs2 = Path.GetFullPath(path2); - Assert.Equal(abs1, abs2); - } + static void AssertEqualAbsolute(string path1, string path2) + { + var abs1 = Path.GetFullPath(path1); + var abs2 = Path.GetFullPath(path2); + Assert.Equal(abs1, abs2); + } - [Fact] - public void TheRollerReturnsTheLogFileDirectory() - { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); - AssertEqualAbsolute("Logs", roller.LogFileDirectory); - } + [Fact] + public void TheRollerReturnsTheLogFileDirectory() + { + var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); + AssertEqualAbsolute("Logs", roller.LogFileDirectory); + } - [Fact] - public void TheLogFileIsNotRequiredToIncludeAnExtension() - { - var roller = new PathRoller(Path.Combine("Logs", "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", "log-20130714"), path); - } + [Fact] + public void TheLogFileIsNotRequiredToIncludeAnExtension() + { + var roller = new PathRoller(Path.Combine("Logs", "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", "log-20130714"), path); + } - [Fact] - public void TheLogFileIsNotRequiredToIncludeADirectory() - { - var roller = new PathRoller("log-", RollingInterval.Day); - var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); - roller.GetLogFilePath(now, null, out var path); - AssertEqualAbsolute("log-20130714", path); - } + [Fact] + public void TheLogFileIsNotRequiredToIncludeADirectory() + { + var roller = new PathRoller("log-", RollingInterval.Day); + var now = new DateTime(2013, 7, 14, 3, 24, 9, 980); + roller.GetLogFilePath(now, null, out var path); + AssertEqualAbsolute("log-20130714", 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 }); - Assert.Empty(matched); - } + [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 }); + Assert.Empty(matched); + } - [Fact] - public void TheDirectorSearchPatternUsesWildcardInPlaceOfDate() - { - var roller = new PathRoller(Path.Combine("Logs", "log-.txt"), RollingInterval.Day); - Assert.Equal("log-*.txt", roller.DirectorySearchPattern); - } + [Fact] + public void TheDirectorSearchPatternUsesWildcardInPlaceOfDate() + { + var roller = new PathRoller(Path.Combine("Logs", "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)] - public void MatchingSelectsFiles(string template, string zeroth, string thirtyFirst, RollingInterval interval) - { - var roller = new PathRoller(template, interval); - var matched = roller.SelectMatches(new[] { zeroth, thirtyFirst }).ToArray(); - Assert.Equal(2, matched.Length); - Assert.Null(matched[0].SequenceNumber); - Assert.Equal(31, matched[1].SequenceNumber); - } + [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)] + public void MatchingSelectsFiles(string template, string zeroth, string thirtyFirst, RollingInterval interval) + { + var roller = new PathRoller(template, interval); + var matched = roller.SelectMatches(new[] { zeroth, thirtyFirst }).ToArray(); + Assert.Equal(2, matched.Length); + Assert.Null(matched[0].SequenceNumber); + Assert.Equal(31, matched[1].SequenceNumber); + } - [Theory] - [InlineData("log-.txt", "log-20150101.txt", "log-20141231.txt", RollingInterval.Day)] - [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 matched = roller.SelectMatches(new[] { older, newer }).OrderByDescending(m => m.DateTime).Select(m => m.Filename).ToArray(); - Assert.Equal(new[] { newer, older }, matched); - } + [Theory] + [InlineData("log-.txt", "log-20150101.txt", "log-20141231.txt", RollingInterval.Day)] + [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 matched = roller.SelectMatches(new[] { older, newer }).OrderByDescending(m => m.DateTime).Select(m => m.Filename).ToArray(); + Assert.Equal(new[] { newer, older }, matched); } } diff --git a/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs b/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs index 887ffe2..68a8cc8 100644 --- a/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs +++ b/test/Serilog.Sinks.File.Tests/WriteCountingStreamTests.cs @@ -1,83 +1,77 @@ -using System.IO; using System.Text; using Serilog.Sinks.File.Tests.Support; using Xunit; -namespace Serilog.Sinks.File.Tests +namespace Serilog.Sinks.File.Tests; + +public class WriteCountingStreamTests { - public class WriteCountingStreamTests + [Fact] + public void CountedLengthIsResetToStreamLengthIfNewSizeIsSmaller() { - [Fact] - public void CountedLengthIsResetToStreamLengthIfNewSizeIsSmaller() + // If we counted 10 bytes written and SetLength was called with a smaller length (e.g. 5) + // we adjust the counter to the new byte count of the file to reflect reality + + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + + long streamLengthAfterSetLength; + long countedLengthAfterSetLength; + + using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var countingStream = new WriteCountingStream(fileStream)) + using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) { - // If we counted 10 bytes written and SetLength was called with a smaller length (e.g. 5) - // we adjust the counter to the new byte count of the file to reflect reality + writer.WriteLine("Hello, world!"); + writer.Flush(); - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); + countingStream.SetLength(5); + streamLengthAfterSetLength = countingStream.Length; + countedLengthAfterSetLength = countingStream.CountedLength; + } - long streamLengthAfterSetLength; - long countedLengthAfterSetLength; + Assert.Equal(5, streamLengthAfterSetLength); + Assert.Equal(5, countedLengthAfterSetLength); - using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) - using (var countingStream = new WriteCountingStream(fileStream)) - using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) - { - writer.WriteLine("Hello, world!"); - writer.Flush(); + var lines = System.IO.File.ReadAllLines(path); - countingStream.SetLength(5); - streamLengthAfterSetLength = countingStream.Length; - countedLengthAfterSetLength = countingStream.CountedLength; - } + Assert.Single(lines); + Assert.Equal("Hello", lines[0]); + } - Assert.Equal(5, streamLengthAfterSetLength); - Assert.Equal(5, countedLengthAfterSetLength); + [Fact] + public void CountedLengthRemainsTheSameIfNewSizeIsLarger() + { + // If we counted 10 bytes written and SetLength was called with a larger length (e.g. 100) + // we leave the counter intact because our position on the stream remains the same... The + // file just grew larger in size - var lines = System.IO.File.ReadAllLines(path); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); - Assert.Single(lines); - Assert.Equal("Hello", lines[0]); - } - } + long streamLengthBeforeSetLength; + long streamLengthAfterSetLength; + long countedLengthAfterSetLength; - [Fact] - public void CountedLengthRemainsTheSameIfNewSizeIsLarger() + using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (var countingStream = new WriteCountingStream(fileStream)) + using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) { - // If we counted 10 bytes written and SetLength was called with a larger length (e.g. 100) - // we leave the counter intact because our position on the stream remains the same... The - // file just grew larger in size - - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - - long streamLengthBeforeSetLength; - long streamLengthAfterSetLength; - long countedLengthAfterSetLength; - - using (var fileStream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read)) - using (var countingStream = new WriteCountingStream(fileStream)) - using (var writer = new StreamWriter(countingStream, new UTF8Encoding(false))) - { - writer.WriteLine("Hello, world!"); - writer.Flush(); - - streamLengthBeforeSetLength = countingStream.CountedLength; - countingStream.SetLength(100); - streamLengthAfterSetLength = countingStream.Length; - countedLengthAfterSetLength = countingStream.CountedLength; - } - - Assert.Equal(100, streamLengthAfterSetLength); - Assert.Equal(streamLengthBeforeSetLength, countedLengthAfterSetLength); - - var lines = System.IO.File.ReadAllLines(path); - - Assert.Equal(2, lines.Length); - Assert.Equal("Hello, world!", lines[0]); - } + writer.WriteLine("Hello, world!"); + writer.Flush(); + + streamLengthBeforeSetLength = countingStream.CountedLength; + countingStream.SetLength(100); + streamLengthAfterSetLength = countingStream.Length; + countedLengthAfterSetLength = countingStream.CountedLength; } + + Assert.Equal(100, streamLengthAfterSetLength); + Assert.Equal(streamLengthBeforeSetLength, countedLengthAfterSetLength); + + var lines = System.IO.File.ReadAllLines(path); + + Assert.Equal(2, lines.Length); + Assert.Equal("Hello, world!", lines[0]); } }