Skip to content

Commit 6805678

Browse files
authored
Merge pull request #13 from nblumhardt/f-charcountfix
Revise the method of file size limiting
2 parents d25ad67 + 1b77766 commit 6805678

File tree

10 files changed

+233
-98
lines changed

10 files changed

+233
-98
lines changed

example/Sample/Program.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.IO;
3+
using Serilog;
4+
5+
namespace Sample
6+
{
7+
public class Program
8+
{
9+
public static void Main(string[] args)
10+
{
11+
Log.Logger = new LoggerConfiguration()
12+
.WriteTo.File("log.txt")
13+
.CreateLogger();
14+
15+
var sw = System.Diagnostics.Stopwatch.StartNew();
16+
17+
for (var i = 0; i < 1000000; ++i)
18+
{
19+
Log.Information("Hello, file logger!");
20+
}
21+
22+
Log.CloseAndFlush();
23+
24+
sw.Stop();
25+
26+
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
27+
28+
File.Delete("log.txt");
29+
}
30+
}
31+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyConfiguration("")]
9+
[assembly: AssemblyCompany("")]
10+
[assembly: AssemblyProduct("Sample")]
11+
[assembly: AssemblyTrademark("")]
12+
13+
// Setting ComVisible to false makes the types in this assembly not visible
14+
// to COM components. If you need to access a type in this assembly from
15+
// COM, set the ComVisible attribute to true on that type.
16+
[assembly: ComVisible(false)]
17+
18+
// The following GUID is for the ID of the typelib if this project is exposed to COM
19+
[assembly: Guid("a34235a2-a717-4a1c-bf5c-f4a9e06e1260")]

example/Sample/Sample.xproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
5+
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
6+
</PropertyGroup>
7+
8+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
9+
<PropertyGroup Label="Globals">
10+
<ProjectGuid>a34235a2-a717-4a1c-bf5c-f4a9e06e1260</ProjectGuid>
11+
<RootNamespace>Sample</RootNamespace>
12+
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
13+
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
14+
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
15+
</PropertyGroup>
16+
17+
<PropertyGroup>
18+
<SchemaVersion>2.0</SchemaVersion>
19+
</PropertyGroup>
20+
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
21+
</Project>

example/Sample/project.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"version": "1.0.0-*",
3+
"buildOptions": {
4+
"emitEntryPoint": true
5+
},
6+
7+
"dependencies": {
8+
"Serilog.Sinks.File": { "target": "project" },
9+
"Microsoft.NETCore.App": {
10+
"type": "platform",
11+
"version": "1.0.0"
12+
}
13+
},
14+
15+
"frameworks": {
16+
"netcoreapp1.0": {
17+
"imports": "dnxcore50"
18+
}
19+
}
20+
}

serilog-sinks-file.sln

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 14
4-
VisualStudioVersion = 14.0.25123.0
4+
VisualStudioVersion = 14.0.25420.1
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{037440DE-440B-4129-9F7A-09B42D00397E}"
77
EndProject
@@ -21,6 +21,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7B927378-9
2121
EndProject
2222
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}"
2323
EndProject
24+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "example", "example", "{196B1544-C617-4D7C-96D1-628713BDD52A}"
25+
EndProject
26+
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Sample", "example\Sample\Sample.xproj", "{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}"
27+
EndProject
2428
Global
2529
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2630
Debug|Any CPU = Debug|Any CPU
@@ -35,12 +39,17 @@ Global
3539
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Debug|Any CPU.Build.0 = Debug|Any CPU
3640
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.ActiveCfg = Release|Any CPU
3741
{3C2D8E01-5580-426A-BDD9-EC59CD98E618}.Release|Any CPU.Build.0 = Release|Any CPU
42+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260}.Release|Any CPU.Build.0 = Release|Any CPU
3846
EndGlobalSection
3947
GlobalSection(SolutionProperties) = preSolution
4048
HideSolutionNode = FALSE
4149
EndGlobalSection
4250
GlobalSection(NestedProjects) = preSolution
4351
{57E0ED0E-0F45-48AB-A73D-6A92B7C32095} = {037440DE-440B-4129-9F7A-09B42D00397E}
4452
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {7B927378-9F16-4F6F-B3F6-156395136646}
53+
{A34235A2-A717-4A1C-BF5C-F4A9E06E1260} = {196B1544-C617-4D7C-96D1-628713BDD52A}
4554
EndGlobalSection
4655
EndGlobal

