Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
824c4d3
structured logger formatter prototype
maryamariyan Apr 15, 2020
833b53d
add ref to Linq + add public apis
maryamariyan Apr 15, 2020
1f02cdc
New APIs for Structured Log Formatting
maryamariyan May 12, 2020
8ecb51e
Apply review feedback
maryamariyan May 14, 2020
ed1f429
nit cleanup
maryamariyan May 14, 2020
4516d5a
- messages can color vars in messages (Compact)
maryamariyan May 15, 2020
ae2e2fa
TODO
maryamariyan May 15, 2020
b89910d
Rename to XConsoleLog...
maryamariyan May 15, 2020
ecf8bdd
FormatterNames const not instance properties
maryamariyan May 15, 2020
fc946f0
add AddCompact and other helpers
maryamariyan May 15, 2020
8ebc659
Rename AddCompactFormatter for short?
maryamariyan May 15, 2020
5141aba
Revert "Rename AddCompactFormatter for short?"
maryamariyan May 21, 2020
6d04fb4
Add back deprecated APIs
maryamariyan May 21, 2020
b9a004e
exception messaging in compact remains single line
maryamariyan May 21, 2020
44906fb
cleanup
maryamariyan May 26, 2020
bdb4244
rename default to colored
maryamariyan May 26, 2020
d2e187a
deprecate ConsoleLoggerFormat
maryamariyan May 26, 2020
bc056e2
minor rename
maryamariyan May 26, 2020
6ffc7e7
Rename back to default formatter
maryamariyan May 27, 2020
58d3e88
triple slash comments on new helpers
maryamariyan May 28, 2020
f514a8c
slight impl fixup
maryamariyan May 28, 2020
c4bc2ef
ConsoleLoggerProvider: keep ctor not deprecate
maryamariyan May 28, 2020
0b41bed
Added triple slash comments
maryamariyan May 28, 2020
fa81978
corner cases
maryamariyan May 29, 2020
22d2eff
version 3 log formatter
maryamariyan Jun 11, 2020
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
Next Next commit
structured logger formatter prototype
  • Loading branch information
