diff --git a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
index 5cb19e9..c38a308 100644
--- a/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
+++ b/src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
@@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-using System;
-using System.ComponentModel;
-using System.Text;
using Serilog.Configuration;
using Serilog.Core;
using Serilog.Debugging;
@@ -23,6 +20,10 @@
using Serilog.Formatting.Display;
using Serilog.Formatting.Json;
using Serilog.Sinks.File;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
// ReSharper disable MethodOverloadWithOptionalParameter
@@ -140,6 +141,7 @@ public static LoggerConfiguration File(
/// The maximum number of log files that will be retained,
/// including the current log file. For unlimited retention, pass null. The default is 31.
/// Character encoding used to write the text file. The default is UTF-8 without BOM.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
public static LoggerConfiguration File(
@@ -156,7 +158,8 @@ public static LoggerConfiguration File(
RollingInterval rollingInterval = RollingInterval.Infinite,
bool rollOnFileSizeLimit = false,
int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
- Encoding encoding = null)
+ Encoding encoding = null,
+ Func> logFileHeaders = null)
{
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
if (path == null) throw new ArgumentNullException(nameof(path));
@@ -165,7 +168,7 @@ public static LoggerConfiguration File(
var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes,
levelSwitch, buffered, shared, flushToDiskInterval,
- rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding);
+ rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, encoding, logFileHeaders);
}
///
@@ -174,7 +177,7 @@ public static LoggerConfiguration File(
/// Logger sink configuration.
/// A formatter, such as , to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
- /// overload of
+ /// overload of
/// and specify the outputTemplate parameter instead.
///
/// Path to the file.
@@ -195,6 +198,7 @@ public static LoggerConfiguration File(
/// The maximum number of log files that will be retained,
/// including the current log file. For unlimited retention, pass null. The default is 31.
/// Character encoding used to write the text file. The default is UTF-8 without BOM.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
public static LoggerConfiguration File(
@@ -210,10 +214,11 @@ public static LoggerConfiguration File(
RollingInterval rollingInterval = RollingInterval.Infinite,
bool rollOnFileSizeLimit = false,
int? retainedFileCountLimit = DefaultRetainedFileCountLimit,
- Encoding encoding = null)
+ Encoding encoding = null,
+ Func> logFileHeaders = null)
{
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, fileSizeLimitBytes, levelSwitch,
- buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit);
+ buffered, false, shared, flushToDiskInterval, encoding, rollingInterval, rollOnFileSizeLimit, retainedFileCountLimit, logFileHeaders);
}
///
@@ -228,6 +233,7 @@ public static LoggerConfiguration File(
/// Supplies culture-specific formatting information, or null.
/// A message template describing the format used to write to the sink.
/// the default is "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
public static LoggerConfiguration File(
@@ -236,14 +242,15 @@ public static LoggerConfiguration File(
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
string outputTemplate = DefaultOutputTemplate,
IFormatProvider formatProvider = null,
- LoggingLevelSwitch levelSwitch = null)
+ LoggingLevelSwitch levelSwitch = null,
+ Func> logFileHeaders = null)
{
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
if (path == null) throw new ArgumentNullException(nameof(path));
if (outputTemplate == null) throw new ArgumentNullException(nameof(outputTemplate));
var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);
- return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch);
+ return File(sinkConfiguration, formatter, path, restrictedToMinimumLevel, levelSwitch, logFileHeaders);
}
///
@@ -252,7 +259,7 @@ public static LoggerConfiguration File(
/// Logger sink configuration.
/// A formatter, such as , to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
- /// overload of
+ /// overload of
/// and specify the outputTemplate parameter instead.
///
/// Path to the file.
@@ -260,6 +267,7 @@ public static LoggerConfiguration File(
/// events passed through the sink. Ignored when is specified.
/// A switch allowing the pass-through minimum level
/// to be changed at runtime.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
public static LoggerConfiguration File(
@@ -267,10 +275,11 @@ public static LoggerConfiguration File(
ITextFormatter formatter,
string path,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
- LoggingLevelSwitch levelSwitch = null)
+ LoggingLevelSwitch levelSwitch = null,
+ Func> logFileHeaders = null)
{
return ConfigureFile(sinkConfiguration.Sink, formatter, path, restrictedToMinimumLevel, null, levelSwitch, false, true,
- false, null, null, RollingInterval.Infinite, false, null);
+ false, null, null, RollingInterval.Infinite, false, null, logFileHeaders);
}
static LoggerConfiguration ConfigureFile(
@@ -287,7 +296,8 @@ static LoggerConfiguration ConfigureFile(
Encoding encoding,
RollingInterval rollingInterval,
bool rollOnFileSizeLimit,
- int? retainedFileCountLimit)
+ int? retainedFileCountLimit,
+ Func> logFileHeaders)
{
if (addSink == null) throw new ArgumentNullException(nameof(addSink));
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
@@ -300,7 +310,7 @@ static LoggerConfiguration ConfigureFile(
if (rollOnFileSizeLimit || rollingInterval != RollingInterval.Infinite)
{
- sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit);
+ sink = new RollingFileSink(path, formatter, fileSizeLimitBytes, retainedFileCountLimit, encoding, buffered, shared, rollingInterval, rollOnFileSizeLimit, logFileHeaders);
}
else
{
@@ -309,11 +319,11 @@ static LoggerConfiguration ConfigureFile(
#pragma warning disable 618
if (shared)
{
- sink = new SharedFileSink(path, formatter, fileSizeLimitBytes);
+ sink = new SharedFileSink(path, formatter, fileSizeLimitBytes, logFileHeaders: logFileHeaders);
}
else
{
- sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered);
+ sink = new FileSink(path, formatter, fileSizeLimitBytes, buffered: buffered, logFileHeaders: logFileHeaders);
}
#pragma warning restore 618
}
diff --git a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
index bfd288f..07009ab 100644
--- a/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/FileSink.cs
@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+using Serilog.Events;
+using Serilog.Formatting;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Text;
-using Serilog.Events;
-using Serilog.Formatting;
namespace Serilog.Sinks.File
{
@@ -33,6 +34,8 @@ public sealed class FileSink : IFileSink, IDisposable
readonly bool _buffered;
readonly object _syncRoot = new object();
readonly WriteCountingStream _countingStreamWrapper;
+ readonly Func> _logFileHeaders;
+ bool _appendHeaderLogs = false;
/// Construct a .
/// Path to the file.
@@ -43,10 +46,11 @@ public sealed class FileSink : IFileSink, IDisposable
/// Character encoding used to write the text file. The default is UTF-8 without BOM.
/// Indicates if flushing to the output file can be buffered or not. The default
/// is false.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
///
- public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false)
+ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, bool buffered = false, Func> logFileHeaders = null)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
@@ -55,6 +59,7 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
_textFormatter = textFormatter;
_fileSizeLimitBytes = fileSizeLimitBytes;
_buffered = buffered;
+ _logFileHeaders = logFileHeaders;
var directory = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
@@ -62,6 +67,9 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
Directory.CreateDirectory(directory);
}
+ if (logFileHeaders != null && FileNotFoundOrEmpty(path))
+ _appendHeaderLogs = true;
+
Stream outputStream = _underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
if (_fileSizeLimitBytes != null)
{
@@ -71,6 +79,13 @@ public FileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBy
_output = new StreamWriter(outputStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
+ bool FileNotFoundOrEmpty(string path)
+ {
+ var fileInfo = new FileInfo(path);
+
+ return !fileInfo.Exists || fileInfo.Length == 0;
+ }
+
bool IFileSink.EmitOrOverflow(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
@@ -82,21 +97,36 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
return false;
}
- _textFormatter.Format(logEvent, _output);
- if (!_buffered)
- _output.Flush();
+ if (_appendHeaderLogs)
+ {
+ var logFileHeaderCollection = _logFileHeaders.Invoke();
+
+ foreach (var item in logFileHeaderCollection)
+ AppendToOutput(item);
+
+ _appendHeaderLogs = false;
+ }
+
+ AppendToOutput(logEvent);
return true;
}
}
+ private void AppendToOutput(LogEvent logEvent)
+ {
+ _textFormatter.Format(logEvent, _output);
+ if (!_buffered)
+ _output.Flush();
+ }
+
///
/// Emit the provided log event to the sink.
///
/// The log event to write.
public void Emit(LogEvent logEvent)
{
- ((IFileSink) this).EmitOrOverflow(logEvent);
+ ((IFileSink)this).EmitOrOverflow(logEvent);
}
///
diff --git a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs
index 6593e68..5ca1af1 100644
--- a/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs
@@ -14,14 +14,15 @@
#pragma warning disable 618
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
using Serilog.Core;
using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
namespace Serilog.Sinks.File
{
@@ -35,6 +36,7 @@ sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable
readonly bool _buffered;
readonly bool _shared;
readonly bool _rollOnFileSizeLimit;
+ readonly Func> _logFileHeaders;
readonly object _syncRoot = new object();
bool _isDisposed;
@@ -50,7 +52,8 @@ public RollingFileSink(string path,
bool buffered,
bool shared,
RollingInterval rollingInterval,
- bool rollOnFileSizeLimit)
+ bool rollOnFileSizeLimit,
+ Func> logFileHeaders)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (fileSizeLimitBytes.HasValue && fileSizeLimitBytes < 0) throw new ArgumentException("Negative value provided; file size limit must be non-negative");
@@ -64,6 +67,7 @@ public RollingFileSink(string path,
_buffered = buffered;
_shared = shared;
_rollOnFileSizeLimit = rollOnFileSizeLimit;
+ _logFileHeaders = logFileHeaders;
}
public void Emit(LogEvent logEvent)
@@ -146,8 +150,8 @@ void OpenFile(DateTime now, int? minSequence = null)
try
{
_currentFile = _shared ?
- (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding) :
- new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered);
+ (IFileSink)new SharedFileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _logFileHeaders) :
+ new FileSink(path, _textFormatter, _fileSizeLimitBytes, _encoding, _buffered, _logFileHeaders);
_currentFileSequence = sequence;
}
catch (IOException ex)
@@ -177,7 +181,7 @@ void ApplyRetentionPolicy(string currentFilePath)
// because files are only opened on response to an event being processed.
var potentialMatches = Directory.GetFiles(_roller.LogFileDirectory, _roller.DirectorySearchPattern)
.Select(Path.GetFileName)
- .Union(new [] { currentFileName });
+ .Union(new[] { currentFileName });
var newestFirst = _roller
.SelectMatches(potentialMatches)
diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
index 805e786..16111f5 100644
--- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.AtomicAppend.cs
@@ -14,13 +14,14 @@
#if ATOMIC_APPEND
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;
using System.Text;
-using Serilog.Core;
-using Serilog.Events;
-using Serilog.Formatting;
namespace Serilog.Sinks.File
{
@@ -36,6 +37,8 @@ public sealed class SharedFileSink : IFileSink, IDisposable
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly object _syncRoot = new object();
+ readonly Func> _logFileHeaders;
+ bool _appendHeaderLogs = false;
// The stream is reopened with a larger buffer if atomic writes beyond the current buffer size are needed.
FileStream _fileOutput;
@@ -50,10 +53,11 @@ public sealed class SharedFileSink : IFileSink, IDisposable
/// 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.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
///
- public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null)
+ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, Func> logFileHeaders = null)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
@@ -63,6 +67,7 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
_path = path;
_textFormatter = textFormatter;
_fileSizeLimitBytes = fileSizeLimitBytes;
+ _logFileHeaders = logFileHeaders;
var directory = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
@@ -70,6 +75,9 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
Directory.CreateDirectory(directory);
}
+ if (logFileHeaders != null && FileNotFoundOrEmpty(path))
+ _appendHeaderLogs = true;
+
// 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(
@@ -85,6 +93,13 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
+ bool FileNotFoundOrEmpty(string path)
+ {
+ var fileInfo = new FileInfo(path);
+
+ return !fileInfo.Exists || fileInfo.Length == 0;
+ }
+
bool IFileSink.EmitOrOverflow(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
@@ -93,10 +108,20 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
{
try
{
- _textFormatter.Format(logEvent, _output);
- _output.Flush();
+ if (_appendHeaderLogs)
+ {
+ var logFileHeaderCollection = _logFileHeaders.Invoke();
+
+ foreach (var item in logFileHeaderCollection)
+ AppendToOutput(item);
+
+ _appendHeaderLogs = false;
+ }
+
+ AppendToOutput(logEvent);
+
var bytes = _writeBuffer.GetBuffer();
- var length = (int) _writeBuffer.Length;
+ var length = (int)_writeBuffer.Length;
if (length > _fileStreamBufferLength)
{
var oldOutput = _fileOutput;
@@ -141,6 +166,12 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
}
}
+ private void AppendToOutput(LogEvent logEvent)
+ {
+ _textFormatter.Format(logEvent, _output);
+ _output.Flush();
+ }
+
///
/// Emit the provided log event to the sink.
///
diff --git a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs
index a779bda..82e8a5d 100644
--- a/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs
+++ b/src/Serilog.Sinks.File/Sinks/File/SharedFileSink.OSMutex.cs
@@ -15,6 +15,7 @@
#if OS_MUTEX
using System;
+using System.Collections.Generic;
using System.IO;
using System.Text;
using Serilog.Core;
@@ -35,6 +36,8 @@ public sealed class SharedFileSink : IFileSink, IDisposable
readonly ITextFormatter _textFormatter;
readonly long? _fileSizeLimitBytes;
readonly object _syncRoot = new object();
+ readonly Func> _logFileHeaders;
+ bool _appendHeaderLogs = false;
const string MutexNameSuffix = ".serilog";
const int MutexWaitTimeout = 10000;
@@ -47,10 +50,11 @@ public sealed class SharedFileSink : IFileSink, IDisposable
/// 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.
+ /// This action calls when a new log file created. It enables you to write any header text to the log file.
/// Configuration object allowing method chaining.
/// The file will be written using the UTF-8 character set.
///
- public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null)
+ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeLimitBytes, Encoding encoding = null, Func> logFileHeaders = null)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
@@ -59,6 +63,7 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
_textFormatter = textFormatter;
_fileSizeLimitBytes = fileSizeLimitBytes;
+ _logFileHeaders = logFileHeaders;
var directory = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
@@ -66,12 +71,22 @@ public SharedFileSink(string path, ITextFormatter textFormatter, long? fileSizeL
Directory.CreateDirectory(directory);
}
+ if (logFileHeaders != null && FileNotFoundOrEmpty(path))
+ _appendHeaderLogs = true;
+
var mutexName = Path.GetFullPath(path).Replace(Path.DirectorySeparatorChar, ':') + MutexNameSuffix;
_mutex = new Mutex(false, mutexName);
_underlyingStream = System.IO.File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
_output = new StreamWriter(_underlyingStream, encoding ?? new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
+ bool FileNotFoundOrEmpty(string path)
+ {
+ var fileInfo = new FileInfo(path);
+
+ return !fileInfo.Exists || fileInfo.Length == 0;
+ }
+
bool IFileSink.EmitOrOverflow(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
@@ -90,9 +105,18 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
return false;
}
- _textFormatter.Format(logEvent, _output);
- _output.Flush();
- _underlyingStream.Flush();
+ if (_appendHeaderLogs)
+ {
+ var logFileHeaderCollection = _logFileHeaders.Invoke();
+
+ foreach (var item in logFileHeaderCollection)
+ AppendToOutput(item);
+
+ _appendHeaderLogs = false;
+ }
+
+ AppendToOutput(logEvent);
+
return true;
}
finally
@@ -102,6 +126,13 @@ bool IFileSink.EmitOrOverflow(LogEvent logEvent)
}
}
+ private void AppendToOutput(LogEvent logEvent)
+ {
+ _textFormatter.Format(logEvent, _output);
+ _output.Flush();
+ _underlyingStream.Flush();
+ }
+
///
/// Emit the provided log event to the sink.
///