using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using AWS.Logger.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace AWS.Logger.AspNetCore
{
///
/// Implementation of the ILoggerProvider which is used to create instances of ILogger.
///
public class AWSLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary();
private IExternalScopeProvider _scopeProvider;
private readonly IAWSLoggerCore _core;
private readonly AWSLoggerConfigSection _configSection;
private readonly Func _customFormatter;
private Func _customFilter;
// Constants
private const string DEFAULT_CATEGORY_NAME = "Default";
///
/// Creates the logging provider with the configuration information to connect to AWS and how the messages should be sent.
///
/// Configuration on how to connect to AWS and how the log messages should be sent.
/// A custom formatter which accepts a LogLevel, a state, and an exception and returns the formatted log message.
public AWSLoggerProvider(AWSLoggerConfig config, Func formatter = null)
: this(config, LogLevel.Trace, formatter)
{
}
///
/// Creates the logging provider with the configuration information to connect to AWS and how the messages should be sent.
///
/// Configuration on how to connect to AWS and how the log messages should be sent.
/// The minimum log level for messages to be written.
/// A custom formatter which accepts a LogLevel, a state, and an exception and returns the formatted log message.
public AWSLoggerProvider(AWSLoggerConfig config, LogLevel minLevel, Func formatter = null)
: this(config, CreateLogLevelFilter(minLevel), formatter)
{
}
///
/// Creates the logging provider with the configuration information to connect to AWS and how the messages should be sent.
///
/// Configuration on how to connect to AWS and how the log messages should be sent.
/// A filter function that has the logger category name and log level which can be used to filter messages being sent to AWS.
/// A custom formatter which accepts a LogLevel, a state, and an exception and returns the formatted log message.
public AWSLoggerProvider(AWSLoggerConfig config, Func filter, Func formatter = null)
{
_scopeProvider = NullExternalScopeProvider.Instance;
_core = new AWSLoggerCore(config, "ILogger");
_customFilter = filter;
_customFormatter = formatter;
}
///
/// Creates the logging provider with the configuration section information to connect to AWS and how the messages should be sent. Also contains the LogLevel details
///
/// Contains configuration on how to connect to AWS and how the log messages should be sent. Also contains the LogeLevel details based upon which the filter values would be set
/// A custom formatter which accepts a LogLevel, a state, and an exception and returns the formatted log message.
public AWSLoggerProvider(AWSLoggerConfigSection configSection, Func formatter)
: this(configSection)
{
_customFormatter = formatter;
}
///
/// Creates the logging provider with the configuration section information to connect to AWS and how the messages should be sent. Also contains the LogLevel details
///
/// Contains configuration on how to connect to AWS and how the log messages should be sent. Also contains the LogeLevel details based upon which the filter values would be set
public AWSLoggerProvider(AWSLoggerConfigSection configSection)
{
_scopeProvider = configSection.IncludeScopes ? new LoggerExternalScopeProvider() : NullExternalScopeProvider.Instance;
_configSection = configSection;
_core = new AWSLoggerCore(_configSection.Config, "ILogger");
}
///
/// Called by the ILoggerFactory to create an ILogger
///
/// The category name of the logger which can be used for filtering.
///
public ILogger CreateLogger(string categoryName)
{
var name = string.IsNullOrEmpty(categoryName) ? DEFAULT_CATEGORY_NAME : categoryName;
var filter = _customFilter;
if (_configSection != null && filter == null)
{
filter = CreateConfigSectionFilter(_configSection.LogLevels, name);
}
return _loggers.GetOrAdd(name, loggerName => new AWSLogger(categoryName, _core, filter, _customFormatter)
{
ScopeProvider = _scopeProvider,
IncludeScopes = _configSection?.IncludeScopes ?? Constants.IncludeScopesDefault,
IncludeLogLevel = _configSection?.IncludeLogLevel ?? Constants.IncludeLogLevelDefault,
IncludeCategory = _configSection?.IncludeCategory ?? Constants.IncludeCategoryDefault,
IncludeEventId = _configSection?.IncludeEventId ?? Constants.IncludeEventIdDefault,
IncludeNewline = _configSection?.IncludeNewline ?? Constants.IncludeNewlineDefault,
IncludeException = _configSection?.IncludeException ?? Constants.IncludeExceptionDefault
});
}
///
/// Disposes the provider.
///
public void Dispose()
{
_core.Close();
}
///
/// Creates a simple filter based on a minimum log level.
///
///
///
public static Func CreateLogLevelFilter(LogLevel minLevel)
{
return (category, logLevel) => logLevel >= minLevel;
}
///
/// Creates a filter based upon the prefix of the category name given to the logger
///
/// Contains the configuration details of the Log levels
/// Identifier name that is given to a logger
///
public static Func CreateConfigSectionFilter(IConfiguration logLevels, string categoryName)
{
string name = categoryName;
foreach (var prefix in GetKeyPrefixes(name))
{
LogLevel level;
if (TryGetSwitch(prefix, logLevels, out level))
{
return (n, l) => l >= level;
}
}
return (n, l) => false;
}
///
/// This method fetches the prefix name from the supplied category name of the logger. In case of no prefix match "Default" value is returned.
///
/// The category name parameter given to a logger
///
private static IEnumerable GetKeyPrefixes(string name)
{
while (!string.IsNullOrEmpty(name))
{
yield return name;
var lastIndexOfDot = name.LastIndexOf('.');
if (lastIndexOfDot == -1)
{
yield return "Default";
break;
}
name = name.Substring(0, lastIndexOfDot);
}
}
///
/// This method gets the prefix name from the function CreateConfigSectionFilter and checks if there is a filter that matches.
///
/// The prefix name supplied by the function CreateConfigSectionFilter. The filter matching operation would be based upon this supplied value.
/// The Configuration section supplied by the user that deals with the logLevels.
/// The LogLevel that was found to be a match.
///
public static bool TryGetSwitch(string name, IConfiguration logLevels, out LogLevel level)
{
var switches = logLevels;
if (switches == null)
{
level = LogLevel.Trace;
return true;
}
var value = switches[name];
if (string.IsNullOrEmpty(value))
{
level = LogLevel.None;
return false;
}
else if (Enum.TryParse(value, out level))
{
return true;
}
else
{
var message = $"Configuration value '{value}' for category '{name}' is not supported.";
throw new InvalidOperationException(message);
}
}
///
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
foreach (var logger in _loggers)
{
logger.Value.ScopeProvider = _scopeProvider;
}
}
}
}