using System; using Microsoft.Extensions.Logging; using AWS.Logger.Core; using System.Text; using System.Collections.Generic; namespace AWS.Logger.AspNetCore { /// /// Implementation of the Microsoft.Extensions.Logging.ILogger. /// public class AWSLogger : ILogger { private readonly string _categoryName; private readonly IAWSLoggerCore _core; private readonly Func _filter; private readonly Func _customFormatter; private bool _includeScopes; /// /// Prefix log messages with scopes created with ILogger.BeginScope /// public bool IncludeScopes { get { return this._includeScopes; } set { if(value && this.ScopeProvider == NullExternalScopeProvider.Instance) { this.ScopeProvider = new LoggerExternalScopeProvider(); } this._includeScopes = value; } } /// /// Include log level in log message /// public bool IncludeLogLevel { get; set; } = Constants.IncludeLogLevelDefault; /// /// Include category in log message /// public bool IncludeCategory { get; set; } = Constants.IncludeCategoryDefault; /// /// Include event id in log message /// public bool IncludeEventId { get; set; } = Constants.IncludeEventIdDefault; /// /// Include new line in log message /// public bool IncludeNewline { get; set; } = Constants.IncludeNewlineDefault; /// /// Include exception in log message /// public bool IncludeException { get; set; } = Constants.IncludeExceptionDefault; internal IExternalScopeProvider ScopeProvider { get; set; } = NullExternalScopeProvider.Instance; /// /// Construct an instance of AWSLogger /// /// The category name for the logger which can be used for filtering. /// The core logger that is used to send messages to AWS. /// Filter function that will only allow messages to be sent to AWS if it returns true. If the value is null all messages are sent. /// A custom formatter which accepts a LogLevel, a state, and an exception and returns the formatted log message. public AWSLogger(string categoryName, IAWSLoggerCore core, Func filter, Func customFormatter = null) { _categoryName = categoryName; _core = core; _filter = filter; _customFormatter = customFormatter; // This is done in the constructor to ensure the logic in the setter is run during initialization. this.IncludeScopes = Constants.IncludeScopesDefault; } /// public IDisposable BeginScope(TState state) { if (state == null) throw new ArgumentNullException(nameof(state)); return ScopeProvider?.Push(state) ?? new NoOpDisposable(); } /// /// Test to see if the log level is enabled for logging. This is evaluated by running the filter function passed into the constructor. /// /// /// public bool IsEnabled(LogLevel logLevel) { if (_filter == null) return true; return _filter(_categoryName, logLevel); } /// /// Log the message /// /// /// /// /// /// /// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { if (!IsEnabled(logLevel)) return; string message; if (_customFormatter == null) { if (formatter == null) throw new ArgumentNullException(nameof(formatter)); var messageText = formatter(state, exception); // If neither a message nor an exception are provided, don't log anything (there's nothing to log, after all). if (string.IsNullOrEmpty(messageText) && exception == null) return; // Format of the logged text, optional components are in {} // {[LogLevel] }{ Scopes: => }{Category: }{EventId: }MessageText {Exception}{\n} var messageBuilder = new StringBuilder(); Action addToBuilder = token => { if (string.IsNullOrEmpty(token)) return; if (messageBuilder.Length > 0) messageBuilder.Append(" "); messageBuilder.Append(token); }; if (IncludeLogLevel) { addToBuilder($"[{logLevel}]"); } GetScopeInformation(messageBuilder); if (IncludeCategory) { addToBuilder($"{_categoryName}:"); } if (IncludeEventId) { addToBuilder($"[{eventId}]:"); } addToBuilder(messageText); if (IncludeException) { addToBuilder($"{exception}"); } if (IncludeNewline) { messageBuilder.Append(Environment.NewLine); } message = messageBuilder.ToString(); } else { message = _customFormatter(logLevel, state, exception); // If neither a message nor an exception are provided, don't log anything (there's nothing to log, after all). if (string.IsNullOrEmpty(message) && exception == null) return; } _core.AddMessage(message); } private void GetScopeInformation(StringBuilder messageBuilder) { var scopeProvider = ScopeProvider; if (IncludeScopes && scopeProvider != null) { var initialLength = messageBuilder.Length; scopeProvider.ForEachScope((scope, builder) => { if(messageBuilder.Length > 0) { messageBuilder.Append(" "); } messageBuilder.Append(scope.ToString()); }, (messageBuilder)); if (messageBuilder.Length > initialLength) { messageBuilder.Append(" =>"); } } } private class NoOpDisposable : IDisposable { public void Dispose() { } } } }