Skip to content

New logFileHeaders parameter added to configuration method #95

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

Closed
wants to merge 1 commit into from
Closed
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
44 changes: 27 additions & 17 deletions src/Serilog.Sinks.File/FileLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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

Expand Down Expand Up @@ -140,6 +141,7 @@ public static LoggerConfiguration File(
/// <param name="retainedFileCountLimit">The maximum number of log files that will be retained,
/// including the current log file. For unlimited retention, pass null. The default is 31.</param>
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
/// <param name="logFileHeaders">This action calls when a new log file created. It enables you to write any header text to the log file.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
Expand All @@ -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<IEnumerable<LogEvent>> logFileHeaders = null)
{
if (sinkConfiguration == null) throw new ArgumentNullException(nameof(sinkConfiguration));
if (path == null) throw new ArgumentNullException(nameof(path));
Expand All @@ -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);
}

/// <summary>
Expand All @@ -174,7 +177,7 @@ public static LoggerConfiguration File(
/// <param name="sinkConfiguration">Logger sink configuration.</param>
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding)"/>
/// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan?, RollingInterval, bool, int?, Encoding, Func{IEnumerable{LogEvent}})"/>
/// and specify the outputTemplate parameter instead.
/// </param>
/// <param name="path">Path to the file.</param>
Expand All @@ -195,6 +198,7 @@ public static LoggerConfiguration File(
/// <param name="retainedFileCountLimit">The maximum number of log files that will be retained,
/// including the current log file. For unlimited retention, pass null. The default is 31.</param>
/// <param name="encoding">Character encoding used to write the text file. The default is UTF-8 without BOM.</param>
/// <param name="logFileHeaders">This action calls when a new log file created. It enables you to write any header text to the log file.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
Expand All @@ -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<IEnumerable<LogEvent>> 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);
}

/// <summary>
Expand All @@ -228,6 +233,7 @@ public static LoggerConfiguration File(
/// <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:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}".</param>
/// <param name="logFileHeaders">This action calls when a new log file created. It enables you to write any header text to the log file.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
Expand All @@ -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<IEnumerable<LogEvent>> 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);
}

/// <summary>
Expand All @@ -252,25 +259,27 @@ public static LoggerConfiguration File(
/// <param name="sinkConfiguration">Logger sink configuration.</param>
/// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
/// text for the file. If control of regular text formatting is required, use the other
/// overload of <see cref="File(LoggerAuditSinkConfiguration, string, LogEventLevel, string, IFormatProvider, LoggingLevelSwitch)"/>
/// overload of <see cref="File(LoggerAuditSinkConfiguration, string, LogEventLevel, string, IFormatProvider, LoggingLevelSwitch, Func{IEnumerable{LogEvent}})"/>
/// and specify the outputTemplate parameter instead.
/// </param>
/// <param name="path">Path to the file.</param>
/// <param name="restrictedToMinimumLevel">The minimum level for
/// 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="logFileHeaders">This action calls when a new log file created. It enables you to write any header text to the log file.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
public static LoggerConfiguration File(
this LoggerAuditSinkConfiguration sinkConfiguration,
ITextFormatter formatter,
string path,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
Func<IEnumerable<LogEvent>> 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(
Expand All @@ -287,7 +296,8 @@ static LoggerConfiguration ConfigureFile(
Encoding encoding,
RollingInterval rollingInterval,
bool rollOnFileSizeLimit,
int? retainedFileCountLimit)
int? retainedFileCountLimit,
Func<IEnumerable<LogEvent>> logFileHeaders)
{
if (addSink == null) throw new ArgumentNullException(nameof(addSink));
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
Expand All @@ -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
{
Expand All @@ -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
}
Expand Down
44 changes: 37 additions & 7 deletions src/Serilog.Sinks.File/Sinks/File/FileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -33,6 +34,8 @@ public sealed class FileSink : IFileSink, IDisposable
readonly bool _buffered;
readonly object _syncRoot = new object();
readonly WriteCountingStream _countingStreamWrapper;
readonly Func<IEnumerable<LogEvent>> _logFileHeaders;
bool _appendHeaderLogs = false;

/// <summary>Construct a <see cref="FileSink"/>.</summary>
/// <param name="path">Path to the file.</param>
Expand All @@ -43,10 +46,11 @@ public sealed class FileSink : IFileSink, IDisposable
/// <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>
/// <param name="logFileHeaders">This action calls when a new log file created. It enables you to write any header text to the log file.</param>
/// <returns>Configuration object allowing method chaining.</returns>
/// <remarks>The file will be written using the UTF-8 character set.</remarks>
/// <exception cref="IOException"></exception>
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<IEnumerable<LogEvent>> logFileHeaders = null)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (textFormatter == null) throw new ArgumentNullException(nameof(textFormatter));
Expand All @@ -55,13 +59,17 @@ 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))
{
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)
{
Expand All @@ -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));
Expand All @@ -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();
}

/// <summary>
/// Emit the provided log event to the sink.
/// </summary>
/// <param name="logEvent">The log event to write.</param>
public void Emit(LogEvent logEvent)
{
((IFileSink) this).EmitOrOverflow(logEvent);
((IFileSink)this).EmitOrOverflow(logEvent);
}

/// <inheritdoc />
Expand Down
20 changes: 12 additions & 8 deletions src/Serilog.Sinks.File/Sinks/File/RollingFileSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -35,6 +36,7 @@ sealed class RollingFileSink : ILogEventSink, IFlushableFileSink, IDisposable
readonly bool _buffered;
readonly bool _shared;
readonly bool _rollOnFileSizeLimit;
readonly Func<IEnumerable<LogEvent>> _logFileHeaders;

readonly object _syncRoot = new object();
bool _isDisposed;
Expand All @@ -50,7 +52,8 @@ public RollingFileSink(string path,
bool buffered,
bool shared,
RollingInterval rollingInterval,
bool rollOnFileSizeLimit)
bool rollOnFileSizeLimit,
Func<IEnumerable<LogEvent>> 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");
Expand All @@ -64,6 +67,7 @@ public RollingFileSink(string path,
_buffered = buffered;
_shared = shared;
_rollOnFileSizeLimit = rollOnFileSizeLimit;
_logFileHeaders = logFileHeaders;
}

public void Emit(LogEvent logEvent)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading