Skip to content

Revise the method of file size limiting #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions example/Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.IO;
using Serilog;

namespace Sample
{
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.File("log.txt")
.CreateLogger();

var sw = System.Diagnostics.Stopwatch.StartNew();

for (var i = 0; i < 1000000; ++i)
{
Log.Information("Hello, file logger!");
}

Log.CloseAndFlush();

sw.Stop();

Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");

File.Delete("log.txt");
}
}
}
19 changes: 19 additions & 0 deletions example/Sample/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Sample")]
[assembly: AssemblyTrademark("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a34235a2-a717-4a1c-bf5c-f4a9e06e1260")]
21 changes: 21 additions & 0 deletions example/Sample/Sample.xproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>a34235a2-a717-4a1c-bf5c-f4a9e06e1260</ProjectGuid>
<RootNamespace>Sample</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
</PropertyGroup>

<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
20 changes: 20 additions & 0 deletions example/Sample/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},

"dependencies": {
"Serilog.Sinks.File": { "target": "project" },
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
}
},

"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}
11 changes: 10 additions & 1 deletion serilog-sinks-file.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}"
EndProject
Expand All @@ -21,6 +21,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7B927378-9
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Serilog.Sinks.File.Tests", "test\Serilog.Sinks.File.Tests\Serilog.Sinks.File.Tests.xproj", "{3C2D8E01-5580-426A-BDD9-EC59CD98E618}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{196B1544-C617-4D7C-96D1-628713BDD52A}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Sample", "example\Sample\Sample.xproj", "{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -35,12 +39,17 @@ Global
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{57E0ED0E-0F45-48AB-A73D-6A92B7C32095} = {037440DE-440B-4129-9F7A-09B42D00397E}
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {7B927378-9F16-4F6F-B3F6-156395136646}
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260} = {196B1544-C617-4D7C-96D1-628713BDD52A}
EndGlobalSection
EndGlobal
10 changes: 6 additions & 4 deletions src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ public static class FileLoggerConfigurationExtensions
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
/// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB.</param>
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <returns>Configuration object allowing method chaining.</returns>
Expand Down Expand Up @@ -80,8 +81,9 @@ public static LoggerConfiguration File(
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
/// to be changed at runtime.</param>
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB.</param>
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <returns>Configuration object allowing method chaining.</returns>
Expand Down

This file was deleted.

32 changes: 18 additions & 14 deletions src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,20 @@ namespace Serilog.Sinks.File
/// </summary>
public sealed class FileSink : ILogEventSink, IDisposable
{
const int BytesPerCharacterApproximate = 1;
readonly TextWriter _output;
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly bool _buffered;
readonly object _syncRoot = new object();
readonly WriteCountingStream _countingStreamWrapper;

/// <summary>Construct a <see cref="FileSink"/>.</summary>
/// <param name="path">Path to the file.</param>
/// <param name="textFormatter">Formatter used to convert log events to text.</param>
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB.</param>
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8.</param>
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
/// will be written in full even if it exceeds the limit.</param>
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
/// is false.</param>
/// <returns>Configuration object allowing method chaining.</returns>
Expand All @@ -50,6 +52,7 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative");

_textFormatter = textFormatter;
_fileSizeLimitBytes = fileSizeLimitBytes;
_buffered = buffered;

var directory = Path.GetDirectoryName(path);
Expand All @@ -58,18 +61,13 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
Directory.CreateDirectory(directory);
}

var file = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
var outputWriter = new StreamWriter(file, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
if (fileSizeLimitBytes != null)
Stream file = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
if (_fileSizeLimitBytes != null)
{
var initialBytes = file.Length;
var remainingCharacters = Math.Max(fileSizeLimitBytes.Value - initialBytes, 0L) / BytesPerCharacterApproximate;
_output = new CharacterCountLimitedTextWriter(outputWriter, remainingCharacters);
}
else
{
_output = outputWriter;
file = _countingStreamWrapper = new WriteCountingStream(file);
}

_output = new StreamWriter(file, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}

/// <summary>
Expand All @@ -81,6 +79,12 @@ public void Emit(LogEvent logEvent)
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
lock (_syncRoot)
{
if (_fileSizeLimitBytes != null)
{
if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes.Value)
return;
}

_textFormatter.Format(logEvent, _output);
if (!_buffered)
_output.Flush();
Expand Down
75 changes: 75 additions & 0 deletions src/Serilog.Sinks.File/Sinks/File/WriteCountingStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.
// 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.IO;

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't this throw a NotSupportedException if the stream doesn't support seeking? It might not matter since we're in control of which types of streams that gets passed here, in this case, FileStream, but are there cases where a FileStream can throw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it's probably less general than it appears 👍 - in the worst case though, the exception will be pretty easy to track down, so inclined to leave this one. Thanks!

}

public long CountedLength => _countedLength;

protected override void Dispose(bool disposing)
{
if (disposing)
_stream.Dispose();

base.Dispose(disposing);
}

public override void Write(byte[] buffer, int offset, int count)
{
_stream.Write(buffer, offset, count);
_countedLength += count;
}

public override void Flush() => _stream.Flush();
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => _stream.Length;

public override long Position
{
get { return _stream.Position; }
set { throw new NotSupportedException(); }
}

public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}

public override void SetLength(long value)
{
throw new NotSupportedException();
}

public override int Read(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
}
Loading