src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ public static class FileLoggerConfigurationExtensions
4242
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
4343
/// <param name="outputTemplate">A message template describing the format used to write to the sink.
4444
/// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
45-
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
46-
/// For unrestricted growth, pass null. The default is 1 GB.</param>
45+
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
46+
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
47+
/// will be written in full even if it exceeds the limit.</param>
4748
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
4849
/// is false.</param>
4950
/// <returns>Configuration object allowing method chaining.</returns>
@@ -80,8 +81,9 @@ public static LoggerConfiguration File(
8081
/// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
8182
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
8283
/// to be changed at runtime.</param>
83-
/// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
84-
/// For unrestricted growth, pass null. The default is 1 GB.</param>
84+
/// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
85+
/// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
86+
/// will be written in full even if it exceeds the limit.</param>
8587
/// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
8688
/// is false.</param>
8789
/// <returns>Configuration object allowing method chaining.</returns>

src/Serilog.Sinks.File/Sinks/File/CharacterCountLimitedTextWriter.cs

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/Serilog.Sinks.File/Sinks/File/FileSink.cs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@ namespace Serilog.Sinks.File
2626
/// </summary>
2727
public sealed class FileSink : ILogEventSink, IDisposable
2828
{
29-
const int BytesPerCharacterApproximate = 1;
3029
readonly TextWriter _output;
3130
readonly ITextFormatter _textFormatter;
31+
readonly long? _fileSizeLimitBytes;
3232
readonly bool _buffered;
3333
readonly object _syncRoot = new object();
34+
readonly WriteCountingStream _countingStreamWrapper;
3435

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

5254
_textFormatter = textFormatter;
55+
_fileSizeLimitBytes = fileSizeLimitBytes;
5356
_buffered = buffered;
5457

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

61-
var file = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
62-
var outputWriter = new StreamWriter(file, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
63-
if (fileSizeLimitBytes != null)
64+
Stream file = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
65+
if (_fileSizeLimitBytes != null)
6466
{
65-
var initialBytes = file.Length;
66-
var remainingCharacters = Math.Max(fileSizeLimitBytes.Value - initialBytes, 0L) / BytesPerCharacterApproximate;
67-
_output = new CharacterCountLimitedTextWriter(outputWriter, remainingCharacters);
68-
}
69-
else
70-
{
71-
_output = outputWriter;
67+
file = _countingStreamWrapper = new WriteCountingStream(file);
7268
}
69+
70+
_output = new StreamWriter(file, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
7371
}
7472

7573
/// <summary>
@@ -81,6 +79,12 @@ public void Emit(LogEvent logEvent)
8179
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
8280
lock (_syncRoot)
8381
{
82+
if (_fileSizeLimitBytes != null)
83+
{
84+
if (_countingStreamWrapper.CountedLength >= _fileSizeLimitBytes.Value)
85+
return;
86+
}
87+
8488
_textFormatter.Format(logEvent, _output);
8589
if (!_buffered)
8690
_output.Flush();
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2013-2016 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.IO;
17+
18+
namespace Serilog.Sinks.File
19+
{
20+
sealed class WriteCountingStream : Stream
21+
{
22+
readonly Stream _stream;
23+
long _countedLength;
24+
25+
public WriteCountingStream(Stream stream)
26+
{
27+
if (stream == null) throw new ArgumentNullException(nameof(stream));
28+
_stream = stream;
29+
_countedLength = stream.Length;
30+
}
31+
32+
public long CountedLength => _countedLength;
33+
34+
protected override void Dispose(bool disposing)
35+
{
36+
if (disposing)
37+
_stream.Dispose();
38+
39+
base.Dispose(disposing);
40+
}
41+
42+
public override void Write(byte[] buffer, int offset, int count)
43+
{
44+
_stream.Write(buffer, offset, count);
45+
_countedLength += count;
46+
}
47+
48+
public override void Flush() => _stream.Flush();
49+
public override bool CanRead => false;
50+
public override bool CanSeek => false;
51+
public override bool CanWrite => true;
52+
public override long Length => _stream.Length;
53+
54+
public override long Position
55+
{
56+
get { return _stream.Position; }
57+
set { throw new NotSupportedException(); }
58+
}
59+
60+
public override long Seek(long offset, SeekOrigin origin)
61+
{
62+
throw new NotSupportedException();
63+
}
64+
65+
public override void SetLength(long value)
66+
{
67+
throw new NotSupportedException();
68+
}
69+
70+
public override int Read(byte[] buffer, int offset, int count)
71+
{
72+
throw new NotSupportedException();
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)