netcore3.0 Logging 日志系统(二)

上一篇介绍了netcore的日志系统,接下来看下netcore里面提供了哪写日志功能:

一、 DebugLogger

  

/// <summary>
    /// A logger that writes messages in the debug output window only when a debugger is attached.
    /// </summary>
    internal partial class DebugLogger : ILogger
    {
        private readonly string _name;

        /// <summary>
        /// Initializes a new instance of the <see cref="DebugLogger"/> class.
        /// </summary>
        /// <param name="name">The name of the logger.</param>
        public DebugLogger(string name)
        {
            _name = name;
        }

        /// <inheritdoc />
        public IDisposable BeginScope<TState>(TState state)
        {
            return NullScope.Instance;
        }

        /// <inheritdoc />
        public bool IsEnabled(LogLevel logLevel)
        {
            // If the filter is null, everything is enabled
            // unless the debugger is not attached
            return Debugger.IsAttached && logLevel != LogLevel.None;
        }

        /// <inheritdoc />
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }

            var message = formatter(state, exception);

            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            message = $"{ logLevel }: {message}";

            if (exception != null)
            {
                message += Environment.NewLine + Environment.NewLine + exception;
            }

            DebugWriteLine(message, _name);
        }
    }
/// <summary>
    /// The provider for the <see cref="DebugLogger"/>.
    /// </summary>
    [ProviderAlias("Debug")]
    public class DebugLoggerProvider : ILoggerProvider
    {
        /// <inheritdoc />
        public ILogger CreateLogger(string name)
        {
            return new DebugLogger(name);
        }

        public void Dispose()
        {
        }
    }

扩展方法:

/// <summary>
    /// Extension methods for the <see cref="ILoggerFactory"/> class.
    /// </summary>
    public static class DebugLoggerFactoryExtensions
    {
        /// <summary>
        /// Adds a debug logger named 'Debug' to the factory.
        /// </summary>
        /// <param name="builder">The extension method argument.</param>
        public static ILoggingBuilder AddDebug(this ILoggingBuilder builder)
        {
            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DebugLoggerProvider>());

            return builder;
        }
    }

 

二、 ConsoleLogger

  

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 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)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }

            _name = name;
            _queueProcessor = loggerProcessor;
        }

        internal IExternalScopeProvider ScopeProvider { get; set; }

        internal ConsoleLoggerOptions Options { get; set; }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }

            var message = formatter(state, exception);

            if (!string.IsNullOrEmpty(message) || exception != null)
            {
                WriteMessage(logLevel, _name, eventId.Id, message, exception);
            }
        }

        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;
            }
            _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());
            }

            var timestampFormat = Options.TimestampFormat;

            return new LogMessageEntry(
                message: logBuilder.ToString(),
                timeStamp: timestampFormat != null ? DateTime.Now.ToString(timestampFormat) : null,
                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)
            {
                logBuilder.Append(DateTime.Now.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);
            }
        }

        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; }
        }
    }
/// <summary>
    /// A provider of <see cref="ConsoleLogger"/> instances.
    /// </summary>
    [ProviderAlias("Console")]
    public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
    {
        private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;
        private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;
        private readonly ConsoleLoggerProcessor _messageQueue;

        private IDisposable _optionsReloadToken;
        private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;

        /// <summary>
        /// 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)
        {
            _options = options;
            _loggers = new ConcurrentDictionary<string, ConsoleLogger>();

            ReloadLoggerOptions(options.CurrentValue);
            _optionsReloadToken = _options.OnChange(ReloadLoggerOptions);

            _messageQueue = new ConsoleLoggerProcessor();
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                _messageQueue.Console = new WindowsLogConsole();
                _messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);
            }
            else
            {
                _messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());
                _messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));
            }
        }

        private void ReloadLoggerOptions(ConsoleLoggerOptions options)
        {
            foreach (var logger in _loggers)
            {
                logger.Value.Options = options;
            }
        }

        /// <inheritdoc />
        public ILogger CreateLogger(string name)
        {
            return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue)
            {
                Options = _options.CurrentValue,
                ScopeProvider = _scopeProvider
            });
        }

        /// <inheritdoc />
        public void Dispose()
        {
            _optionsReloadToken?.Dispose();
            _messageQueue.Dispose();
        }

        /// <inheritdoc />
        public void SetScopeProvider(IExternalScopeProvider scopeProvider)
        {
            _scopeProvider = scopeProvider;

            foreach (var logger in _loggers)
            {
                logger.Value.ScopeProvider = _scopeProvider;
            }

        }
    }

扩展方法:

