Skip to content

Commit

Permalink
Reduce heap allocation of the default log message formatting logic in…
Browse files Browse the repository at this point in the history
… the FileLogger.Log (#74) -- extract all optimized code to StringLogEntryFormatter class, optimize GetFormattedLength, added more tests
  • Loading branch information
VitaliyMF committed Nov 18, 2024
1 parent ef7c0f9 commit 2396077
Show file tree
Hide file tree
Showing 11 changed files with 534 additions and 393 deletions.
80 changes: 0 additions & 80 deletions src/NReco.Logging.File/Extensions/DateTimeExtensions.cs

This file was deleted.

41 changes: 0 additions & 41 deletions src/NReco.Logging.File/Extensions/IntExtensions.cs

This file was deleted.

82 changes: 9 additions & 73 deletions src/NReco.Logging.File/FileLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
using System;
using System.Buffers;
using System.Text;

using Microsoft.Extensions.Logging;
using NReco.Logging.File.Extensions;

namespace NReco.Logging.File {

/// <summary>
Expand All @@ -40,19 +39,6 @@ public bool IsEnabled(LogLevel logLevel) {
return logLevel>=LoggerPrv.MinLevel;
}

string GetShortLogLevel(LogLevel logLevel) {
return logLevel switch {
LogLevel.Trace => "TRCE",
LogLevel.Debug => "DBUG",
LogLevel.Information => "INFO",
LogLevel.Warning => "WARN",
LogLevel.Error => "FAIL",
LogLevel.Critical => "CRIT",
LogLevel.None => "NONE",
_ => logLevel.ToString().ToUpper(),
};
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,

Check warning on line 42 in src/NReco.Logging.File/FileLogger.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'FileLogger.Log<TState>(LogLevel, EventId, TState, Exception, Func<TState, Exception, string>)'
Exception exception, Func<TState, Exception, string> formatter) {
if (!IsEnabled(logLevel)) {
Expand All @@ -74,66 +60,16 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
new LogMessage(logName, logLevel, eventId, message, exception)));
}
else {
const int MaxStackAllocatedBufferLength = 256;
DateTime timeStamp = LoggerPrv.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
var logMessageLength = CalculateLogMessageLength(timeStamp, eventId, message);
char[] charBuffer = null;
try {
Span<char> buffer = logMessageLength <= MaxStackAllocatedBufferLength
? stackalloc char[MaxStackAllocatedBufferLength]
: (charBuffer = ArrayPool<char>.Shared.Rent(logMessageLength));

FormatLogEntryDefault(buffer, timeStamp, message, logLevel, eventId, exception);
}
finally {
if (charBuffer is not null) {
ArrayPool<char>.Shared.Return(charBuffer);
}
}
LoggerPrv.WriteEntry(
Format.StringLogEntryFormatter.Instance.LowAllocLogEntryFormat(
logName,
LoggerPrv.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now,
logLevel,
eventId,
message,
exception));
}
}

private void FormatLogEntryDefault(Span<char> buffer, DateTime timeStamp, string message,
LogLevel logLevel, EventId eventId, Exception exception) {
// default formatting logic
using var logBuilder = new ValueStringBuilder(buffer);
if (!string.IsNullOrEmpty(message)) {
timeStamp.TryFormatO(logBuilder.RemainingRawChars, out var charsWritten);
logBuilder.AppendSpan(charsWritten);
logBuilder.Append('\t');
logBuilder.Append(GetShortLogLevel(logLevel));
logBuilder.Append("\t[");
logBuilder.Append(logName);
logBuilder.Append("]\t[");
if (eventId.Name is not null) {
logBuilder.Append(eventId.Name);
}
else {
eventId.Id.TryFormat(logBuilder.RemainingRawChars, out charsWritten);
logBuilder.AppendSpan(charsWritten);
}
logBuilder.Append("]\t");
logBuilder.Append(message);
}

if (exception != null) {
// exception message
logBuilder.Append(exception.ToString());
logBuilder.Append(Environment.NewLine);
}
LoggerPrv.WriteEntry(logBuilder.ToString());
}

private int CalculateLogMessageLength(DateTime timeStamp, EventId eventId, string message) {
return timeStamp.GetFormattedLength()
+ 1 /* '\t' */
+ 4 /* GetShortLogLevel */
+ 2 /* "\t[" */
+ (logName?.Length ?? 0)
+ 3 /* "]\t[" */
+ (eventId.Name?.Length ?? eventId.Id.GetFormattedLength())
+ 2 /* "]\t" */
+ (message?.Length ?? 0);
}
}
}
3 changes: 2 additions & 1 deletion src/NReco.Logging.File/FileLoggerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ internal void WriteMessage(string message, bool flush) {
/// Returns the index of a file or 0 if none found
/// </summary>
private int GetIndexFromFile(string baseLogFileName, string filename) {
#if NETSTANDARD
#if NETSTANDARD2_0
var baseFileNameOnly = Path.GetFileNameWithoutExtension(baseLogFileName);
var currentFileNameOnly = Path.GetFileNameWithoutExtension(filename);

Expand All @@ -367,6 +367,7 @@ private string GetFileFromIndex(string baseLogFileName, int index) {
var nextFileName = Path.GetFileNameWithoutExtension(baseLogFileName) + (index > 0 ? index.ToString() : "") + Path.GetExtension(baseLogFileName);
return Path.Combine(Path.GetDirectoryName(baseLogFileName), nextFileName);
#else
// Contact for ReadOnlySpan<char> is not available in both netstandard2.0 and netstandard2.1
var nextFileName = string.Concat(Path.GetFileNameWithoutExtension(baseLogFileName.AsSpan()), index > 0 ? index.ToString() : "", Path.GetExtension(baseLogFileName.AsSpan()));
return string.Concat(Path.Join(Path.GetDirectoryName(baseLogFileName.AsSpan()), nextFileName.AsSpan()));
#endif
Expand Down
Loading

0 comments on commit 2396077

Please sign in to comment.