diff --git a/Build.ps1 b/Build.ps1 index e0eebae..06a36af 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,46 +1,42 @@ -echo "build: Build started" +Write-Output "build: Build started" Push-Location $PSScriptRoot if(Test-Path .\artifacts) { - echo "build: Cleaning .\artifacts" - Remove-Item .\artifacts -Force -Recurse + Write-Output "build: Cleaning ./artifacts" + Remove-Item ./artifacts -Force -Recurse } & dotnet restore --no-cache -$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL]; -$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL]; +$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:APPVEYOR_REPO_BRANCH]; +$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:APPVEYOR_BUILD_NUMBER]; $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"] -$commitHash = $(git rev-parse --short HEAD) -$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""] -echo "build: Package version suffix is $suffix" -echo "build: Build version suffix is $buildSuffix" +Write-Output "build: Package version suffix is $suffix" -foreach ($src in ls src/*) { +foreach ($src in Get-ChildItem src/*) { Push-Location $src - echo "build: Packaging project in $src" + Write-Output "build: Packaging project in $src" - & dotnet build -c Release --version-suffix=$buildSuffix -p:EnableSourceLink=true if ($suffix) { - & dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --no-build + & dotnet pack -c Release --include-source -o ../../artifacts --version-suffix=$suffix } else { - & dotnet pack -c Release -o ..\..\artifacts --no-build + & dotnet pack -c Release --include-source -o ../../artifacts } - if($LASTEXITCODE -ne 0) { exit 1 } + if($LASTEXITCODE -ne 0) { throw "Packaging failed" } Pop-Location } -foreach ($test in ls test/*.Tests) { +foreach ($test in Get-ChildItem test/*.Tests) { Push-Location $test - echo "build: Testing project in $test" + Write-Output "build: Testing project in $test" & dotnet test -c Release - if($LASTEXITCODE -ne 0) { exit 3 } + if($LASTEXITCODE -ne 0) { throw "Testing failed" } Pop-Location } diff --git a/Directory.Build.props b/Directory.Build.props index d54992d..0248539 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ $(MSBuildThisFileDirectory)assets/Serilog.snk enable enable + latest diff --git a/README.md b/README.md index bdc3654..1ac6ed7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Writes [Serilog](https://serilog.net) events to one or more text files. Install the [Serilog.Sinks.File](https://www.nuget.org/packages/Serilog.Sinks.File/) package from NuGet: ```powershell -Install-Package Serilog.Sinks.File +dotnet add package Serilog.Sinks.File ``` To configure the sink in C# code, call `WriteTo.File()` during logger configuration: @@ -36,7 +36,7 @@ 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. diff --git a/appveyor.yml b/appveyor.yml index c15a72e..42f5a75 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,9 +2,9 @@ version: '{build}' skip_tags: true image: - Visual Studio 2022 - - Ubuntu + - Ubuntu2204 build_script: -- ps: ./Build.ps1 +- pwsh: ./Build.ps1 for: - matrix: @@ -19,7 +19,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: LE+O+3Zs0nz2F/+M4eDvKBhEBUpUV0t864vN/2dxwa7aIVqeU3pKSMjWRX+JWJ49 + secure: sDnchSg4TZIOK7oIUI6BJwFPNENTOZrGNsroGO1hehLJSvlHpFmpTwiX8+bgPD+Q on: branch: /^(main|dev)$/ - provider: GitHub diff --git a/example/Sample/Program.cs b/example/Sample/Program.cs index d980bd8..660ca72 100644 --- a/example/Sample/Program.cs +++ b/example/Sample/Program.cs @@ -1,35 +1,27 @@ using Serilog; using Serilog.Debugging; -namespace Sample; +SelfLog.Enable(Console.Out); -public class Program -{ - public static void Main(string[] args) - { - SelfLog.Enable(Console.Out); - - var sw = System.Diagnostics.Stopwatch.StartNew(); +var sw = System.Diagnostics.Stopwatch.StartNew(); - Log.Logger = new LoggerConfiguration() - .WriteTo.File("log.txt") - .CreateLogger(); +Log.Logger = new LoggerConfiguration() + .WriteTo.File("log.txt") + .CreateLogger(); - for (var i = 0; i < 1000000; ++i) - { - Log.Information("Hello, file logger!"); - } +for (var i = 0; i < 1000000; ++i) +{ + Log.Information("Hello, file logger!"); +} - Log.CloseAndFlush(); +Log.CloseAndFlush(); - sw.Stop(); +sw.Stop(); - Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms"); - Console.WriteLine($"Size: {new FileInfo("log.txt").Length}"); +Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms"); +Console.WriteLine($"Size: {new FileInfo("log.txt").Length}"); - Console.WriteLine("Press any key to delete the temporary log file..."); - Console.ReadKey(true); +Console.WriteLine("Press any key to delete the temporary log file..."); +Console.ReadKey(true); - File.Delete("log.txt"); - } -} +File.Delete("log.txt"); diff --git a/example/Sample/Sample.csproj b/example/Sample/Sample.csproj index a05ad36..fa9016e 100644 --- a/example/Sample/Sample.csproj +++ b/example/Sample/Sample.csproj @@ -1,7 +1,7 @@ - net48;net6.0 + net48;net8.0 Exe true diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs index 42e21cb..5440792 100644 --- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs +++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs @@ -518,8 +518,8 @@ static LoggerConfiguration ConfigureFile( if (addSink == null) throw new ArgumentNullException(nameof(addSink)); if (formatter == null) throw new ArgumentNullException(nameof(formatter)); if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null.", nameof(fileSizeLimitBytes)); - if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); + if (fileSizeLimitBytes is < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null.", nameof(fileSizeLimitBytes)); + if (retainedFileCountLimit is < 1) throw new ArgumentException("At least one file must be retained.", nameof(retainedFileCountLimit)); if (retainedFileTimeLimit.HasValue && retainedFileTimeLimit < TimeSpan.Zero) throw new ArgumentException("Negative value provided; retained file time limit must be non-negative.", nameof(retainedFileTimeLimit)); if (shared && buffered) throw new ArgumentException("Buffered writes are not available when file sharing is enabled.", nameof(buffered)); if (shared && hooks != null) throw new ArgumentException("File lifecycle hooks are not currently supported for shared log files.", nameof(hooks)); diff --git a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj index e9edc97..a7629b8 100644 --- a/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj +++ b/src/Serilog.Sinks.File/Serilog.Sinks.File.csproj @@ -2,42 +2,53 @@ Write Serilog events to text files in plain or JSON format. - 5.0.1 + 6.0.0 Serilog Contributors - net45;netstandard1.3;netstandard2.0;netstandard2.1;net5.0;net6.0 + + net471;net462 + + $(TargetFrameworks);net8.0;net6.0;netstandard2.0 true serilog;file - images\icon.png + serilog-sink-nuget.png https://serilog.net/images/serilog-sink-nuget.png https://github.com/serilog/serilog-sinks-file Apache-2.0 https://github.com/serilog/serilog-sinks-file git Serilog - true - false true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb True snupkg + README.md - - + - + $(DefineConstants);ATOMIC_APPEND;HRESULTS - + $(DefineConstants);OS_MUTEX + + $(DefineConstants);ENUMERABLE_MAXBY + + + + $(DefineConstants);ENUMERABLE_MAXBY + + - + + diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs index 2c3034c..b7eb06e 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileLifeCycleHookChain.cs @@ -16,10 +16,10 @@ namespace Serilog.Sinks.File; -class FileLifeCycleHookChain : FileLifecycleHooks +sealed class FileLifeCycleHookChain : FileLifecycleHooks { - private readonly FileLifecycleHooks _first; - private readonly FileLifecycleHooks _second; + readonly FileLifecycleHooks _first; + readonly FileLifecycleHooks _second; public FileLifeCycleHookChain(FileLifecycleHooks first, FileLifecycleHooks second) { diff --git a/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs b/src/Serilog.Sinks.File/Sinks/File/FileLifecycleHooks.cs index ac6df00..d83926a 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.Text; +// ReSharper disable UnusedMember.Global namespace Serilog.Sinks.File; diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs index 9e39892..a246b66 100644 --- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs @@ -67,7 +67,7 @@ internal FileSink( FileLifecycleHooks? hooks) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); + if (fileSizeLimitBytes is < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; _buffered = buffered; diff --git a/src/Serilog.Sinks.File/Sinks/File/NullSink.cs b/src/Serilog.Sinks.File/Sinks/File/NullSink.cs index 8992197..386bab0 100644 --- a/src/Serilog.Sinks.File/Sinks/File/NullSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/NullSink.cs @@ -21,9 +21,9 @@ namespace Serilog.Sinks.File; /// An instance of this sink may be substituted when an instance of the /// is unable to be constructed. /// -class NullSink : ILogEventSink +sealed class NullSink : ILogEventSink { public void Emit(LogEvent logEvent) { } -} \ No newline at end of file +} diff --git a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs index 922682d..e6773eb 100644 --- a/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs +++ b/src/Serilog.Sinks.File/Sinks/File/PathRoller.cs @@ -17,7 +17,7 @@ namespace Serilog.Sinks.File; -class PathRoller +sealed class PathRoller { const string PeriodMatchGroup = "period"; const string SequenceNumberMatchGroup = "sequence"; diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs index 373a97e..6c55d44 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs @@ -52,8 +52,8 @@ public RollingFileSink(string path, TimeSpan? retainedFileTimeLimit) { if (path == null) throw new ArgumentNullException(nameof(path)); - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); - if (retainedFileCountLimit.HasValue && retainedFileCountLimit < 1) throw new ArgumentException("Zero or negative value provided; retained file count limit must be at least 1"); + if (fileSizeLimitBytes is < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); + if (retainedFileCountLimit is < 1) throw new ArgumentException("Zero or negative value provided; retained file count limit must be at least 1."); if (retainedFileTimeLimit.HasValue && retainedFileTimeLimit < TimeSpan.Zero) throw new ArgumentException("Negative value provided; retained file time limit must be non-negative.", nameof(retainedFileTimeLimit)); _roller = new PathRoller(path, rollingInterval); @@ -121,8 +121,9 @@ void OpenFile(DateTime now, int? minSequence = null) { if (Directory.Exists(_roller.LogFileDirectory)) { + // ReSharper disable once ConvertClosureToMethodGroup existingFiles = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) - .Select(f => Path.GetFileName(f)); + .Select(f => Path.GetFileName(f)); } } catch (DirectoryNotFoundException) { } @@ -130,8 +131,12 @@ void OpenFile(DateTime now, int? minSequence = null) var latestForThisCheckpoint = _roller .SelectMatches(existingFiles) .Where(m => m.DateTime == currentCheckpoint) +#if ENUMERABLE_MAXBY + .MaxBy(m => m.SequenceNumber); +#else .OrderByDescending(m => m.SequenceNumber) .FirstOrDefault(); +#endif var sequence = latestForThisCheckpoint?.SequenceNumber; if (minSequence != null) @@ -149,7 +154,7 @@ void OpenFile(DateTime now, int? minSequence = null) { _currentFile = _shared ? #pragma warning disable 618 - (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : + new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) : #pragma warning restore 618 new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _hooks); @@ -180,6 +185,7 @@ void ApplyRetentionPolicy(string currentFilePath, DateTime now) // We consider the current file to exist, even if nothing's been written yet, // because files are only opened on response to an event being processed. + // ReSharper disable once ConvertClosureToMethodGroup var potentialMatches = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern) .Select(f => Path.GetFileName(f)) .Union(new[] { currentFileName }); diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs index 98ffb42..a469abe 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingIntervalExtensions.cs @@ -18,44 +18,30 @@ static class RollingIntervalExtensions { public static string GetFormat(this RollingInterval interval) { - switch (interval) + return interval switch { - case RollingInterval.Infinite: - return ""; - case RollingInterval.Year: - return "yyyy"; - case RollingInterval.Month: - return "yyyyMM"; - case RollingInterval.Day: - return "yyyyMMdd"; - case RollingInterval.Hour: - return "yyyyMMddHH"; - case RollingInterval.Minute: - return "yyyyMMddHHmm"; - default: - throw new ArgumentException("Invalid rolling interval"); - } + RollingInterval.Infinite => "", + RollingInterval.Year => "yyyy", + RollingInterval.Month => "yyyyMM", + RollingInterval.Day => "yyyyMMdd", + RollingInterval.Hour => "yyyyMMddHH", + RollingInterval.Minute => "yyyyMMddHHmm", + _ => throw new ArgumentException("Invalid rolling interval.") + }; } public static DateTime? GetCurrentCheckpoint(this RollingInterval interval, DateTime instant) { - switch (interval) + return interval switch { - case RollingInterval.Infinite: - return null; - case RollingInterval.Year: - return new DateTime(instant.Year, 1, 1, 0, 0, 0, instant.Kind); - case RollingInterval.Month: - return new DateTime(instant.Year, instant.Month, 1, 0, 0, 0, instant.Kind); - case RollingInterval.Day: - return new DateTime(instant.Year, instant.Month, instant.Day, 0, 0, 0, instant.Kind); - case RollingInterval.Hour: - return new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, 0, 0, instant.Kind); - case RollingInterval.Minute: - return new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, instant.Minute, 0, instant.Kind); - default: - throw new ArgumentException("Invalid rolling interval"); - } + RollingInterval.Infinite => null, + RollingInterval.Year => new DateTime(instant.Year, 1, 1, 0, 0, 0, instant.Kind), + RollingInterval.Month => new DateTime(instant.Year, instant.Month, 1, 0, 0, 0, instant.Kind), + RollingInterval.Day => new DateTime(instant.Year, instant.Month, instant.Day, 0, 0, 0, instant.Kind), + RollingInterval.Hour => new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, 0, 0, instant.Kind), + RollingInterval.Minute => new DateTime(instant.Year, instant.Month, instant.Day, instant.Hour, instant.Minute, 0, instant.Kind), + _ => throw new ArgumentException("Invalid rolling interval.") + }; } public static DateTime? GetNextCheckpoint(this RollingInterval interval, DateTime instant) @@ -64,20 +50,14 @@ public static string GetFormat(this RollingInterval interval) if (current == null) return null; - switch (interval) + return interval switch { - case RollingInterval.Year: - return current.Value.AddYears(1); - case RollingInterval.Month: - return current.Value.AddMonths(1); - case RollingInterval.Day: - return current.Value.AddDays(1); - case RollingInterval.Hour: - return current.Value.AddHours(1); - case RollingInterval.Minute: - return current.Value.AddMinutes(1); - default: - throw new ArgumentException("Invalid rolling interval"); - } + RollingInterval.Year => current.Value.AddYears(1), + RollingInterval.Month => current.Value.AddMonths(1), + RollingInterval.Day => current.Value.AddDays(1), + RollingInterval.Hour => current.Value.AddHours(1), + RollingInterval.Minute => current.Value.AddMinutes(1), + _ => throw new ArgumentException("Invalid rolling interval.") + }; } } diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs b/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs index 6441627..3a5b493 100644 --- a/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs +++ b/src/Serilog.Sinks.File/Sinks/File/RollingLogFile.cs @@ -15,19 +15,9 @@ namespace Serilog.Sinks.File; -class RollingLogFile +class RollingLogFile(string filename, DateTime? dateTime, int? sequenceNumber) { - public RollingLogFile(string filename, DateTime? dateTime, int? sequenceNumber) - { - Filename = filename; - DateTime = dateTime; - SequenceNumber = sequenceNumber; - } - - public string Filename { get; } - - public DateTime? DateTime { get; } - - public int? SequenceNumber { get; } + public string Filename { get; } = filename; + public DateTime? DateTime { get; } = dateTime; + public int? SequenceNumber { get; } = sequenceNumber; } - diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs index 6cf55cb..c753e46 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs @@ -14,163 +14,160 @@ #if ATOMIC_APPEND -using System; -using System.IO; using System.Security.AccessControl; using System.Text; using Serilog.Events; using Serilog.Formatting; -namespace Serilog.Sinks.File +namespace Serilog.Sinks.File; + +/// +/// Write log events to a disk file. +/// +[Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(shared: true)` instead.")] +public sealed class SharedFileSink : IFileSink, IDisposable { - /// - /// Write log events to a disk file. - /// - [Obsolete("This type will be removed from the public API in a future version; use `WriteTo.File(shared: true)` instead.")] - public sealed class SharedFileSink : IFileSink, IDisposable + readonly MemoryStream _writeBuffer; + readonly string _path; + readonly TextWriter _output; + readonly ITextFormatter _textFormatter; + readonly long? _fileSizeLimitBytes; + readonly object _syncRoot = new(); + + // The stream is reopened with a larger buffer if atomic writes beyond the current buffer size are needed. + FileStream _fileOutput; + int _fileStreamBufferLength = DefaultFileStreamBufferLength; + + const int DefaultFileStreamBufferLength = 4096; + + /// Construct a . + /// Path to the file. + /// Formatter used to convert log events to text. + /// The approximate maximum size, in bytes, to which a log file will be allowed to grow. + /// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit + /// will be written in full even if it exceeds the limit. + /// Character encoding used to write the text file. The default is UTF-8 without BOM. + /// Configuration object allowing method chaining. + /// When is null + /// When is null + /// + /// + /// + /// When is too long + /// The caller does not have the required permission to access the + /// Invalid + public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null) { - readonly MemoryStream _writeBuffer; - readonly string _path; - readonly TextWriter _output; - readonly ITextFormatter _textFormatter; - readonly long? _fileSizeLimitBytes; - readonly object _syncRoot = new object(); - - // The stream is reopened with a larger buffer if atomic writes beyond the current buffer size are needed. - FileStream _fileOutput; - int _fileStreamBufferLength = DefaultFileStreamBufferLength; - - const int DefaultFileStreamBufferLength = 4096; - - /// Construct a . - /// Path to the file. - /// Formatter used to convert log events to text. - /// The approximate maximum size, in bytes, to which a log file will be allowed to grow. - /// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit - /// will be written in full even if it exceeds the limit. - /// Character encoding used to write the text file. The default is UTF-8 without BOM. - /// Configuration object allowing method chaining. - /// When is null - /// When is null - /// - /// - /// - /// When is too long - /// The caller does not have the required permission to access the - /// Invalid - public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding? encoding = null) - { - if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) - throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null"); + if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) + throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null"); - _path = path ?? throw new ArgumentNullException(nameof(path)); - _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); - _fileSizeLimitBytes = fileSizeLimitBytes; + _path = path ?? throw new ArgumentNullException(nameof(path)); + _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); + _fileSizeLimitBytes = fileSizeLimitBytes; - var directory = Path.GetDirectoryName(path); - if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - // FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND, but that API is not yet - // exposed by .NET Core. - _fileOutput = new FileStream( - path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - _fileStreamBufferLength, - FileOptions.None); - - _writeBuffer = new MemoryStream(); - _output = new StreamWriter(_writeBuffer, - encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); } - bool IFileSink.EmitOrOverflow(LogEvent logEvent) - { - if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); + // FileSystemRights.AppendData sets the Win32 FILE_APPEND_DATA flag. On Linux this is O_APPEND, but that API is not yet + // exposed by .NET Core. + _fileOutput = new FileStream( + path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite, + _fileStreamBufferLength, + FileOptions.None); + + _writeBuffer = new MemoryStream(); + _output = new StreamWriter(_writeBuffer, + encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + } + + bool IFileSink.EmitOrOverflow(LogEvent logEvent) + { + if (logEvent == null) throw new ArgumentNullException(nameof(logEvent)); - lock (_syncRoot) + lock (_syncRoot) + { + try { - try + _textFormatter.Format(logEvent, _output); + _output.Flush(); + var bytes = _writeBuffer.GetBuffer(); + var length = (int) _writeBuffer.Length; + if (length > _fileStreamBufferLength) { - _textFormatter.Format(logEvent, _output); - _output.Flush(); - var bytes = _writeBuffer.GetBuffer(); - var length = (int) _writeBuffer.Length; - if (length > _fileStreamBufferLength) - { - var oldOutput = _fileOutput; - - _fileOutput = new FileStream( - _path, - FileMode.Append, - FileSystemRights.AppendData, - FileShare.ReadWrite, - length, - FileOptions.None); - _fileStreamBufferLength = length; - - oldOutput.Dispose(); - } + var oldOutput = _fileOutput; + + _fileOutput = new FileStream( + _path, + FileMode.Append, + FileSystemRights.AppendData, + FileShare.ReadWrite, + length, + FileOptions.None); + _fileStreamBufferLength = length; + + oldOutput.Dispose(); + } - if (_fileSizeLimitBytes != null) + if (_fileSizeLimitBytes != null) + { + try { - try - { - if (_fileOutput.Length >= _fileSizeLimitBytes.Value) - return false; - } - catch (FileNotFoundException) { } // Cheaper and more reliable than checking existence + if (_fileOutput.Length >= _fileSizeLimitBytes.Value) + return false; } - - _fileOutput.Write(bytes, 0, length); - _fileOutput.Flush(); - return true; - } - catch - { - // Make sure there's no leftover cruft in there. - _output.Flush(); - throw; - } - finally - { - _writeBuffer.Position = 0; - _writeBuffer.SetLength(0); + catch (FileNotFoundException) { } // Cheaper and more reliable than checking existence } + + _fileOutput.Write(bytes, 0, length); + _fileOutput.Flush(); + return true; + } + catch + { + // Make sure there's no leftover cruft in there. + _output.Flush(); + throw; + } + finally + { + _writeBuffer.Position = 0; + _writeBuffer.SetLength(0); } } + } - /// - /// Emit the provided log event to the sink. - /// - /// The log event to write. - /// When is null - public void Emit(LogEvent logEvent) - { - ((IFileSink)this).EmitOrOverflow(logEvent); - } + /// + /// Emit the provided log event to the sink. + /// + /// The log event to write. + /// When is null + public void Emit(LogEvent logEvent) + { + ((IFileSink)this).EmitOrOverflow(logEvent); + } - /// - public void Dispose() + /// + public void Dispose() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _fileOutput.Dispose(); - } + _fileOutput.Dispose(); } + } - /// - public void FlushToDisk() + /// + public void FlushToDisk() + { + lock (_syncRoot) { - lock (_syncRoot) - { - _output.Flush(); - _fileOutput.Flush(true); - } + _output.Flush(); + _fileOutput.Flush(true); } } } diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs index 0038c6b..6f5dc5c 100644 --- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs +++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs @@ -14,12 +14,9 @@ #if OS_MUTEX -using System; -using System.IO; using System.Text; using Serilog.Events; using Serilog.Formatting; -using System.Threading; using Serilog.Debugging; namespace Serilog.Sinks.File; @@ -60,7 +57,7 @@ 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 (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 1) + if (fileSizeLimitBytes is < 1) throw new ArgumentException("Invalid value provided; file size limit must be at least 1 byte, or null."); _textFormatter = textFormatter ?? throw new ArgumentNullException(nameof(textFormatter)); _fileSizeLimitBytes = fileSizeLimitBytes; diff --git a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs index e95d80f..39eec07 100644 --- a/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs +++ b/src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs @@ -46,7 +46,6 @@ public override void Write(byte[] buffer, int offset, int count) public override bool CanWrite => true; public override long Length => _stream.Length; - public override long Position { get => _stream.Position; diff --git a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs index 9c32200..b3ce3e2 100644 --- a/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileLoggerConfigurationExtensionsTests.cs @@ -29,52 +29,44 @@ public void WhenAuditingCreationExceptionsPropagate() [Fact] public void WhenWritingLoggingExceptionsAreSuppressed() { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() + using var tmp = TempFolder.ForCaller(); + using var log = new LoggerConfiguration() .WriteTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) - .CreateLogger()) - { - log.Information("Hello"); - } + .CreateLogger(); + log.Information("Hello"); } [Fact] public void WhenAuditingLoggingExceptionsPropagate() { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() + using var tmp = TempFolder.ForCaller(); + using var log = new LoggerConfiguration() .AuditTo.File(new ThrowingLogEventFormatter(), tmp.AllocateFilename()) - .CreateLogger()) - { - var ex = Assert.Throws(() => log.Information("Hello")); - Assert.IsType(ex.GetBaseException()); - } + .CreateLogger(); + var ex = Assert.Throws(() => log.Information("Hello")); + Assert.IsType(ex.GetBaseException()); } [Fact] public void WhenFlushingToDiskReportedFileSinkCanBeCreatedAndDisposed() { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() + using var tmp = TempFolder.ForCaller(); + using var log = new LoggerConfiguration() .WriteTo.File(tmp.AllocateFilename(), flushToDiskInterval: TimeSpan.FromMilliseconds(500)) - .CreateLogger()) - { - log.Information("Hello"); - Thread.Sleep(TimeSpan.FromSeconds(1)); - } + .CreateLogger(); + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); } [Fact] public void WhenFlushingToDiskReportedSharedFileSinkCanBeCreatedAndDisposed() { - using (var tmp = TempFolder.ForCaller()) - using (var log = new LoggerConfiguration() + using var tmp = TempFolder.ForCaller(); + using var log = new LoggerConfiguration() .WriteTo.File(tmp.AllocateFilename(), shared: true, flushToDiskInterval: TimeSpan.FromMilliseconds(500)) - .CreateLogger()) - { - log.Information("Hello"); - Thread.Sleep(TimeSpan.FromSeconds(1)); - } + .CreateLogger(); + log.Information("Hello"); + Thread.Sleep(TimeSpan.FromSeconds(1)); } [Fact] @@ -98,19 +90,17 @@ public void HooksAreNotAvailableWhenSharingEnabled() [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."); - } + using var tmp = TempFolder.ForCaller(); + var filename = tmp.AllocateFilename("txt"); - // Don't forget the two-byte BOM :-) - Assert.Equal(22, System.IO.File.ReadAllBytes(filename).Length); + 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); } } diff --git a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs index af5c8f5..0c76349 100644 --- a/test/Serilog.Sinks.File.Tests/FileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/FileSinkTests.cs @@ -14,43 +14,39 @@ public class FileSinkTests [Fact] public void FileIsWrittenIfNonexistent() { - 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)) - { - sink.Emit(evt); - } + using var tmp = TempFolder.ForCaller(); + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - var lines = System.IO.File.ReadAllLines(nonexistent); - Assert.Contains("Hello, world!", lines[0]); + using (var sink = new FileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); } [Fact] public void FileIsAppendedToWhenAlreadyCreated() { - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } - - using (var sink = new FileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + using (var sink = new FileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); + } - var lines = System.IO.File.ReadAllLines(path); - Assert.Contains("Hello, world!", lines[0]); - Assert.Contains("Hello, world!", lines[1]); + using (var sink = new FileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(path); + Assert.Contains("Hello, world!", lines[0]); + Assert.Contains("Hello, world!", lines[1]); } [Fact] @@ -59,23 +55,21 @@ public void WhenLimitIsSpecifiedFileSizeIsRestricted() const int maxBytes = 5000; const int eventsToLimit = 10; - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var sink = new FileSink(path, new JsonFormatter(), maxBytes)) + using (var sink = new FileSink(path, new JsonFormatter(), maxBytes)) + { + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes); - Assert.True(size < maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes); + Assert.True(size < maxBytes * 2); } [Fact] @@ -84,22 +78,20 @@ public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() const int maxBytes = 5000; const int eventsToLimit = 10; - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var sink = new FileSink(path, new JsonFormatter(), null)) + using (var sink = new FileSink(path, new JsonFormatter(), null)) + { + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes * 2); } @@ -146,132 +138,122 @@ public void OnOpenedLifecycleHookCanWrapUnderlyingStream() { var gzipWrapper = new GZipHooks(); - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new FileSink(path, new JsonFormatter(), null, null, false, gzipWrapper)) - { - sink.Emit(evt); - sink.Emit(evt); - } + using (var sink = new FileSink(path, new JsonFormatter(), null, null, false, gzipWrapper)) + { + sink.Emit(evt); + sink.Emit(evt); + } - // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against - // what we wrote - List lines; - using (var textStream = new MemoryStream()) + // Ensure the data was written through the wrapping GZipStream, by decompressing and comparing against + // what we wrote + List lines; + using (var textStream = new MemoryStream()) + { + using (var fs = System.IO.File.OpenRead(path)) + using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) { - using (var fs = System.IO.File.OpenRead(path)) - using (var decompressStream = new GZipStream(fs, CompressionMode.Decompress)) - { - decompressStream.CopyTo(textStream); - } - - textStream.Position = 0; - lines = textStream.ReadAllLines(); + decompressStream.CopyTo(textStream); } - Assert.Equal(2, lines.Count); - Assert.Contains("Hello, world!", lines[0]); + textStream.Position = 0; + lines = textStream.ReadAllLines(); } + + Assert.Equal(2, lines.Count); + Assert.Contains("Hello, world!", lines[0]); } [Fact] public static void OnOpenedLifecycleHookCanWriteFileHeader() { - using (var tmp = TempFolder.ForCaller()) - { - var headerWriter = new FileHeaderWriter("This is the file header"); + 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 - } + var path = tmp.AllocateFilename("txt"); + using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) + { + // Open and write header + } - using (var sink = new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) - { - // Length check should prevent duplicate header here - sink.Emit(Some.LogEvent()); - } + using (var sink = new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, headerWriter)) + { + // Length check should prevent duplicate header here + sink.Emit(Some.LogEvent()); + } - var lines = System.IO.File.ReadAllLines(path); + var lines = System.IO.File.ReadAllLines(path); - Assert.Equal(2, lines.Length); - Assert.Equal(headerWriter.Header, lines[0]); - Assert.Equal('{', lines[1][0]); - } + Assert.Equal(2, lines.Length); + Assert.Equal(headerWriter.Header, lines[0]); + Assert.Equal('{', lines[1][0]); } [Fact] public static void OnOpenedLifecycleHookCanCaptureFilePath() { - using (var tmp = TempFolder.ForCaller()) - { - var capturePath = new CaptureFilePathHook(); + using var tmp = TempFolder.ForCaller(); + var capturePath = new CaptureFilePathHook(); - var path = tmp.AllocateFilename("txt"); - using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, capturePath)) - { - // Open and capture the log file path - } - - Assert.Equal(path, capturePath.Path); + var path = tmp.AllocateFilename("txt"); + using (new FileSink(path, new JsonFormatter(), null, new UTF8Encoding(false), false, capturePath)) + { + // Open and capture the log file path } + + Assert.Equal(path, capturePath.Path); } [Fact] public static void OnOpenedLifecycleHookCanEmptyTheFileContents() { - using (var tmp = TempFolder.ForCaller()) - { - var emptyFileHook = new TruncateFileHook(); + using var tmp = TempFolder.ForCaller(); + var emptyFileHook = new TruncateFileHook(); - var path = tmp.AllocateFilename("txt"); - using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false)) - { - sink.Emit(Some.LogEvent()); - } + var path = tmp.AllocateFilename("txt"); + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false)) + { + sink.Emit(Some.LogEvent()); + } - using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook)) - { - // Hook will clear the contents of the file before emitting the log events - sink.Emit(Some.LogEvent()); - } + using (var sink = new FileSink(path, new JsonFormatter(), fileSizeLimitBytes: null, encoding: new UTF8Encoding(false), buffered: false, hooks: emptyFileHook)) + { + // Hook will clear the contents of the file before emitting the log events + sink.Emit(Some.LogEvent()); + } - var lines = System.IO.File.ReadAllLines(path); + var lines = System.IO.File.ReadAllLines(path); - Assert.Single(lines); - Assert.Equal('{', lines[0][0]); - } + Assert.Single(lines); + Assert.Equal('{', lines[0][0]); } static void WriteTwoEventsAndCheckOutputFileLength(long? maxBytes, Encoding encoding) { - using (var tmp = TempFolder.ForCaller()) + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Irrelevant as it will be replaced by the formatter"); + const string actualEventOutput = "x"; + var formatter = new FixedOutputFormatter(actualEventOutput); + var eventOuputLength = encoding.GetByteCount(actualEventOutput); + + using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Irrelevant as it will be replaced by the formatter"); - const string actualEventOutput = "x"; - var formatter = new FixedOutputFormatter(actualEventOutput); - var eventOuputLength = encoding.GetByteCount(actualEventOutput); - - using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) - { - sink.Emit(evt); - } - var size = new FileInfo(path).Length; - Assert.Equal(encoding.GetPreamble().Length + eventOuputLength, size); - - //write a second event to the same file - using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) - { - sink.Emit(evt); - } + sink.Emit(evt); + } + var size = new FileInfo(path).Length; + Assert.Equal(encoding.GetPreamble().Length + eventOuputLength, size); - size = new FileInfo(path).Length; - Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size); + //write a second event to the same file + using (var sink = new FileSink(path, formatter, maxBytes, encoding: encoding)) + { + sink.Emit(evt); } + + size = new FileInfo(path).Length; + Assert.Equal(encoding.GetPreamble().Length + eventOuputLength * 2, size); } } diff --git a/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs b/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs index a7b307b..7572067 100644 --- a/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs +++ b/test/Serilog.Sinks.File.Tests/RollingIntervalExtensionsTests.cs @@ -4,20 +4,20 @@ namespace Serilog.Sinks.File.Tests; public class RollingIntervalExtensionsTests { - public static object?[][] IntervalInstantCurrentNextCheckpoint => new[] - { - new object?[]{ RollingInterval.Infinite, new DateTime(2018, 01, 01), null, null }, - new object?[]{ RollingInterval.Year, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, - new object?[]{ RollingInterval.Year, new DateTime(2018, 06, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01) }, - new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, - new object?[]{ RollingInterval.Month, new DateTime(2018, 01, 14), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01) }, - new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, - new object?[]{ RollingInterval.Day, new DateTime(2018, 01, 01, 12, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02) }, - new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, - new object?[]{ RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 30, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0) }, - new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) }, - new object?[]{ RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 30), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0) } - }; + public static object?[][] IntervalInstantCurrentNextCheckpoint => + [ + [RollingInterval.Infinite, new DateTime(2018, 01, 01), null, null], + [RollingInterval.Year, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01)], + [RollingInterval.Year, new DateTime(2018, 06, 01), new DateTime(2018, 01, 01), new DateTime(2019, 01, 01)], + [RollingInterval.Month, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01)], + [RollingInterval.Month, new DateTime(2018, 01, 14), new DateTime(2018, 01, 01), new DateTime(2018, 02, 01)], + [RollingInterval.Day, new DateTime(2018, 01, 01), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02)], + [RollingInterval.Day, new DateTime(2018, 01, 01, 12, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 02)], + [RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0)], + [RollingInterval.Hour, new DateTime(2018, 01, 01, 0, 30, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 1, 0, 0)], + [RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 0), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0)], + [RollingInterval.Minute, new DateTime(2018, 01, 01, 0, 0, 30), new DateTime(2018, 01, 01), new DateTime(2018, 01, 01, 0, 1, 0)] + ]; [Theory] [MemberData(nameof(IntervalInstantCurrentNextCheckpoint))] diff --git a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj index 5d5af8c..9ad3db7 100644 --- a/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj +++ b/test/Serilog.Sinks.File.Tests/Serilog.Sinks.File.Tests.csproj @@ -1,7 +1,7 @@ - net48;net6.0 + net48;net8.0 true @@ -10,12 +10,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs index ec90f25..dff0a0d 100644 --- a/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs +++ b/test/Serilog.Sinks.File.Tests/SharedFileSinkTests.cs @@ -11,43 +11,39 @@ public class SharedFileSinkTests [Fact] public void FileIsWrittenIfNonexistent() { - using (var tmp = TempFolder.ForCaller()) - { - var nonexistent = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); - - using (var sink = new SharedFileSink(nonexistent, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + using var tmp = TempFolder.ForCaller(); + var nonexistent = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - var lines = System.IO.File.ReadAllLines(nonexistent); - Assert.Contains("Hello, world!", lines[0]); + using (var sink = new SharedFileSink(nonexistent, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(nonexistent); + Assert.Contains("Hello, world!", lines[0]); } [Fact] public void FileIsAppendedToWhenAlreadyCreated() { - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent("Hello, world!"); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent("Hello, world!"); - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } - - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) - { - sink.Emit(evt); - } + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); + } - var lines = System.IO.File.ReadAllLines(path); - Assert.Contains("Hello, world!", lines[0]); - Assert.Contains("Hello, world!", lines[1]); + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + { + sink.Emit(evt); } + + var lines = System.IO.File.ReadAllLines(path); + Assert.Contains("Hello, world!", lines[0]); + Assert.Contains("Hello, world!", lines[1]); } [Fact] @@ -56,23 +52,21 @@ public void WhenLimitIsSpecifiedFileSizeIsRestricted() const int maxBytes = 5000; const int eventsToLimit = 10; - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var sink = new SharedFileSink(path, new JsonFormatter(), maxBytes)) + using (var sink = new SharedFileSink(path, new JsonFormatter(), maxBytes)) + { + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes); - Assert.True(size < maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes); + Assert.True(size < maxBytes * 2); } [Fact] @@ -81,21 +75,19 @@ public void WhenLimitIsNotSpecifiedFileSizeIsNotRestricted() const int maxBytes = 5000; const int eventsToLimit = 10; - using (var tmp = TempFolder.ForCaller()) - { - var path = tmp.AllocateFilename("txt"); - var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); + using var tmp = TempFolder.ForCaller(); + var path = tmp.AllocateFilename("txt"); + var evt = Some.LogEvent(new string('n', maxBytes / eventsToLimit)); - using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + using (var sink = new SharedFileSink(path, new JsonFormatter(), null)) + { + for (var i = 0; i < eventsToLimit * 2; i++) { - for (var i = 0; i < eventsToLimit * 2; i++) - { - sink.Emit(evt); - } + sink.Emit(evt); } - - var size = new FileInfo(path).Length; - Assert.True(size > maxBytes * 2); } + + var size = new FileInfo(path).Length; + Assert.True(size > maxBytes * 2); } } diff --git a/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs index 566b5ba..c8b93a8 100644 --- a/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs +++ b/test/Serilog.Sinks.File.Tests/Support/ArchiveOldLogsHook.cs @@ -1,8 +1,8 @@ namespace Serilog.Sinks.File.Tests.Support; -internal class ArchiveOldLogsHook : FileLifecycleHooks +class ArchiveOldLogsHook : FileLifecycleHooks { - private readonly string _relativeArchiveDir; + readonly string _relativeArchiveDir; public ArchiveOldLogsHook(string relativeArchiveDir) { diff --git a/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs b/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs index 3386a4f..81a480c 100644 --- a/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs +++ b/test/Serilog.Sinks.File.Tests/Support/DelegateDisposable.cs @@ -2,8 +2,8 @@ namespace Serilog.Sinks.File.Tests.Support; public class DelegateDisposable : IDisposable { - private readonly Action _disposeAction; - private bool _disposed; + readonly Action _disposeAction; + bool _disposed; public DelegateDisposable(Action disposeAction) { diff --git a/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs b/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs deleted file mode 100644 index ff8cf35..0000000 --- a/test/Serilog.Sinks.File.Tests/Support/DisposableLogger.cs +++ /dev/null @@ -1,419 +0,0 @@ -using Serilog.Core; -using Serilog.Events; - -namespace Serilog.Sinks.File.Tests.Support; - -public class DisposableLogger : ILogger, IDisposable -{ - public bool Disposed { get; set; } - - public void Dispose() - { - Disposed = true; - } - - public ILogger ForContext(ILogEventEnricher enricher) - { - throw new NotImplementedException(); - } - - public ILogger ForContext(IEnumerable enrichers) - { - throw new NotImplementedException(); - } - - public ILogger ForContext(string propertyName, object value, bool destructureObjects = false) - { - throw new NotImplementedException(); - } - - public ILogger ForContext() - { - throw new NotImplementedException(); - } - - public ILogger ForContext(Type source) - { - throw new NotImplementedException(); - } - - public void Write(LogEvent logEvent) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, T0 propertyValue0, - T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public bool IsEnabled(LogEventLevel level) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Verbose(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Verbose(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Debug(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Debug(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Information(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Information(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Warning(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Warning(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Error(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Error(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Fatal(string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T propertyValue) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, T0 propertyValue0, T1 propertyValue1, - T2 propertyValue2) - { - throw new NotImplementedException(); - } - - public void Fatal(Exception exception, string messageTemplate, params object[] propertyValues) - { - throw new NotImplementedException(); - } - - public bool BindMessageTemplate(string messageTemplate, object[] propertyValues, out MessageTemplate parsedTemplate, - out IEnumerable boundProperties) - { - throw new NotImplementedException(); - } - - public bool BindProperty(string propertyName, object value, bool destructureObjects, out LogEventProperty property) - { - throw new NotImplementedException(); - } -} diff --git a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs index 3fd7a15..db8781b 100644 --- a/test/Serilog.Sinks.File.Tests/Support/Extensions.cs +++ b/test/Serilog.Sinks.File.Tests/Support/Extensions.cs @@ -4,7 +4,7 @@ namespace Serilog.Sinks.File.Tests.Support; public static class Extensions { - public static object LiteralValue(this LogEventPropertyValue @this) + public static object? LiteralValue(this LogEventPropertyValue @this) { return ((ScalarValue)@this).Value; } @@ -13,13 +13,11 @@ public static List ReadAllLines(this Stream @this) { var lines = new List(); - using (var reader = new StreamReader(@this)) + using var reader = new StreamReader(@this); + string? line; + while ((line = reader.ReadLine()) != null) { - string? line; - while ((line = reader.ReadLine()) != null) - { - lines.Add(line); - } + lines.Add(line); } return lines;