public static class ConsoleLoggerExtensions
    {
        /// <summary>
        /// Adds a console logger named 'Console' to the factory.
        /// </summary>
        /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
        public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
        {
            builder.AddConfiguration();

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
            LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services);
            return builder;
        }

        /// <summary>
        /// Adds a console logger named 'Console' to the factory.
        /// </summary>
        /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
        /// <param name="configure">A delegate to configure the <see cref="ConsoleLogger"/>.</param>
        public static ILoggingBuilder AddConsole(this ILoggingBuilder builder, Action<ConsoleLoggerOptions> configure)
        {
            if (configure == null)
            {
                throw new ArgumentNullException(nameof(configure));
            }

            builder.AddConsole();
            builder.Services.Configure(configure);

            return builder;
        }
    }

 

三、 EventLogLogger

  

/// <summary>
    /// A logger that writes messages to Windows Event Log.
    /// </summary>
    internal class EventLogLogger : ILogger
    {
        private readonly string _name;
        private readonly EventLogSettings _settings;
        private readonly IExternalScopeProvider _externalScopeProvider;

        private const string ContinuationString = "...";
        private readonly int _beginOrEndMessageSegmentSize;
        private readonly int _intermediateMessageSegmentSize;

        /// <summary>
        /// Initializes a new instance of the <see cref="EventLogLogger"/> class.
        /// </summary>
        /// <param name="name">The name of the logger.</param>
        /// <param name="settings">The <see cref="EventLogSettings"/>.</param>
        /// <param name="externalScopeProvider">The <see cref="IExternalScopeProvider"/>.</param>
        public EventLogLogger(string name, EventLogSettings settings, IExternalScopeProvider externalScopeProvider)
        {
            _name = name ?? throw new ArgumentNullException(nameof(name));
            _settings = settings ?? throw new ArgumentNullException(nameof(settings));

            _externalScopeProvider = externalScopeProvider;
            EventLog = settings.EventLog;

            // Examples:
            // 1. An error occu...
            // 2. ...esponse stream
            _beginOrEndMessageSegmentSize = EventLog.MaxMessageSize - ContinuationString.Length;

            // Example:
            // ...rred while writ...
            _intermediateMessageSegmentSize = EventLog.MaxMessageSize - 2 * ContinuationString.Length;
        }

        public IEventLog EventLog { get; }

        /// <inheritdoc />
        public IDisposable BeginScope<TState>(TState state)
        {
            return _externalScopeProvider?.Push(state);
        }

        /// <inheritdoc />
        public bool IsEnabled(LogLevel logLevel)
        {
            return logLevel != LogLevel.None &&
                (_settings.Filter == null || _settings.Filter(_name, logLevel));
        }

        /// <inheritdoc />
        public void Log<TState>(
            LogLevel logLevel,
            EventId eventId,
            TState state,
            Exception exception,
            Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }

            var message = formatter(state, exception);

            if (string.IsNullOrEmpty(message))
            {
                return;
            }

            var builder = new StringBuilder()
                            .Append("Category: ")
                            .AppendLine(_name)
                            .Append("EventId: ")
                            .Append(eventId.Id)
                            .AppendLine();

            _externalScopeProvider?.ForEachScope((scope, sb) =>
            {
                if (scope is IEnumerable<KeyValuePair<string, object>> properties)
                {
                    foreach (var pair in properties)
                    {
                        sb.Append(pair.Key).Append(": ").AppendLine(pair.Value?.ToString());
                    }
                }
                else if (scope != null)
                {
                    sb.AppendLine(scope.ToString());
                }
            },
            builder);

            builder.AppendLine()
            .AppendLine(message);

            if (exception != null)
            {
                builder.AppendLine().AppendLine("Exception: ").Append(exception).AppendLine();
            }

            WriteMessage(builder.ToString(), GetEventLogEntryType(logLevel), EventLog.DefaultEventId ?? eventId.Id);
        }

        // category '0' translates to 'None' in event log
        private void WriteMessage(string message, EventLogEntryType eventLogEntryType, int eventId)
        {
            if (message.Length <= EventLog.MaxMessageSize)
            {
                EventLog.WriteEntry(message, eventLogEntryType, eventId, category: 0);
                return;
            }

            var startIndex = 0;
            string messageSegment = null;
            while (true)
            {
                // Begin segment
                // Example: An error occu...
                if (startIndex == 0)
                {
                    messageSegment = message.Substring(startIndex, _beginOrEndMessageSegmentSize) + ContinuationString;
                    startIndex += _beginOrEndMessageSegmentSize;
                }
                else
                {
                    // Check if rest of the message can fit within the maximum message size
                    // Example: ...esponse stream
                    if ((message.Length - (startIndex + 1)) <= _beginOrEndMessageSegmentSize)
                    {
                        messageSegment = ContinuationString + message.Substring(startIndex);
                        EventLog.WriteEntry(messageSegment, eventLogEntryType, eventId, category: 0);
                        break;
                    }
                    else
                    {
                        // Example: ...rred while writ...
                        messageSegment =
                            ContinuationString
                            + message.Substring(startIndex, _intermediateMessageSegmentSize)
                            + ContinuationString;
                        startIndex += _intermediateMessageSegmentSize;
                    }
                }

                EventLog.WriteEntry(messageSegment, eventLogEntryType, eventId, category: 0);
            }
        }

        private EventLogEntryType GetEventLogEntryType(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Information:
                case LogLevel.Debug:
                case LogLevel.Trace:
                    return EventLogEntryType.Information;
                case LogLevel.Warning:
                    return EventLogEntryType.Warning;
                case LogLevel.Critical:
                case LogLevel.Error:
                    return EventLogEntryType.Error;
                default:
                    return EventLogEntryType.Information;
            }
        }
    }
