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()
{
}
}
}
}