maryamariyan committed Jun 11, 2020
commit 824c4d3f96fa5fe8a54b6735e861317db9f67095
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,19 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.Text;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Extensions.Logging.Console
{
internal class ConsoleLogger : ILogger
{
private static readonly string _loglevelPadding = ": ";
private static readonly string _messagePadding;
private static readonly string _newLineWithMessagePadding;

// ConsoleColor does not have a value to specify the 'Default' color
private readonly ConsoleColor? DefaultConsoleColor = null;
private readonly IEnumerable<ILogFormatter> _formatters;

private readonly string _name;
private readonly ConsoleLoggerProcessor _queueProcessor;

[ThreadStatic]
private static StringBuilder _logBuilder;

static ConsoleLogger()
{
var logLevelString = GetLogLevelString(LogLevel.Information);
_messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length);
_newLineWithMessagePadding = Environment.NewLine + _messagePadding;
}

internal ConsoleLogger(string name, ConsoleLoggerProcessor loggerProcessor)
internal ConsoleLogger(string name, ConsoleLoggerProcessor loggerProcessor, IEnumerable<ILogFormatter> formatters)
{
if (name == null)
{
Expand All @@ -39,6 +24,7 @@ internal ConsoleLogger(string name, ConsoleLoggerProcessor loggerProcessor)

_name = name;
_queueProcessor = loggerProcessor;
_formatters = formatters;
}

internal IExternalScopeProvider ScopeProvider { get; set; }
Expand Down Expand Up @@ -67,280 +53,17 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except

public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
{
var format = Options.Format;
Debug.Assert(format >= ConsoleLoggerFormat.Default && format <= ConsoleLoggerFormat.Systemd);

var logBuilder = _logBuilder;
_logBuilder = null;

if (logBuilder == null)
{
logBuilder = new StringBuilder();
}

LogMessageEntry entry;
if (format == ConsoleLoggerFormat.Default)
{
entry = CreateDefaultLogMessage(logBuilder, logLevel, logName, eventId, message, exception);
}
else if (format == ConsoleLoggerFormat.Systemd)
{
entry = CreateSystemdLogMessage(logBuilder, logLevel, logName, eventId, message, exception);
}
else
{
entry = default;
}
var formatter = _formatters.Single(f => f.Name == (Options.Formatter ?? Options.Format.ToString()));
var entry = formatter.Format(logLevel, logName, eventId, message, exception);
_queueProcessor.EnqueueMessage(entry);

logBuilder.Clear();
if (logBuilder.Capacity > 1024)
{
logBuilder.Capacity = 1024;
}
_logBuilder = logBuilder;
}

private LogMessageEntry CreateDefaultLogMessage(StringBuilder logBuilder, LogLevel logLevel, string logName, int eventId, string message, Exception exception)
{
// Example:
// INFO: ConsoleApp.Program[10]
// Request received

var logLevelColors = GetLogLevelConsoleColors(logLevel);
var logLevelString = GetLogLevelString(logLevel);
// category and event id
logBuilder.Append(_loglevelPadding);
logBuilder.Append(logName);
logBuilder.Append('[');
logBuilder.Append(eventId);
logBuilder.AppendLine("]");

// scope information
GetScopeInformation(logBuilder, multiLine: true);

if (!string.IsNullOrEmpty(message))
{
// message
logBuilder.Append(_messagePadding);

var len = logBuilder.Length;
logBuilder.AppendLine(message);
logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
}

// Example:
// System.InvalidOperationException
// at Namespace.Class.Function() in File:line X
if (exception != null)
{
// exception message
logBuilder.AppendLine(exception.ToString());
}

string timestamp = null;
var timestampFormat = Options.TimestampFormat;
if (timestampFormat != null)
{
var dateTime = GetCurrentDateTime();
timestamp = dateTime.ToString(timestampFormat);
}

return new LogMessageEntry(
message: logBuilder.ToString(),
timeStamp: timestamp,
levelString: logLevelString,
levelBackground: logLevelColors.Background,
levelForeground: logLevelColors.Foreground,
messageColor: DefaultConsoleColor,
logAsError: logLevel >= Options.LogToStandardErrorThreshold
);
}

private LogMessageEntry CreateSystemdLogMessage(StringBuilder logBuilder, LogLevel logLevel, string logName, int eventId, string message, 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.
// Example:
// <6>ConsoleApp.Program[10] Request received

// loglevel
var logLevelString = GetSyslogSeverityString(logLevel);
logBuilder.Append(logLevelString);

// timestamp
var timestampFormat = Options.TimestampFormat;
if (timestampFormat != null)
{
var dateTime = GetCurrentDateTime();
logBuilder.Append(dateTime.ToString(timestampFormat));
}

// category and event id
logBuilder.Append(logName);
logBuilder.Append('[');
logBuilder.Append(eventId);
logBuilder.Append(']');

// scope information
GetScopeInformation(logBuilder, multiLine: false);

// message
if (!string.IsNullOrEmpty(message))
{
logBuilder.Append(' ');
// message
AppendAndReplaceNewLine(logBuilder, message);
}

// exception
// System.InvalidOperationException at Namespace.Class.Function() in File:line X
if (exception != null)
{
logBuilder.Append(' ');
AppendAndReplaceNewLine(logBuilder, exception.ToString());
}

// newline delimiter
logBuilder.Append(Environment.NewLine);

return new LogMessageEntry(
message: logBuilder.ToString(),
logAsError: logLevel >= Options.LogToStandardErrorThreshold
);

static void AppendAndReplaceNewLine(StringBuilder sb, string message)
{
var len = sb.Length;
sb.Append(message);
sb.Replace(Environment.NewLine, " ", len, message.Length);
}
}

private DateTime GetCurrentDateTime()
{
return Options.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
}

public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}

public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;

private static string GetLogLevelString(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace:
return "trce";
case LogLevel.Debug:
return "dbug";
case LogLevel.Information:
return "info";
case LogLevel.Warning:
return "warn";
case LogLevel.Error:
return "fail";
case LogLevel.Critical:
return "crit";
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}

private static string GetSyslogSeverityString(LogLevel logLevel)
{
// 'Syslog Message Severities' from https://tools.ietf.org/html/rfc5424.
switch (logLevel)
{
case LogLevel.Trace:
case LogLevel.Debug:
return "<7>"; // debug-level messages
case LogLevel.Information:
return "<6>"; // informational messages
case LogLevel.Warning:
return "<4>"; // warning conditions
case LogLevel.Error:
return "<3>"; // error conditions
case LogLevel.Critical:
return "<2>"; // critical conditions
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}

private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
{
if (Options.DisableColors)
{
return new ConsoleColors(null, null);
}

// We must explicitly set the background color if we are setting the foreground color,
// since just setting one can look bad on the users console.
switch (logLevel)
{
case LogLevel.Critical:
return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red);
case LogLevel.Error:
return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red);
case LogLevel.Warning:
return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black);
case LogLevel.Information:
return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black);
case LogLevel.Debug:
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
case LogLevel.Trace:
return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
default:
return new ConsoleColors(DefaultConsoleColor, DefaultConsoleColor);
}
}