/// <summary>
    /// The provider for the <see cref="EventLogLogger"/>.
    /// </summary>
    [ProviderAlias("EventLog")]
    public class EventLogLoggerProvider : ILoggerProvider, ISupportExternalScope
    {
        internal readonly EventLogSettings _settings;

        private IExternalScopeProvider _scopeProvider;

        /// <summary>
        /// Initializes a new instance of the <see cref="EventLogLoggerProvider"/> class.
        /// </summary>
        public EventLogLoggerProvider()
            : this(settings: null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EventLogLoggerProvider"/> class.
        /// </summary>
        /// <param name="settings">The <see cref="EventLogSettings"/>.</param>
        public EventLogLoggerProvider(EventLogSettings settings)
        {
            _settings = settings ?? new EventLogSettings();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EventLogLoggerProvider"/> class.
        /// </summary>
        /// <param name="options">The <see cref="IOptions{EventLogSettings}"/>.</param>
        public EventLogLoggerProvider(IOptions<EventLogSettings> options)
            : this(options.Value)
        {
        }

        /// <inheritdoc />
        public ILogger CreateLogger(string name)
        {
            return new EventLogLogger(name, _settings, _scopeProvider);
        }

        /// <inheritdoc />
        public void Dispose()
        {
            if (_settings.EventLog is WindowsEventLog windowsEventLog)
            {
                windowsEventLog.DiagnosticsEventLog.Dispose();
            }
        }

        /// <inheritdoc />
        public void SetScopeProvider(IExternalScopeProvider scopeProvider)
        {
            _scopeProvider = scopeProvider;
        }
    }

扩展方法:

public static class EventLoggerFactoryExtensions
    {
        /// <summary>
        /// Adds an event logger named 'EventLog' to the factory.
        /// </summary>
        /// <param name="builder">The extension method argument.</param>
        /// <returns>The <see cref="ILoggingBuilder"/> so that additional calls can be chained.</returns>
        public static ILoggingBuilder AddEventLog(this ILoggingBuilder builder)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EventLogLoggerProvider>());

            return builder;
        }

        /// <summary>
        /// Adds an event logger. Use <paramref name="settings"/> to enable logging for specific <see cref="LogLevel"/>s.
        /// </summary>
        /// <param name="builder">The extension method argument.</param>
        /// <param name="settings">The <see cref="EventLogSettings"/>.</param>
        /// <returns>The <see cref="ILoggingBuilder"/> so that additional calls can be chained.</returns>
        public static ILoggingBuilder AddEventLog(this ILoggingBuilder builder, EventLogSettings settings)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider>(new EventLogLoggerProvider(settings)));

            return builder;
        }

        /// <summary>
        /// Adds an event logger. Use <paramref name="configure"/> to enable logging for specific <see cref="LogLevel"/>s.
        /// </summary>
        /// <param name="builder">The extension method argument.</param>
        /// <param name="configure">A delegate to configure the <see cref="EventLogSettings"/>.</param>
        /// <returns>The <see cref="ILoggingBuilder"/> so that additional calls can be chained.</returns>
        public static ILoggingBuilder AddEventLog(this ILoggingBuilder builder, Action<EventLogSettings> configure)
        {
            if (configure == null)
            {
                throw new ArgumentNullException(nameof(configure));
            }

            builder.AddEventLog();
            builder.Services.Configure(configure);

            return builder;
        }
    }

 

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 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)        {            if (name == null)            {                throw new ArgumentNullException(nameof(name));            }
            _name = name;            _queueProcessor = loggerProcessor;        }
        internal IExternalScopeProvider ScopeProvider { get; set; }
        internal ConsoleLoggerOptions Options { get; set; }
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)        {            if (!IsEnabled(logLevel))            {                return;            }
            if (formatter == null)            {                throw new ArgumentNullException(nameof(formatter));            }
            var message = formatter(state, exception);
            if (!string.IsNullOrEmpty(message) || exception != null)            {                WriteMessage(logLevel, _name, eventId.Id, message, exception);            }        }
        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;            }            _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());            }
            var timestampFormat = Options.TimestampFormat;
            return new LogMessageEntry(                message: logBuilder.ToString(),                timeStamp: timestampFormat != null ? DateTime.Now.ToString(timestampFormat) : null,                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)            {                logBuilder.Append(DateTime.Now.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);            }        }
        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; }        }    }

posted @ 2020-03-21 18:20  蓝平凡  阅读(576)  评论(0编辑  收藏  举报