From f197183bc3ae53d765c549151a6a41da3e01ac96 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 30 Oct 2017 11:46:27 +1000 Subject: [PATCH 01/38] Dev version bump [Skip CI] --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 569e5b0..320ce07 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -2,7 +2,7 @@ Write Serilog events to text files in plain or JSON format. - 4.0.0 + 4.0.1 Serilog Contributors net45;netstandard1.3 true From 55c5aa2bc3d9d4c766293de23dc9d51895ec9501 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 31 Oct 2017 07:48:37 +1000 Subject: [PATCH 02/38] Renamed `pathFormat` to correct `path` in example - #38 [Skip CI] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87596a7..9ee3bd0 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ In your application's `App.config` or `Web.config` file, specify the file sink a - + ``` The parameters that can be set through the `serilog:write-to:File` keys are the method parameters accepted by the `WriteTo.File()` configuration method. This means, for example, that the `fileSizeLimitBytes` parameter can be set with: From b8bb2cc556987fa065b0035d651bec8071561fb2 Mon Sep 17 00:00:00 2001 From: Thibaud Desodt Date: Wed, 29 Nov 2017 11:00:45 +0100 Subject: [PATCH 03/38] Fix link to nuget package page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9ee3bd0..08f0050 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Writes [Serilog](https://serilog.net) events to one or more text files. ### Getting started -Install the [Serilog.Sinks.File](https://nuget.org/serilog/serilog-sinks-file) package from NuGet: +Install the [Serilog.Sinks.File](https://www.nuget.org/packages/Serilog.Sinks.File/) package from NuGet: ```powershell Install-Package Serilog.Sinks.File From 0c2e485ed04f0e27e88316571c75a3ae2c01e96b Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Wed, 21 Feb 2018 12:23:31 -0600 Subject: [PATCH 04/38] Docs: Fix level output in default format --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08f0050..153a82d 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ The default format above corresponds to an output template like: ```csharp .WriteTo.File("log.txt", - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{u3}] {Message:lj}{NewLine}{Exception}") + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") ``` ##### JSON event formatting From 56b3753c9477dcc207392e0817932ad949c92d28 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 22 Feb 2018 13:40:33 +1000 Subject: [PATCH 05/38] Try to fit the example output template within the margins [Skip CI] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 153a82d..df54699 100644 --- a/README.md +++ b/README.md @@ -154,8 +154,8 @@ The format is controlled using an _output template_, which the file configuratio The default format above corresponds to an output template like: ```csharp - .WriteTo.File("log.txt", - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .WriteTo.File("log.txt", + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") ``` ##### JSON event formatting From 9fe9c0d765d7a566e4355ee9aa9608dc391aa2ea Mon Sep 17 00:00:00 2001 From: Thibaud Desodt Date: Mon, 5 Mar 2018 09:09:47 +0100 Subject: [PATCH 06/38] Fix remaining reference to RollingFile sink --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df54699..9e4f19f 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ To avoid bringing down apps with runaway disk usage the file sink **limits file For the same reason, only **the most recent 31 files** are retained by default (i.e. one long month). To change or remove this limit, pass the `retainedFileCountLimit` parameter. ```csharp - .WriteTo.RollingFile("log.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: null) + .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day, retainedFileCountLimit: null) ``` ### Rolling policies From f18cb0e8aaf54a77c85cfd392028c94218888e4c Mon Sep 17 00:00:00 2001 From: Matthew Erbs Date: Wed, 9 May 2018 15:53:18 +1000 Subject: [PATCH 07/38] New NuGet API Key --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 7e5f9b5..71f05e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: nvZ/z+pMS91b3kG4DgfES5AcmwwGoBYQxr9kp4XiJHj25SAlgdIxFx++1N0lFH2x + secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8 skip_symbols: true on: branch: /^(master|dev)$/ From 37a2bfeaebbb9c7cc1a86758ac56dd4787c8b358 Mon Sep 17 00:00:00 2001 From: Maxime Rouiller Date: Fri, 21 Sep 2018 14:39:06 -0400 Subject: [PATCH 08/38] fixing repository url --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 320ce07..71ef799 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -15,6 +15,8 @@ http://serilog.net/images/serilog-sink-nuget.png http://serilog.net http://www.apache.org/licenses/LICENSE-2.0 + https://github.com/serilog/serilog-sinks-file + git false Serilog true From 99030369f480980507db2a73c9815204b1ba9dca Mon Sep 17 00:00:00 2001 From: Ruslan Shupoval Date: Sat, 3 Nov 2018 05:46:57 -0600 Subject: [PATCH 09/38] add SourceLink support --- .editorconfig | 2 +- .travis.yml | 2 +- Build.ps1 | 4 ++-- Directory.Build.props | 10 ++++++++++ serilog-sinks-file.sln | 1 + src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 Directory.Build.props diff --git a/.editorconfig b/.editorconfig index f16002a..102e19f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ insert_final_newline = true indent_style = space indent_size = 4 -[*.{csproj,json,config,yml}] +[*.{csproj,json,config,yml,props}] indent_size = 2 [*.sh] diff --git a/.travis.yml b/.travis.yml index 6a880da..3b4b3bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: - os: linux dist: trusty sudo: required - dotnet: 2.0.0 + dotnet: 2.1.300 group: edge script: - ./build.sh diff --git a/Build.ps1 b/Build.ps1 index ee4117d..00cc642 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -25,9 +25,9 @@ foreach ($src in ls src/*) { & dotnet build -c Release --version-suffix=$buildSuffix if ($suffix) { - & dotnet pack -c Release --include-source -o ..\..\artifacts --version-suffix=$suffix --no-build + & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build } else { - & dotnet pack -c Release --include-source -o ..\..\artifacts --no-build + & dotnet pack -c Release -o ..\..\artifacts --no-build } if($LASTEXITCODE -ne 0) { exit 1 } diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..f6e7b79 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + + + + diff --git a/serilog-sinks-file.sln b/serilog-sinks-file.sln index 71527e4..989031c 100644 --- a/serilog-sinks-file.sln +++ b/serilog-sinks-file.sln @@ -12,6 +12,7 @@ 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 NuGet.Config = NuGet.Config README.md = README.md assets\Serilog.snk = assets\Serilog.snk diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 71ef799..4c543c8 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -1,4 +1,4 @@ - + Write Serilog events to text files in plain or JSON format. From 1270a765418cef32a906e6935d7d5b380bd317c1 Mon Sep 17 00:00:00 2001 From: Ruslan Shupoval Date: Wed, 14 Nov 2018 16:31:43 -0700 Subject: [PATCH 10/38] move SourceLink configuration from Directory.Build.prop into csproj; disable SourceLink by default; --- Build.ps1 | 2 +- Directory.Build.props | 10 ---------- build.sh | 2 +- serilog-sinks-file.sln | 1 - src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 5 +++++ 5 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 Directory.Build.props diff --git a/Build.ps1 b/Build.ps1 index 00cc642..f7ed285 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -23,7 +23,7 @@ foreach ($src in ls src/*) { echo "build: Packaging project in $src" - & dotnet build -c Release --version-suffix=$buildSuffix + & dotnet build -c Release --version-suffix=$buildSuffix -p:EnableSourceLink=true if ($suffix) { & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build } else { diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index f6e7b79..0000000 --- a/Directory.Build.props +++ /dev/null @@ -1,10 +0,0 @@ - - - - true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - - - - diff --git a/build.sh b/build.sh index 4e46f40..56d265b 100755 --- a/build.sh +++ b/build.sh @@ -3,7 +3,7 @@ dotnet --info dotnet restore for path in src/**/*.csproj; do - dotnet build -f netstandard1.3 -c Release ${path} + dotnet build -f netstandard1.3 -c Release ${path} -p:EnableSourceLink=true done for path in test/*.Tests/*.csproj; do diff --git a/serilog-sinks-file.sln b/serilog-sinks-file.sln index 989031c..71527e4 100644 --- a/serilog-sinks-file.sln +++ b/serilog-sinks-file.sln @@ -12,7 +12,6 @@ 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 NuGet.Config = NuGet.Config README.md = README.md assets\Serilog.snk = assets\Serilog.snk diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 4c543c8..0594db1 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -23,9 +23,14 @@ Serilog.Sinks.File true + + false + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + From b952e33fb6fc6ae746ace855080251624a8227df Mon Sep 17 00:00:00 2001 From: Vadim Hatsura Date: Thu, 10 Jan 2019 15:48:27 +0300 Subject: [PATCH 11/38] Add target for .net standard 2.0 --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 0594db1..1c2716b 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -4,7 +4,7 @@ Write Serilog events to text files in plain or JSON format. 4.0.1 Serilog Contributors - net45;netstandard1.3 + net45;netstandard1.3;netstandard2.0 true Serilog.Sinks.File ../../assets/Serilog.snk @@ -48,6 +48,10 @@ $(DefineConstants);OS_MUTEX + + $(DefineConstants);OS_MUTEX + + @@ -58,4 +62,11 @@ + + + + + + + From 195f95963e38d753b0fbd72e628bc659bff63d74 Mon Sep 17 00:00:00 2001 From: Vadim Hatsura Date: Mon, 14 Jan 2019 11:37:00 +0300 Subject: [PATCH 12/38] Remove System.IO and System.IO.FileSystem.Primitives from dependencies --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 1c2716b..6d7babf 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -30,7 +30,7 @@ - + @@ -63,9 +63,7 @@ - - From b472fc867140adc09e3b262049eb375e89213e38 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 17 Jan 2019 10:45:22 +1000 Subject: [PATCH 13/38] Update minor version to reflect added capability --- src/Serilog.Sinks.File/Serilog.Sinks.File.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index 6d7babf..f8640f6 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -1,8 +1,8 @@ - + Write Serilog events to text files in plain or JSON format. - 4.0.1 + 4.1.0 Serilog Contributors net45;netstandard1.3;netstandard2.0 true From f7bfe4e672cddbb578c948d0e5f93153d712de8e Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Fri, 1 Feb 2019 14:57:12 +0000 Subject: [PATCH 14/38] Enabled FileSink and RollingFileSink's output stream in another stream, such as a GZipStream --- .../FileLoggerConfigurationExtensions.cs | 33 +++++++---- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 9 ++- .../Sinks/File/RollingFileSink.cs | 7 ++- src/Serilog.Sinks.File/StreamWrapper.cs | 17 ++++++ .../Serilog.Sinks.File.Tests/FileSinkTests.cs | 43 +++++++++++++- .../RollingFileSinkTests.cs | 59 +++++++++++++++++++ .../Support/Extensions.cs | 20 ++++++- .../Support/GZipStreamWrapper.cs | 25 ++++++++ 8 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 src/Serilog.Sinks.File/StreamWrapper.cs create mode 100644 test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 5cb19e9..bf2d5a4 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -135,11 +135,12 @@ public static LoggerConfiguration File( /// 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 + /// 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 wrapping the output stream in another stream, such as a GZipStream. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -156,7 +157,8 @@ public static LoggerConfiguration File( RollingInterval rollingInterval = RollingInterval.Infinite, bool rollOnFileSizeLimit = false, int? retainedFileCountLimit = DefaultRetainedFileCountLimit, - Encoding encoding = null) + Encoding encoding = null, + StreamWrapper wrapper = null) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); if (path == null) throw new ArgumentNullException(nameof(path)); @@ -165,7 +167,7 @@ public static LoggerConfiguration File( var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, - rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding); + rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, wrapper); } /// @@ -174,7 +176,7 @@ public static LoggerConfiguration File( /// Logger sink configuration. /// A formatter, such as , to convert the log events into /// text for the file. If control of regular text formatting is required, use the other - /// overload of + /// overload of /// and specify the outputTemplate parameter instead. /// /// Path to the file. @@ -190,11 +192,12 @@ public static LoggerConfiguration File( /// 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 + /// 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 wrapping the output stream in another stream, such as a GZipStream. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -210,10 +213,12 @@ public static LoggerConfiguration File( RollingInterval rollingInterval = RollingInterval.Infinite, bool rollOnFileSizeLimit = false, int? retainedFileCountLimit = DefaultRetainedFileCountLimit, - Encoding encoding = null) + Encoding encoding = null, + StreamWrapper wrapper = null) { return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, - buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit); + buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, + retainedFileCountLimit, wrapper); } /// @@ -270,7 +275,7 @@ public static LoggerConfiguration File( LoggingLevelSwitch levelSwitch = null) { return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true, - false, null, null, RollingInterval.Infinite, false, null); + false, null, null, RollingInterval.Infinite, false, null, null); } static LoggerConfiguration ConfigureFile( @@ -287,7 +292,8 @@ static LoggerConfiguration ConfigureFile( Encoding encoding, RollingInterval rollingInterval, bool rollOnFileSizeLimit, - int? retainedFileCountLimit) + int? retainedFileCountLimit, + StreamWrapper wrapper) { if (addSink == null) throw new ArgumentNullException(nameof(addSink)); if (formatter == null) throw new ArgumentNullException(nameof(formatter)); @@ -300,7 +306,7 @@ static LoggerConfiguration ConfigureFile( if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) { - sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit); + sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, wrapper); } else { @@ -309,11 +315,16 @@ static LoggerConfiguration ConfigureFile( #pragma warning disable 618 if (shared) { + if (wrapper != null) + { + SelfLog.WriteLine("Unable to use output stream wrapper - these are not supported for shared log files"); + } + sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); } else { - sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered); + sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered, wrapper: wrapper); } #pragma warning restore 618 } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index bfd288f..c896034 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -43,10 +43,12 @@ public sealed class FileSink : IFileSink, IDisposable /// 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. + /// Optionally enables wrapping the output stream in another stream, such as a GZipStream. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. /// - public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false) + public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false, + StreamWrapper wrapper = null) { if (path == null) throw new ArgumentNullException(nameof(path)); if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); @@ -68,6 +70,11 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); } + if (wrapper != null) + { + outputStream = wrapper.Wrap(outputStream); + } + _output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 644176f..73415b2 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -35,6 +35,7 @@ sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable readonly bool _buffered; readonly bool _shared; readonly bool _rollOnFileSizeLimit; + readonly StreamWrapper _wrapper; readonly object _syncRoot = new object(); bool _isDisposed; @@ -50,7 +51,8 @@ public RollingFileSink(string path, bool buffered, bool shared, RollingInterval rollingInterval, - bool rollOnFileSizeLimit) + bool rollOnFileSizeLimit, + StreamWrapper wrapper = null) { if (path == null) throw new ArgumentNullException(nameof(path)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); @@ -64,6 +66,7 @@ public RollingFileSink(string path, _buffered = buffered; _shared = shared; _rollOnFileSizeLimit = rollOnFileSizeLimit; + _wrapper = wrapper; } public void Emit(LogEvent logEvent) @@ -144,7 +147,7 @@ void OpenFile(DateTime now, int? minSequence = null) { _currentFile = _shared ? (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : - new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered); + new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _wrapper); _currentFileSequence = sequence; } catch (IOException ex) diff --git a/src/Serilog.Sinks.File/StreamWrapper.cs b/src/Serilog.Sinks.File/StreamWrapper.cs new file mode 100644 index 0000000..bccdd22 --- /dev/null +++ b/src/Serilog.Sinks.File/StreamWrapper.cs @@ -0,0 +1,17 @@ +using System.IO; + +namespace Serilog +{ + /// + /// Wraps the log file's output stream in another stream, such as a GZipStream + /// + public abstract class StreamWrapper + { + /// + /// Wraps in another stream, such as a GZipStream, then returns the wrapped stream + /// + /// The source log file stream + /// The wrapped stream + public abstract Stream Wrap(Stream sourceStream); + } +} diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index ea9a5d4..3cc83f4 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -1,9 +1,11 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; using Xunit; using Serilog.Formatting.Json; using Serilog.Sinks.File.Tests.Support; using Serilog.Tests.Support; -using System.Text; #pragma warning disable 618 @@ -141,6 +143,42 @@ public void WhenLimitIsNotSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppend WriteTwoEventsAndCheckOutputFileLength(null, encoding); } + [Fact] + public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() + { + var gzipWrapper = new GZipStreamWrapper(); + + using (var tmp = TempFolder.ForCaller()) + { + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); + + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, wrapper: gzipWrapper)) + { + sink.Emit(evt); + sink.Emit(evt); + } + + // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against + // what we wrote + var lines = new List(); + using (var textStream = new MemoryStream()) + { + using (var fs = System.IO.File.OpenRead(nonexistent)) + using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) + { + decompressStream.CopyTo(textStream); + } + + textStream.Position = 0; + lines = textStream.ReadAllLines(); + } + + Assert.Equal(2, lines.Count); + Assert.Contains("Hello, world!", lines[0]); + } + } + static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) { using (var tmp = TempFolder.ForCaller()) @@ -170,4 +208,3 @@ static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding enco } } } - diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index 3efe3f9..c2521df 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Reflection; using Xunit; @@ -96,6 +97,64 @@ public void WhenSizeLimitIsBreachedNewFilesCreated() } } + [Fact] + public void WhenStreamWrapperSpecifiedIsUsedForRolledFiles() + { + var gzipWrapper = new GZipStreamWrapper(); + var fileName = Some.String() + ".txt"; + + using (var temp = new TempFolder()) + { + 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, wrapper: 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.Equal(1, lines.Count); + Assert.True(lines[0].EndsWith(logEvents[i].MessageTemplate.Text)); + } + } + } + } + [Fact] public void IfTheLogFolderDoesNotExistItWillBeCreated() { diff --git a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs index a31122d..f7fb775 100644 --- a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs +++ b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs @@ -1,4 +1,6 @@ -using Serilog.Events; +using System.Collections.Generic; +using System.IO; +using Serilog.Events; namespace Serilog.Sinks.File.Tests.Support { @@ -8,5 +10,21 @@ public static object LiteralValue(this LogEventPropertyValue @this) { return ((ScalarValue)@this).Value; } + + public static List ReadAllLines(this Stream @this) + { + var lines = new List(); + + using (var reader = new StreamReader(@this)) + { + string line; + while ((line = reader.ReadLine()) != null) + { + lines.Add(line); + } + } + + return lines; + } } } diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs b/test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs new file mode 100644 index 0000000..30c316c --- /dev/null +++ b/test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs @@ -0,0 +1,25 @@ +using System.IO; +using System.IO.Compression; + +namespace Serilog.Sinks.File.Tests.Support +{ + /// + /// + /// Demonstrates the use of , by compressing log output using GZip + /// + public class GZipStreamWrapper : StreamWrapper + { + readonly int _bufferSize; + + public GZipStreamWrapper(int bufferSize = 1024 * 32) + { + _bufferSize = bufferSize; + } + + public override Stream Wrap(Stream sourceStream) + { + var compressStream = new GZipStream(sourceStream, CompressionMode.Compress); + return new BufferedStream(compressStream, _bufferSize); + } + } +} From 9eee731a4ccf6f3338a20acebc2d5a3d64199fb5 Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Thu, 14 Feb 2019 20:56:16 +0000 Subject: [PATCH 15/38] Rename StreamWrapper to FileLifecycleHooks --- ...StreamWrapper.cs => FileLifecycleHooks.cs} | 7 +++--- .../FileLoggerConfigurationExtensions.cs | 22 +++++++++---------- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 8 +++---- .../Sinks/File/RollingFileSink.cs | 8 +++---- .../Serilog.Sinks.File.Tests/FileSinkTests.cs | 4 ++-- .../RollingFileSinkTests.cs | 4 ++-- .../{GZipStreamWrapper.cs => GZipHooks.cs} | 6 ++--- 7 files changed, 30 insertions(+), 29 deletions(-) rename src/Serilog.Sinks.File/{StreamWrapper.cs => FileLifecycleHooks.cs} (75%) rename test/Serilog.Sinks.File.Tests/Support/{GZipStreamWrapper.cs => GZipHooks.cs} (68%) diff --git a/src/Serilog.Sinks.File/StreamWrapper.cs b/src/Serilog.Sinks.File/FileLifecycleHooks.cs similarity index 75% rename from src/Serilog.Sinks.File/StreamWrapper.cs rename to src/Serilog.Sinks.File/FileLifecycleHooks.cs index bccdd22..bdfe941 100644 --- a/src/Serilog.Sinks.File/StreamWrapper.cs +++ b/src/Serilog.Sinks.File/FileLifecycleHooks.cs @@ -1,11 +1,12 @@ -using System.IO; namespace Serilog { + using System.IO; + /// - /// Wraps the log file's output stream in another stream, such as a GZipStream + /// Enables hooking into log file lifecycle events /// - public abstract class StreamWrapper + public abstract class FileLifecycleHooks { /// /// Wraps in another stream, such as a GZipStream, then returns the wrapped stream diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index bf2d5a4..aa43c50 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -140,7 +140,7 @@ public static LoggerConfiguration File( /// 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 wrapping the output stream in another stream, such as a GZipStream. + /// Optionally enables hooking into log file lifecycle events. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -158,7 +158,7 @@ public static LoggerConfiguration File( bool rollOnFileSizeLimit = false, int? retainedFileCountLimit = DefaultRetainedFileCountLimit, Encoding encoding = null, - StreamWrapper wrapper = null) + FileLifecycleHooks hooks = null) { if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration)); if (path == null) throw new ArgumentNullException(nameof(path)); @@ -167,7 +167,7 @@ public static LoggerConfiguration File( var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, shared, flushToDiskInterval, - rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, wrapper); + rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, hooks); } /// @@ -176,7 +176,7 @@ public static LoggerConfiguration File( /// Logger sink configuration. /// A formatter, such as , to convert the log events into /// text for the file. If control of regular text formatting is required, use the other - /// overload of + /// overload of /// and specify the outputTemplate parameter instead. /// /// Path to the file. @@ -197,7 +197,7 @@ public static LoggerConfiguration File( /// 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 wrapping the output stream in another stream, such as a GZipStream. + /// Optionally enables hooking into log file lifecycle events. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( @@ -214,11 +214,11 @@ public static LoggerConfiguration File( bool rollOnFileSizeLimit = false, int? retainedFileCountLimit = DefaultRetainedFileCountLimit, Encoding encoding = null, - StreamWrapper wrapper = null) + FileLifecycleHooks hooks = null) { return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, - retainedFileCountLimit, wrapper); + retainedFileCountLimit, hooks); } /// @@ -293,7 +293,7 @@ static LoggerConfiguration ConfigureFile( RollingInterval rollingInterval, bool rollOnFileSizeLimit, int? retainedFileCountLimit, - StreamWrapper wrapper) + FileLifecycleHooks hooks) { if (addSink == null) throw new ArgumentNullException(nameof(addSink)); if (formatter == null) throw new ArgumentNullException(nameof(formatter)); @@ -306,7 +306,7 @@ static LoggerConfiguration ConfigureFile( if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite) { - sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, wrapper); + sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, hooks); } else { @@ -315,7 +315,7 @@ static LoggerConfiguration ConfigureFile( #pragma warning disable 618 if (shared) { - if (wrapper != null) + if (hooks != null) { SelfLog.WriteLine("Unable to use output stream wrapper - these are not supported for shared log files"); } @@ -324,7 +324,7 @@ static LoggerConfiguration ConfigureFile( } else { - sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered, wrapper: wrapper); + sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered, hooks: hooks); } #pragma warning restore 618 } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index c896034..f97817a 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -43,12 +43,12 @@ public sealed class FileSink : IFileSink, IDisposable /// 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. - /// Optionally enables wrapping the output stream in another stream, such as a GZipStream. + /// Optionally enables hooking into log file lifecycle events. /// Configuration object allowing method chaining. /// The file will be written using the UTF-8 character set. /// public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false, - StreamWrapper wrapper = null) + FileLifecycleHooks hooks = null) { if (path == null) throw new ArgumentNullException(nameof(path)); if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); @@ -70,9 +70,9 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); } - if (wrapper != null) + if (hooks != null) { - outputStream = wrapper.Wrap(outputStream); + outputStream = hooks.Wrap(outputStream); } _output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 73415b2..993239f 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -35,7 +35,7 @@ sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable readonly bool _buffered; readonly bool _shared; readonly bool _rollOnFileSizeLimit; - readonly StreamWrapper _wrapper; + readonly FileLifecycleHooks _hooks; readonly object _syncRoot = new object(); bool _isDisposed; @@ -52,7 +52,7 @@ public RollingFileSink(string path, bool shared, RollingInterval rollingInterval, bool rollOnFileSizeLimit, - StreamWrapper wrapper = null) + FileLifecycleHooks hooks = null) { if (path == null) throw new ArgumentNullException(nameof(path)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); @@ -66,7 +66,7 @@ public RollingFileSink(string path, _buffered = buffered; _shared = shared; _rollOnFileSizeLimit = rollOnFileSizeLimit; - _wrapper = wrapper; + _hooks = hooks; } public void Emit(LogEvent logEvent) @@ -147,7 +147,7 @@ void OpenFile(DateTime now, int? minSequence = null) { _currentFile = _shared ? (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : - new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _wrapper); + new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); _currentFileSequence = sequence; } catch (IOException ex) diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index 3cc83f4..e701594 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -146,14 +146,14 @@ public void WhenLimitIsNotSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppend [Fact] public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() { - var gzipWrapper = new GZipStreamWrapper(); + var gzipWrapper = new GZipHooks(); using (var tmp = TempFolder.ForCaller()) { var nonexistent = tmp.AllocateFilename("txt"); var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, wrapper: gzipWrapper)) + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, hooks: gzipWrapper)) { sink.Emit(evt); sink.Emit(evt); diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index c2521df..2e9f613 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -100,7 +100,7 @@ public void WhenSizeLimitIsBreachedNewFilesCreated() [Fact] public void WhenStreamWrapperSpecifiedIsUsedForRolledFiles() { - var gzipWrapper = new GZipStreamWrapper(); + var gzipWrapper = new GZipHooks(); var fileName = Some.String() + ".txt"; using (var temp = new TempFolder()) @@ -114,7 +114,7 @@ public void WhenStreamWrapperSpecifiedIsUsedForRolledFiles() }; using (var log = new LoggerConfiguration() - .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1, wrapper: gzipWrapper) + .WriteTo.File(Path.Combine(temp.Path, fileName), rollOnFileSizeLimit: true, fileSizeLimitBytes: 1, hooks: gzipWrapper) .CreateLogger()) { diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs similarity index 68% rename from test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs rename to test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs index 30c316c..efb2319 100644 --- a/test/Serilog.Sinks.File.Tests/Support/GZipStreamWrapper.cs +++ b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs @@ -5,13 +5,13 @@ namespace Serilog.Sinks.File.Tests.Support { /// /// - /// Demonstrates the use of , by compressing log output using GZip + /// Demonstrates the use of , by compressing log output using GZip /// - public class GZipStreamWrapper : StreamWrapper + public class GZipHooks : FileLifecycleHooks { readonly int _bufferSize; - public GZipStreamWrapper(int bufferSize = 1024 * 32) + public GZipHooks(int bufferSize = 1024 * 32) { _bufferSize = bufferSize; } From 83d817f7e3d117a68007420a25585bf2c065d19a Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Thu, 14 Feb 2019 20:56:31 +0000 Subject: [PATCH 16/38] Ignore Rider cache/options files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 483adfa..d8704ba 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ bld/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +# Rider cache/options directory +.idea + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* From 63c36013142aba3b13e7cd38ec470eb5f87aeb8a Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Thu, 14 Feb 2019 21:22:04 +0000 Subject: [PATCH 17/38] Add docs re wrapped stream ownership --- src/Serilog.Sinks.File/FileLifecycleHooks.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Serilog.Sinks.File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/FileLifecycleHooks.cs index bdfe941..3abe448 100644 --- a/src/Serilog.Sinks.File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/FileLifecycleHooks.cs @@ -9,10 +9,13 @@ namespace Serilog public abstract class FileLifecycleHooks { /// - /// Wraps in another stream, such as a GZipStream, then returns the wrapped stream + /// Wraps in another stream, such as a GZipStream, then returns the wrapped stream /// - /// The source log file stream + /// + /// Serilog is responsible for disposing of the wrapped stream + /// + /// The underlying log file stream /// The wrapped stream - public abstract Stream Wrap(Stream sourceStream); + public abstract Stream Wrap(Stream underlyingStream); } } From fca6eb93fabdedc2fcbd9dce1ff95611597ad726 Mon Sep 17 00:00:00 2001 From: BillRob Date: Tue, 12 Mar 2019 17:33:00 -0500 Subject: [PATCH 18/38] Check for directory existence before attempting access. Stops exception from being thrown and tripping breakpoints when debugging application code. --- src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 644176f..6593e68 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -117,8 +117,11 @@ void OpenFile(DateTime now, int? minSequence = null) var existingFiles = Enumerable.Empty(); try { - existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) + if (Directory.Exists(_roller.LogFileDirectory)) + { + existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) .Select(Path.GetFileName); + } } catch (DirectoryNotFoundException) { } From e604418ccaad879556a01b33aa6d34b6dac1c759 Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Sat, 20 Apr 2019 19:39:36 +0100 Subject: [PATCH 19/38] Improve log message when FileLifecycleHooks provided for a shared log file --- src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index aa43c50..c716c1d 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -317,7 +317,7 @@ static LoggerConfiguration ConfigureFile( { if (hooks != null) { - SelfLog.WriteLine("Unable to use output stream wrapper - these are not supported for shared log files"); + SelfLog.WriteLine($"Unable to use {hooks.GetType().Name} FileLifecycleHooks output stream wrapper - these are not supported for shared log files"); } sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); From 873f6d453950e33daf58a6dd38b2a0a60bfb39b6 Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Sat, 20 Apr 2019 20:32:20 +0100 Subject: [PATCH 20/38] Throw clear exception when wrapping the output stream returns null --- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index f97817a..5b167cc 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.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. @@ -73,6 +73,11 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy if (hooks != null) { outputStream = hooks.Wrap(outputStream); + + if (outputStream == null) + { + throw new InvalidOperationException($"{hooks.GetType().Name}.Wrap returned null when wrapping the output stream"); + } } _output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); From e310ab9bd2ab7648d4629b994714240be53a24d6 Mon Sep 17 00:00:00 2001 From: cocowalla <800977+cocowalla@users.noreply.github.com> Date: Sat, 20 Apr 2019 22:37:24 +0100 Subject: [PATCH 21/38] Throw when using hooks with a shared file --- src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs | 6 +----- .../FileLoggerConfigurationExtensionsTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index c716c1d..2f7ab7a 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -301,6 +301,7 @@ static LoggerConfiguration ConfigureFile( if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.", nameof(fileSizeLimitBytes)); if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); + if (shared && hooks != null) throw new ArgumentException("Unable to use {hooks.GetType().Name} FileLifecycleHooks output stream wrapper - these are not supported for shared log files", nameof(hooks)); ILogEventSink sink; @@ -315,11 +316,6 @@ static LoggerConfiguration ConfigureFile( #pragma warning disable 618 if (shared) { - if (hooks != null) - { - SelfLog.WriteLine($"Unable to use {hooks.GetType().Name} FileLifecycleHooks output stream wrapper - these are not supported for shared log files"); - } - sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); } else diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index 0515655..cfa7ad7 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Serilog.Sinks.File.Tests.Support; using Serilog.Tests.Support; @@ -80,11 +80,11 @@ public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() } [Fact] - public void BufferingIsNotAvailableWhenSharingEnabled() + public void HooksAreNotAvailableWhenSharingEnabled() { Assert.Throws(() => new LoggerConfiguration() - .WriteTo.File("logs", buffered: true, shared: true)); + .WriteTo.File("logs", shared: true, hooks: new GZipHooks())); } } } From b660b51289599f2e6c9ce83dc6fdb5e8c81f1369 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 11:01:54 +1000 Subject: [PATCH 22/38] Make FileSink constructor changes non-breaking; fix a bug whereby a specified encoding is not used; clean and tidy --- src/Serilog.Sinks.File/FileLifecycleHooks.cs | 32 ++++++++++++++----- .../FileLoggerConfigurationExtensions.cs | 10 +++--- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 32 +++++++++++-------- .../Sinks/File/RollingFileSink.cs | 11 ++++--- .../Sinks/File/SharedFileSink.AtomicAppend.cs | 8 ++--- .../Serilog.Sinks.File.Tests/FileSinkTests.cs | 6 ++-- .../Support/GZipHooks.cs | 4 +-- 7 files changed, 60 insertions(+), 43 deletions(-) diff --git a/src/Serilog.Sinks.File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/FileLifecycleHooks.cs index 3abe448..879f496 100644 --- a/src/Serilog.Sinks.File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/FileLifecycleHooks.cs @@ -1,21 +1,37 @@ +// Copyright 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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.IO; namespace Serilog { - using System.IO; - /// - /// Enables hooking into log file lifecycle events + /// Enables hooking into log file lifecycle events. /// public abstract class FileLifecycleHooks { /// - /// Wraps in another stream, such as a GZipStream, then returns the wrapped stream + /// 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. /// /// - /// Serilog is responsible for disposing of the wrapped stream + /// 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 log file stream - /// The wrapped stream - public abstract Stream Wrap(Stream underlyingStream); + /// The underlying opened on the log file. + /// The Serilog should use when writing events to the log file. + public virtual Stream OnOpened(Stream underlyingStream) => underlyingStream; } } diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 2f7ab7a..88cf2a9 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -301,7 +301,7 @@ static LoggerConfiguration ConfigureFile( if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative.", nameof(fileSizeLimitBytes)); if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); - if (shared && hooks != null) throw new ArgumentException("Unable to use {hooks.GetType().Name} FileLifecycleHooks output stream wrapper - these are not supported for shared log files", nameof(hooks)); + if (shared && hooks != null) throw new ArgumentException("File lifecycle hooks are not currently supported for shared log files.", nameof(hooks)); ILogEventSink sink; @@ -313,16 +313,16 @@ static LoggerConfiguration ConfigureFile( { try { -#pragma warning disable 618 if (shared) { - sink = new SharedFileSink(path, formatter, fileSizeLimitBytes); +#pragma warning disable 618 + sink = new SharedFileSink(path, formatter, fileSizeLimitBytes, encoding); +#pragma warning restore 618 } else { - sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered, hooks: hooks); + sink = new FileSink(path, formatter, fileSizeLimitBytes, encoding, buffered, hooks); } -#pragma warning restore 618 } catch (Exception ex) { diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 5b167cc..8d29eab 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -23,7 +23,6 @@ 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()` instead.")] public sealed class FileSink : IFileSink, IDisposable { readonly TextWriter _output; @@ -43,18 +42,27 @@ public sealed class FileSink : IFileSink, IDisposable /// 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. - /// Optionally enables hooking into log file lifecycle events. /// Configuration object allowing method chaining. - /// The file will be written using the UTF-8 character set. + /// This constructor preserves compatibility with early versions of the public API. New code should not depend on this type. /// - public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false, - FileLifecycleHooks hooks = null) + [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) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative."); - - _textFormatter = textFormatter; + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; _buffered = buffered; @@ -72,12 +80,8 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy if (hooks != null) { - outputStream = hooks.Wrap(outputStream); - - if (outputStream == null) - { - throw new InvalidOperationException($"{hooks.GetType().Name}.Wrap returned null when wrapping the output stream"); - } + outputStream = hooks.OnOpened(outputStream) ?? + throw new InvalidOperationException($"The file lifecycle hooks `{nameof(FileLifecycleHooks.OnOpened)}()` returned `null` when called with the output stream."); } _output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index bcb87f0..2db6f24 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#pragma warning disable 618 - using System; using System.IO; using System.Linq; @@ -52,11 +50,11 @@ public RollingFileSink(string path, bool shared, RollingInterval rollingInterval, bool rollOnFileSizeLimit, - FileLifecycleHooks hooks = null) + FileLifecycleHooks hooks) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); - if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("Zero or negative value provided; retained file count limit must be at least 1"); + if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative."); + if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("Zero or negative value provided; retained file count limit must be at least 1."); _roller = new PathRoller(path, rollingInterval); _textFormatter = textFormatter; @@ -149,8 +147,11 @@ void OpenFile(DateTime now, int? minSequence = null) try { _currentFile = _shared ? +#pragma warning disable 618 (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : +#pragma warning restore 618 new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); + _currentFileSequence = sequence; } catch (IOException ex) diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index 805e786..9934687 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -18,7 +18,6 @@ using System.IO; using System.Security.AccessControl; using System.Text; -using Serilog.Core; using Serilog.Events; using Serilog.Formatting; @@ -51,17 +50,14 @@ public sealed class SharedFileSink : IFileSink, IDisposable /// 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. /// public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); - _path = path; - _textFormatter = textFormatter; + _path = path ?? throw new ArgumentNullException(nameof(path)); + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; var directory = Path.GetDirectoryName(path); diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index e701594..94e0c63 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -153,7 +153,7 @@ public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() var nonexistent = tmp.AllocateFilename("txt"); var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, hooks: gzipWrapper)) + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, null, false, gzipWrapper)) { sink.Emit(evt); sink.Emit(evt); @@ -161,7 +161,7 @@ public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against // what we wrote - var lines = new List(); + List lines; using (var textStream = new MemoryStream()) { using (var fs = System.IO.File.OpenRead(nonexistent)) @@ -185,7 +185,7 @@ static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding enco { var path = tmp.AllocateFilename("txt"); var evt = Some.LogEvent("Irrelevant as it will be replaced by the formatter"); - var actualEventOutput = "x"; + const string actualEventOutput = "x"; var formatter = new FixedOutputFormatter(actualEventOutput); var eventOuputLength = encoding.GetByteCount(actualEventOutput); diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs index efb2319..330f97c 100644 --- a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs +++ b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs @@ -16,9 +16,9 @@ public GZipHooks(int bufferSize = 1024 * 32) _bufferSize = bufferSize; } - public override Stream Wrap(Stream sourceStream) + public override Stream OnOpened(Stream underlyingStream) { - var compressStream = new GZipStream(sourceStream, CompressionMode.Compress); + var compressStream = new GZipStream(underlyingStream, CompressionMode.Compress); return new BufferedStream(compressStream, _bufferSize); } } From b904968e0128ef2823b10ec52694eddc0a17d4e0 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 11:17:41 +1000 Subject: [PATCH 23/38] Reenable an old test that was lost --- .../FileLoggerConfigurationExtensionsTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index cfa7ad7..6bc6a54 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -79,6 +79,14 @@ public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() } } + [Fact] + public void BufferingIsNotAvailableWhenSharingEnabled() + { + Assert.Throws(() => + new LoggerConfiguration() + .WriteTo.File("logs", buffered: true, shared: true)); + } + [Fact] public void HooksAreNotAvailableWhenSharingEnabled() { From b6090cc359df26b46bf65b9b23065ed9a9ed7c32 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 11:32:48 +1000 Subject: [PATCH 24/38] A test for the encoding fix --- .../FileLoggerConfigurationExtensionsTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index 6bc6a54..3dde37a 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -4,6 +4,7 @@ using Serilog.Tests.Support; using Xunit; using System.IO; +using System.Text; namespace Serilog.Sinks.File.Tests { @@ -94,5 +95,26 @@ public void HooksAreNotAvailableWhenSharingEnabled() new LoggerConfiguration() .WriteTo.File("logs", shared: true, hooks: new GZipHooks())); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpecifiedEncodingIsPropagated(bool shared) + { + using (var tmp = TempFolder.ForCaller()) + { + 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); + } + } } } From 34344ad386abc568f4691bc4094891bb1b3b9823 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 11:52:51 +1000 Subject: [PATCH 25/38] Move FileLifecycleHooks down under Serilog.Sinks.File - avoids namespace pollution when using Serilog logging APIs; mark PeriodicFlushToDiskSink as obsolete in line with other public types shared with Serilog.Sinks.RollingFile --- .../FileLoggerConfigurationExtensions.cs | 2 ++ .../{ => Sinks/File}/FileLifecycleHooks.cs | 2 +- .../Sinks/File/PeriodicFlushToDiskSink.cs | 26 ++++++++++++++----- .../Sinks/File/SharedFileSink.AtomicAppend.cs | 2 +- .../Sinks/File/SharedFileSink.OSMutex.cs | 8 +++--- .../Sinks/File/WriteCountingStream.cs | 18 ++++++------- 6 files changed, 34 insertions(+), 24 deletions(-) rename src/Serilog.Sinks.File/{ => Sinks/File}/FileLifecycleHooks.cs (98%) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 88cf2a9..944d32e 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -337,7 +337,9 @@ static LoggerConfiguration ConfigureFile( if (flushToDiskInterval.HasValue) { +#pragma warning disable 618 sink = new PeriodicFlushToDiskSink(sink, flushToDiskInterval.Value); +#pragma warning restore 618 } return addSink(sink, restrictedToMinimumLevel, levelSwitch); diff --git a/src/Serilog.Sinks.File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs similarity index 98% rename from src/Serilog.Sinks.File/FileLifecycleHooks.cs rename to src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index 879f496..d5e2219 100644 --- a/src/Serilog.Sinks.File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -14,7 +14,7 @@ using System.IO; -namespace Serilog +namespace Serilog.Sinks.File { /// /// Enables hooking into log file lifecycle events. diff --git a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs index cafb72e..66b0868 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PeriodicFlushToDiskSink.cs @@ -1,4 +1,18 @@ -using System; +// Copyright 2016-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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// 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; using System.Threading; using Serilog.Core; using Serilog.Debugging; @@ -9,6 +23,7 @@ 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; @@ -21,15 +36,12 @@ public class PeriodicFlushToDiskSink : ILogEventSink, IDisposable /// /// The sink to wrap. /// The interval at which to flush the underlying sink. - /// + /// public PeriodicFlushToDiskSink(ILogEventSink sink, TimeSpan flushInterval) { - if (sink == null) throw new ArgumentNullException(nameof(sink)); - - _sink = sink; + _sink = sink ?? throw new ArgumentNullException(nameof(sink)); - var flushable = sink as IFlushableFileSink; - if (flushable != null) + if (sink is IFlushableFileSink flushable) { _timer = new Timer(_ => FlushToDisk(flushable), null, flushInterval, flushInterval); } diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index 9934687..866f807 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2016 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. diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs index a779bda..41a19ef 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-2016 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. @@ -17,7 +17,6 @@ using System; using System.IO; using System.Text; -using Serilog.Core; using Serilog.Events; using Serilog.Formatting; using System.Threading; @@ -28,6 +27,7 @@ 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 { readonly TextWriter _output; @@ -53,11 +53,9 @@ public sealed class SharedFileSink : IFileSink, IDisposable public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter)); if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative"); - - _textFormatter = textFormatter; + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; var directory = Path.GetDirectoryName(path); diff --git a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs index da8f0dd..fe0d5d3 100644 --- a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs +++ b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs @@ -1,4 +1,4 @@ -// Copyright 2013-2016 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. @@ -20,16 +20,14 @@ namespace Serilog.Sinks.File sealed class WriteCountingStream : Stream { readonly Stream _stream; - long _countedLength; public WriteCountingStream(Stream stream) { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - _stream = stream; - _countedLength = stream.Length; + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + CountedLength = stream.Length; } - public long CountedLength => _countedLength; + public long CountedLength { get; private set; } protected override void Dispose(bool disposing) { @@ -42,7 +40,7 @@ protected override void Dispose(bool disposing) public override void Write(byte[] buffer, int offset, int count) { _stream.Write(buffer, offset, count); - _countedLength += count; + CountedLength += count; } public override void Flush() => _stream.Flush(); @@ -54,8 +52,8 @@ public override void Write(byte[] buffer, int offset, int count) public override long Position { - get { return _stream.Position; } - set { throw new NotSupportedException(); } + get => _stream.Position; + set => throw new NotSupportedException(); } public override long Seek(long offset, SeekOrigin origin) @@ -73,4 +71,4 @@ public override int Read(byte[] buffer, int offset, int count) throw new NotSupportedException(); } } -} \ No newline at end of file +} From 0ae234e2e9ed044733fe70d7d573fc64dd769eb0 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 12:05:04 +1000 Subject: [PATCH 26/38] Enable hooks and encoding for auditing methods --- .../FileLoggerConfigurationExtensions.cs | 81 +++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 944d32e..2a66a79 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -199,7 +199,6 @@ public static LoggerConfiguration File( /// 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. - /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( this LoggerSinkConfiguration sinkConfiguration, ITextFormatter formatter, @@ -235,20 +234,54 @@ public static LoggerConfiguration File( /// 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. + [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) + { + 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, 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}". + /// 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. public static LoggerConfiguration File( this LoggerAuditSinkConfiguration sinkConfiguration, string path, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, string outputTemplate = DefaultOutputTemplate, IFormatProvider formatProvider = null, - LoggingLevelSwitch levelSwitch = 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); + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, encoding, hooks); } /// @@ -267,15 +300,53 @@ public static LoggerConfiguration File( /// to be changed at runtime. /// 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 LoggerAuditSinkConfiguration sinkConfiguration, + ITextFormatter formatter, + string path, + LogEventLevel restrictedToMinimumLevel, + LoggingLevelSwitch levelSwitch) + { + 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 File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, 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. + /// 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. public static LoggerConfiguration File( this LoggerAuditSinkConfiguration sinkConfiguration, ITextFormatter formatter, string path, LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - LoggingLevelSwitch levelSwitch = null) + 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, null, RollingInterval.Infinite, false, null, null); + false, null, encoding, RollingInterval.Infinite, false, null, hooks); } static LoggerConfiguration ConfigureFile( From 55fcb2b32ece5f765b27dccfe1c6609c1415a34e Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 12:26:48 +1000 Subject: [PATCH 27/38] Add backwards-compatible configuration overloads --- .../FileLoggerConfigurationExtensions.cs | 176 +++++++++++++----- 1 file changed, 131 insertions(+), 45 deletions(-) diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 2a66a79..490803d 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -24,7 +24,7 @@ using Serilog.Formatting.Json; using Serilog.Sinks.File; -// ReSharper disable MethodOverloadWithOptionalParameter +// ReSharper disable RedundantArgumentDefaultValue, MethodOverloadWithOptionalParameter namespace Serilog { @@ -69,10 +69,8 @@ public static LoggerConfiguration File( bool shared, TimeSpan? flushToDiskInterval) { - // ReSharper disable once RedundantArgumentDefaultValue return File(sinkConfiguration, path, restrictedToMinimumLevel, outputTemplate, formatProvider, fileSizeLimitBytes, - levelSwitch, buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, - null, null); + levelSwitch, buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null, null); } /// @@ -110,9 +108,103 @@ public static LoggerConfiguration File( bool shared, TimeSpan? flushToDiskInterval) { - // ReSharper disable once RedundantArgumentDefaultValue return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch, - buffered, shared, flushToDiskInterval, RollingInterval.Infinite, false, null, null); + 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. + /// 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); } /// @@ -142,7 +234,6 @@ public static LoggerConfiguration File( /// 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. - /// The file will be written using the UTF-8 character set. public static LoggerConfiguration File( this LoggerSinkConfiguration sinkConfiguration, string path, @@ -215,6 +306,10 @@ public static LoggerConfiguration File( 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, fileSizeLimitBytes, levelSwitch, buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, hooks); @@ -243,80 +338,71 @@ public static LoggerConfiguration File( IFormatProvider formatProvider, LoggingLevelSwitch levelSwitch) { - 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, null); + 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. - /// 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. + /// 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 LoggerAuditSinkConfiguration sinkConfiguration, + ITextFormatter formatter, string path, - LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, - string outputTemplate = DefaultOutputTemplate, - IFormatProvider formatProvider = null, - LoggingLevelSwitch levelSwitch = null, - Encoding encoding = null, - FileLifecycleHooks hooks = null) + LogEventLevel restrictedToMinimumLevel, + LoggingLevelSwitch levelSwitch) { - 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); + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, null, null); } - + /// - /// Write log events to the specified file. + /// 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. + /// 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. - /// 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 LoggerAuditSinkConfiguration sinkConfiguration, - ITextFormatter formatter, string path, - LogEventLevel restrictedToMinimumLevel, - LoggingLevelSwitch levelSwitch) + 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 (formatter == null) throw new ArgumentNullException(nameof(formatter)); if (path == null) throw new ArgumentNullException(nameof(path)); + if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate)); - return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, null); + var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider); + return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, encoding, hooks); } /// - /// Write log events to the specified file. + /// Write audit log events to the specified file. /// /// Logger sink configuration. /// A formatter, such as , to convert the log events into From d4fa80a83ffad45781b79d416e9bf0df2d3f8a55 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 13:32:19 +1000 Subject: [PATCH 28/38] Expose encoding to OnOpened() hook; switch to AppVeyor Linux builds --- README.md | 2 +- appveyor.yml | 13 ++++++++++--- build.sh | 10 ++++++++-- serilog-sinks-file.sln | 1 - .../Sinks/File/FileLifecycleHooks.cs | 4 +++- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 9 ++++++--- test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs | 3 ++- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9e4f19f..4267d32 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Serilog.Sinks.File [![Build status](https://ci.appveyor.com/api/projects/status/hh9gymy0n6tne46j?svg=true)](https://ci.appveyor.com/project/serilog/serilog-sinks-file) [![Travis build](https://travis-ci.org/serilog/serilog-sinks-file.svg)](https://travis-ci.org/serilog/serilog-sinks-file) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.File.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.File/) [![Documentation](https://img.shields.io/badge/docs-wiki-yellow.svg)](https://github.com/serilog/serilog/wiki) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) +# Serilog.Sinks.File [![Build status](https://ci.appveyor.com/api/projects/status/hh9gymy0n6tne46j?svg=true)](https://ci.appveyor.com/project/serilog/serilog-sinks-file) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Sinks.File.svg?style=flat)](https://www.nuget.org/packages/Serilog.Sinks.File/) [![Documentation](https://img.shields.io/badge/docs-wiki-yellow.svg)](https://github.com/serilog/serilog/wiki) [![Join the chat at https://gitter.im/serilog/serilog](https://img.shields.io/gitter/room/serilog/serilog.svg)](https://gitter.im/serilog/serilog) Writes [Serilog](https://serilog.net) events to one or more text files. diff --git a/appveyor.yml b/appveyor.yml index 71f05e3..67a6f2b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,18 @@ version: '{build}' skip_tags: true -image: Visual Studio 2017 +image: + - Visual Studio 2017 + - Ubuntu configuration: Release -install: - - ps: mkdir -Force ".\build\" | Out-Null build_script: - ps: ./Build.ps1 +for: +- + matrix: + only: + - image: Ubuntu + build_script: + - sh build.sh test: off artifacts: - path: artifacts/Serilog.*.nupkg diff --git a/build.sh b/build.sh index 56d265b..bd380e7 100755 --- a/build.sh +++ b/build.sh @@ -1,11 +1,17 @@ -#!/bin/bash +#!/bin/bash + +set -e dotnet --info +dotnet --list-sdks dotnet restore +echo "🤖 Attempting to build..." for path in src/**/*.csproj; do - dotnet build -f netstandard1.3 -c Release ${path} -p:EnableSourceLink=true + dotnet build -f netstandard1.0 -c Release ${path} + dotnet build -f netstandard1.3 -c Release ${path} done +echo "🤖 Running tests..." for path in test/*.Tests/*.csproj; do dotnet test -f netcoreapp2.0 -c Release ${path} done diff --git a/serilog-sinks-file.sln b/serilog-sinks-file.sln index 71527e4..9c33a2b 100644 --- a/serilog-sinks-file.sln +++ b/serilog-sinks-file.sln @@ -8,7 +8,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{E9D1B5E1-DEB9-4A04-8BAB-24EC7240ADAF}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - .travis.yml = .travis.yml appveyor.yml = appveyor.yml Build.ps1 = Build.ps1 build.sh = build.sh diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index d5e2219..cae8538 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.IO; +using System.Text; namespace Serilog.Sinks.File { @@ -31,7 +32,8 @@ public abstract class FileLifecycleHooks /// 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 OnOpened(Stream underlyingStream) => underlyingStream; + public virtual Stream OnOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 8d29eab..0b0e497 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -78,13 +78,16 @@ internal FileSink( outputStream = _countingStreamWrapper = new WriteCountingStream(_underlyingStream); } + // Parameter reassignment. + encoding = encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + if (hooks != null) { - outputStream = hooks.OnOpened(outputStream) ?? - throw new InvalidOperationException($"The file lifecycle hooks `{nameof(FileLifecycleHooks.OnOpened)}()` returned `null` when called with the output stream."); + outputStream = hooks.OnOpened(outputStream, encoding) ?? + throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnOpened)}(...)` returned `null`."); } - _output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + _output = new StreamWriter(outputStream, encoding); } bool IFileSink.EmitOrOverflow(LogEvent logEvent) diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs index 330f97c..64f57e1 100644 --- a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs +++ b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs @@ -1,5 +1,6 @@ using System.IO; using System.IO.Compression; +using System.Text; namespace Serilog.Sinks.File.Tests.Support { @@ -16,7 +17,7 @@ public GZipHooks(int bufferSize = 1024 * 32) _bufferSize = bufferSize; } - public override Stream OnOpened(Stream underlyingStream) + public override Stream OnOpened(Stream underlyingStream, Encoding _) { var compressStream = new GZipStream(underlyingStream, CompressionMode.Compress); return new BufferedStream(compressStream, _bufferSize); From 9f7352d7da81c1f84974e90b59766e3edfd9bd69 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 13:37:41 +1000 Subject: [PATCH 29/38] Fix Linux build script targets --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index bd380e7..3bb5fec 100755 --- a/build.sh +++ b/build.sh @@ -7,8 +7,8 @@ dotnet restore echo "🤖 Attempting to build..." for path in src/**/*.csproj; do - dotnet build -f netstandard1.0 -c Release ${path} dotnet build -f netstandard1.3 -c Release ${path} + dotnet build -f netstandard2.0 -c Release ${path} done echo "🤖 Running tests..." From ca0ac8c9f56d722f29c8dd512fb342c138b819fb Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 14:00:00 +1000 Subject: [PATCH 30/38] Test for header writing --- .../Serilog.Sinks.File.Tests/FileSinkTests.cs | 35 ++++++++++++++++--- .../Support/FileHeaderWriter.cs | 28 +++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index 94e0c63..2d0f210 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -144,16 +144,16 @@ public void WhenLimitIsNotSpecifiedAndEncodingHasNoPreambleDataIsCorrectlyAppend } [Fact] - public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() + public void OnOpenedLifecycleHookCanWrapUnderlyingStream() { var gzipWrapper = new GZipHooks(); using (var tmp = TempFolder.ForCaller()) { - var nonexistent = tmp.AllocateFilename("txt"); + var path = tmp.AllocateFilename("txt"); var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(nonexistent, new JsonFormatter(), null, null, false, gzipWrapper)) + using (var sink = new FileSink(path, new JsonFormatter(), null, null, false, gzipWrapper)) { sink.Emit(evt); sink.Emit(evt); @@ -164,7 +164,7 @@ public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() List lines; using (var textStream = new MemoryStream()) { - using (var fs = System.IO.File.OpenRead(nonexistent)) + using (var fs = System.IO.File.OpenRead(path)) using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) { decompressStream.CopyTo(textStream); @@ -179,6 +179,33 @@ public void WhenStreamWrapperIsSpecifiedOutputStreamIsWrapped() } } + [Fact] + public static void OnOpenedLifecycleHookCanWriteFileHeader() + { + using (var tmp = TempFolder.ForCaller()) + { + 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 + } + + 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); + + Assert.Equal(2, lines.Length); + Assert.Equal(headerWriter.Header, lines[0]); + Assert.Equal('{', lines[1][0]); + } + } + static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) { using (var tmp = TempFolder.ForCaller()) diff --git a/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs new file mode 100644 index 0000000..83937c0 --- /dev/null +++ b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs @@ -0,0 +1,28 @@ +using System.IO; +using System.Text; + +namespace Serilog.Sinks.File.Tests.Support +{ + class FileHeaderWriter : FileLifecycleHooks + { + public string Header { get; } + + public FileHeaderWriter(string header) + { + Header = header; + } + + public override Stream OnOpened(Stream underlyingStream, Encoding encoding) + { + if (underlyingStream.Length == 0) + { + var writer = new StreamWriter(underlyingStream, encoding); + writer.WriteLine(Header); + writer.Flush(); + underlyingStream.Flush(); + } + + return base.OnOpened(underlyingStream, encoding); + } + } +} From 5b3d64aac5af752476a087c2f7d3e3e553dadb36 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 22 Apr 2019 20:21:42 +1000 Subject: [PATCH 31/38] OnOpened() -> OnFileOpened() --- src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs | 2 +- src/Serilog.Sinks.File/Sinks/File/FileSink.cs | 4 ++-- test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs | 4 ++-- test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index cae8538..3dbddeb 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -34,6 +34,6 @@ public abstract class FileLifecycleHooks /// 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 OnOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; + public virtual Stream OnFileOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; } } diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 0b0e497..8a913fa 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -83,8 +83,8 @@ internal FileSink( if (hooks != null) { - outputStream = hooks.OnOpened(outputStream, encoding) ?? - throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnOpened)}(...)` returned `null`."); + outputStream = hooks.OnFileOpened(outputStream, encoding) ?? + throw new InvalidOperationException($"The file lifecycle hook `{nameof(FileLifecycleHooks.OnFileOpened)}(...)` returned `null`."); } _output = new StreamWriter(outputStream, encoding); diff --git a/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs index 83937c0..ae90604 100644 --- a/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs +++ b/test/Serilog.Sinks.File.Tests/Support/FileHeaderWriter.cs @@ -12,7 +12,7 @@ public FileHeaderWriter(string header) Header = header; } - public override Stream OnOpened(Stream underlyingStream, Encoding encoding) + public override Stream OnFileOpened(Stream underlyingStream, Encoding encoding) { if (underlyingStream.Length == 0) { @@ -22,7 +22,7 @@ public override Stream OnOpened(Stream underlyingStream, Encoding encoding) underlyingStream.Flush(); } - return base.OnOpened(underlyingStream, encoding); + return base.OnFileOpened(underlyingStream, encoding); } } } diff --git a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs index 64f57e1..40a77bb 100644 --- a/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs +++ b/test/Serilog.Sinks.File.Tests/Support/GZipHooks.cs @@ -17,7 +17,7 @@ public GZipHooks(int bufferSize = 1024 * 32) _bufferSize = bufferSize; } - public override Stream OnOpened(Stream underlyingStream, Encoding _) + public override Stream OnFileOpened(Stream underlyingStream, Encoding _) { var compressStream = new GZipStream(underlyingStream, CompressionMode.Compress); return new BufferedStream(compressStream, _bufferSize); From e420a360b1744373eb09c68925246e9932b25dad Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 23 Apr 2019 08:18:54 +1000 Subject: [PATCH 32/38] Small clarification of size limiting behavior [skip ci] --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4267d32..5a78565 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,13 @@ log20180702.txt ### Limits -To avoid bringing down apps with runaway disk usage the file sink **limits file size to 1GB by default**. The limit can be increased or removed using the `fileSizeLimitBytes` parameter. +To avoid bringing down apps with runaway disk usage the file sink **limits file size to 1GB by default**. Once the limit is reached, no further events will be written until the next roll point (see also: [Rolling policies](#rolling-policies) below). + +The limit can be changed or removed using the `fileSizeLimitBytes` parameter. ```csharp .WriteTo.File("log.txt", fileSizeLimitBytes: null) -``` +``` For the same reason, only **the most recent 31 files** are retained by default (i.e. one long month). To change or remove this limit, pass the `retainedFileCountLimit` parameter. From 9f8bfc3016f74729e3f5be7d5ffd4c9978475684 Mon Sep 17 00:00:00 2001 From: Matthew Erbs Date: Fri, 3 May 2019 07:52:19 +1000 Subject: [PATCH 33/38] Update NuGet API Key --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 67a6f2b..79ee987 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,7 +19,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: bd9z4P73oltOXudAjPehwp9iDKsPtC+HbgshOrSgoyQKr5xVK+bxJQngrDJkHdY8 + secure: N59tiJECUYpip6tEn0xvdmDAEiP9SIzyLEFLpwiigm/8WhJvBNs13QxzT1/3/JW/ skip_symbols: true on: branch: /^(master|dev)$/ From 57d96c2c263aff760997fc68fc3e2e4c3358a923 Mon Sep 17 00:00:00 2001 From: Marcin_Osada Date: Mon, 8 Jul 2019 13:14:19 +0200 Subject: [PATCH 34/38] Add OnFileRemoving life cycle hook #106 --- .../Sinks/File/FileLifecycleHooks.cs | 14 ++++++++++++++ .../Sinks/File/RollingFileSink.cs | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index 3dbddeb..bda4c63 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -35,5 +35,19 @@ public abstract class FileLifecycleHooks /// 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; + + /// + /// Method called on log files that were marked as obsolete (old) and by default would be deleted. + /// This can be used to move old logs to archive location or send to backup server + /// + /// + /// Executing long synchronous operation may affect responsiveness of application + /// + /// Log file full path + /// + /// Return if Serilog should delete file. + /// Warning: returning false and keeping file in same place will result in calling this method again in next scan for obsolete files + /// + public virtual bool OnFileRemoving(string fullPath) => true; } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 2db6f24..fd81a54 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -199,11 +199,12 @@ void ApplyRetentionPolicy(string currentFilePath) var fullPath = Path.Combine(_roller.LogFileDirectory, obsolete); try { - System.IO.File.Delete(fullPath); + if (_hooks == null || _hooks.OnFileRemoving(fullPath)) + System.IO.File.Delete(fullPath); } catch (Exception ex) { - SelfLog.WriteLine("Error {0} while removing obsolete log file {1}", ex, fullPath); + SelfLog.WriteLine("Error {0} while processing obsolete log file {1}", ex, fullPath); } } } From 4f1ae37474c229b016d1858f075797a39694bdf1 Mon Sep 17 00:00:00 2001 From: Marcin_Osada Date: Wed, 10 Jul 2019 10:54:22 +0200 Subject: [PATCH 35/38] OnFileRemoving will not interfere with Serilog delete --- .../Sinks/File/FileLifecycleHooks.cs | 10 +++------- src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs | 5 +++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index bda4c63..5949219 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -37,17 +37,13 @@ public abstract class FileLifecycleHooks public virtual Stream OnFileOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; /// - /// Method called on log files that were marked as obsolete (old) and by default would be deleted. - /// This can be used to move old logs to archive location or send to backup server + /// Method called on log files that were marked as obsolete (old) and would be deleted. + /// This can be used to copy old logs to archive location or send to backup server /// /// /// Executing long synchronous operation may affect responsiveness of application /// /// Log file full path - /// - /// Return if Serilog should delete file. - /// Warning: returning false and keeping file in same place will result in calling this method again in next scan for obsolete files - /// - public virtual bool OnFileRemoving(string fullPath) => true; + public virtual void OnFileRemoving(string fullPath) {} } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index fd81a54..3b0ac61 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -199,8 +199,9 @@ void ApplyRetentionPolicy(string currentFilePath) var fullPath = Path.Combine(_roller.LogFileDirectory, obsolete); try { - if (_hooks == null || _hooks.OnFileRemoving(fullPath)) - System.IO.File.Delete(fullPath); + if (_hooks != null) _hooks.OnFileRemoving(fullPath); + + System.IO.File.Delete(fullPath); } catch (Exception ex) { From 735efa9699035e979c73c3f4970ae0ab41ebabd7 Mon Sep 17 00:00:00 2001 From: Marcin_Osada Date: Fri, 12 Jul 2019 12:01:11 +0200 Subject: [PATCH 36/38] Update naming and summaries --- .../Sinks/File/FileLifecycleHooks.cs | 12 +++++------- src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs | 3 +-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index 5949219..fbaf133 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs @@ -19,6 +19,7 @@ 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 { @@ -37,13 +38,10 @@ public abstract class FileLifecycleHooks public virtual Stream OnFileOpened(Stream underlyingStream, Encoding encoding) => underlyingStream; /// - /// Method called on log files that were marked as obsolete (old) and would be deleted. - /// This can be used to copy old logs to archive location or send to backup server + /// 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. /// - /// - /// Executing long synchronous operation may affect responsiveness of application - /// - /// Log file full path - public virtual void OnFileRemoving(string fullPath) {} + /// The full path to the file being deleted. + public virtual void OnFileDeleting(string path) {} } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 3b0ac61..43e5fad 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -199,8 +199,7 @@ void ApplyRetentionPolicy(string currentFilePath) var fullPath = Path.Combine(_roller.LogFileDirectory, obsolete); try { - if (_hooks != null) _hooks.OnFileRemoving(fullPath); - + _hooks?.OnFileDeleting(fullPath); System.IO.File.Delete(fullPath); } catch (Exception ex) From f6d1cffa6bd69649d76d366a92e1647dbde8745f Mon Sep 17 00:00:00 2001 From: Marcin_Osada Date: Mon, 15 Jul 2019 17:05:31 +0200 Subject: [PATCH 37/38] Add test --- .../RollingFileSinkTests.cs | 22 ++++++++++++ .../Support/ArchiveOldLogsHook.cs | 35 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs diff --git a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs index 2e9f613..70408b3 100644 --- a/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingFileSinkTests.cs @@ -71,6 +71,28 @@ public void WhenRetentionCountIsSetOldFilesAreDeleted() }); } + [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, rollingInterval: RollingInterval.Day, hooks: new ArchiveOldLogsHook(archiveDirectory)), + 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])); + + Assert.True(System.IO.File.Exists(ArchiveOldLogsHook.AddTopDirectory(files[0], archiveDirectory))); + }); + } + [Fact] public void WhenSizeLimitIsBreachedNewFilesCreated() { diff --git a/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs new file mode 100644 index 0000000..96dcb8c --- /dev/null +++ b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Text; + +namespace Serilog.Sinks.File.Tests.Support +{ + internal class ArchiveOldLogsHook : FileLifecycleHooks + { + private readonly string _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 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); + + if (createOnNonExist && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + return Path.Combine(directory, file); + } + } +} From 1213d13b3b8c77ca5837ec64cbad774901acdef6 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 24 Aug 2019 23:11:40 +1000 Subject: [PATCH 38/38] compile the regex --- src/Serilog.Sinks.File/Sinks/File/PathRoller.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs index 17c496b..79a6915 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs @@ -52,7 +52,8 @@ public PathRoller(string path, RollingInterval interval) "(?<" + PeriodMatchGroup + ">\\d{" + _periodFormat.Length + "})" + "(?<" + SequenceNumberMatchGroup + ">_[0-9]{3,}){0,1}" + Regex.Escape(_filenameSuffix) + - "$"); + "$", + RegexOptions.Compiled); DirectorySearchPattern = $"{_filenamePrefix}*{_filenameSuffix}"; }