private void GetScopeInformation(StringBuilder stringBuilder, bool multiLine)
{
var scopeProvider = ScopeProvider;
if (Options.IncludeScopes && scopeProvider != null)
{
var initialLength = stringBuilder.Length;

scopeProvider.ForEachScope((scope, state) =>
{
var (builder, paddAt) = state;
var padd = paddAt == builder.Length;
if (padd)
{
builder.Append(_messagePadding);
builder.Append("=> ");
}
else
{
builder.Append(" => ");
}
builder.Append(scope);
}, (stringBuilder, multiLine ? initialLength : -1));

if (stringBuilder.Length > initialLength && multiLine)
{
stringBuilder.AppendLine();
}
}
}

private readonly struct ConsoleColors
{
public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
{
Foreground = foreground;
Background = background;
}

public ConsoleColor? Foreground { get; }

public ConsoleColor? Background { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
{
builder.AddConfiguration();

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILogFormatter, DefaultLogFormatter>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILogFormatter, SystemdLogFormatter>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services);
return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public enum ConsoleLoggerFormat
/// <summary>
/// Produces messages in a format suitable for console output to the systemd journal.
/// </summary>
Systemd,
Systemd
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ public ConsoleLoggerFormat Format
}

/// <summary>
/// Gets or sets value indicating the minimum level of messages that would get written to <c>Console.Error</c>.
///
/// </summary>
public string Formatter { get; set; }

/// <summary>
/// Gets or sets value indicating the minimum level of messaged that would get written to <c>Console.Error</c>.
/// </summary>
public LogLevel LogToStandardErrorThreshold { get; set; } = LogLevel.None;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Options;

Expand All @@ -18,6 +19,7 @@ public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;
private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;
private readonly ConsoleLoggerProcessor _messageQueue;
private readonly IEnumerable<ILogFormatter> _formatters;

private IDisposable _optionsReloadToken;
private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;
Expand All @@ -26,10 +28,12 @@ public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
/// Creates an instance of <see cref="ConsoleLoggerProvider"/>.
/// </summary>
/// <param name="options">The options to create <see cref="ConsoleLogger"/> instances with.</param>
public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options)
/// <param name="formatters"></param>
public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options, IEnumerable<ILogFormatter> formatters)
{
_options = options;
_loggers = new ConcurrentDictionary<string, ConsoleLogger>();
_formatters = formatters;

ReloadLoggerOptions(options.CurrentValue);
_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);
Expand Down Expand Up @@ -58,7 +62,7 @@ private void ReloadLoggerOptions(ConsoleLoggerOptions options)
/// <inheritdoc />
public ILogger CreateLogger(string name)
{
return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue)
return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue, _formatters)
{
Options = _options.CurrentValue,
ScopeProvider = _scopeProvider
Expand Down
Loading