Skip to content

Commit

Permalink
Reduce generic specialized code in console formatters (dotnet#101474)
Browse files Browse the repository at this point in the history
Move code that doesn't need to be generic to a non-generic method. Reduces the size of the Stage 2 goldilocks app by 1.4% since these methods are rather large (especially the `JsonConsoleFormatter` one) and we have many specializations.
  • Loading branch information
MichalStrehovsky authored and michaelgsharp committed May 8, 2024
1 parent d60a3ac commit d853374
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP
{
return;
}
LogLevel logLevel = logEntry.LogLevel;
string category = logEntry.Category;
int eventId = logEntry.EventId.Id;
Exception? exception = logEntry.Exception;

// We extract most of the work into a non-generic method to save code size. If this was left in the generic
// method, we'd get generic specialization for all TState parameters, but that's unnecessary.
WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception,
logEntry.State != null, logEntry.State?.ToString(), logEntry.State as IReadOnlyCollection<KeyValuePair<string, object>>);
}

private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel,
string category, int eventId, Exception? exception, bool hasState, string? stateMessage, IReadOnlyCollection<KeyValuePair<string, object>>? stateProperties)
{
const int DefaultBufferSize = 1024;
using (var output = new PooledByteBufferWriter(DefaultBufferSize))
{
Expand All @@ -48,21 +54,21 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP
DateTimeOffset dateTimeOffset = FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now;
writer.WriteString("Timestamp", dateTimeOffset.ToString(timestampFormat));
}
writer.WriteNumber(nameof(logEntry.EventId), eventId);
writer.WriteString(nameof(logEntry.LogLevel), GetLogLevelString(logLevel));
writer.WriteString(nameof(logEntry.Category), category);
writer.WriteNumber(nameof(LogEntry<object>.EventId), eventId);
writer.WriteString(nameof(LogEntry<object>.LogLevel), GetLogLevelString(logLevel));
writer.WriteString(nameof(LogEntry<object>.Category), category);
writer.WriteString("Message", message);

if (exception != null)
{
writer.WriteString(nameof(Exception), exception.ToString());
}

if (logEntry.State != null)
if (hasState)
{
writer.WriteStartObject(nameof(logEntry.State));
writer.WriteString("Message", logEntry.State.ToString());
if (logEntry.State is IReadOnlyCollection<KeyValuePair<string, object>> stateProperties)
writer.WriteStartObject(nameof(LogEntry<object>.State));
writer.WriteString("Message", stateMessage);
if (stateProperties != null)
{
foreach (KeyValuePair<string, object> item in stateProperties)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,15 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP
{
return;
}
LogLevel logLevel = logEntry.LogLevel;

// We extract most of the work into a non-generic method to save code size. If this was left in the generic
// method, we'd get generic specialization for all TState parameters, but that's unnecessary.
WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.EventId.Id, logEntry.Exception, logEntry.Category);
}

private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel,
int eventId, Exception? exception, string category)
{
ConsoleColors logLevelColors = GetLogLevelConsoleColors(logLevel);
string logLevelString = GetLogLevelString(logLevel);

Expand All @@ -70,22 +78,16 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP
{
textWriter.WriteColoredMessage(logLevelString, logLevelColors.Background, logLevelColors.Foreground);
}
CreateDefaultLogMessage(textWriter, logEntry, message, scopeProvider);
}

private void CreateDefaultLogMessage<TState>(TextWriter textWriter, in LogEntry<TState> logEntry, string message, IExternalScopeProvider? scopeProvider)
{
bool singleLine = FormatterOptions.SingleLine;
int eventId = logEntry.EventId.Id;
Exception? exception = logEntry.Exception;

// Example:
// info: ConsoleApp.Program[10]
// Request received

// category and event id
textWriter.Write(LoglevelPadding);
textWriter.Write(logEntry.Category);
textWriter.Write(category);
textWriter.Write('[');

#if NETCOREAPP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeP
{
return;
}
LogLevel logLevel = logEntry.LogLevel;
string category = logEntry.Category;
int eventId = logEntry.EventId.Id;
Exception? exception = logEntry.Exception;

// We extract most of the work into a non-generic method to save code size. If this was left in the generic
// method, we'd get generic specialization for all TState parameters, but that's unnecessary.
WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception);
}

private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel, string category,
int eventId, Exception? exception)
{
// systemd reads messages from standard out line-by-line in a '<pri>message' format.
// newline characters are treated as message delimiters, so we must replace them.
// Messages longer than the journal LineMax setting (default: 48KB) are cropped.
Expand Down

0 comments on commit d853374

Please sign in to comment.