using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Extensions.Logging
{
///
/// Options that can be used to configure Lambda logging.
///
public class LambdaLoggerOptions
{
// Default configuration section.
// Customer configuration will be fetched from this section, unless otherwise specified.
internal const string DEFAULT_SECTION_NAME = "Lambda.Logging";
private const string INCLUDE_LOG_LEVEL_KEY = "IncludeLogLevel";
private const string INCLUDE_CATEGORY_KEY = "IncludeCategory";
private const string INCLUDE_NEWLINE_KEY = "IncludeNewline";
private const string INCLUDE_EXCEPTION_KEY = "IncludeException";
private const string INCLUDE_EVENT_ID_KEY = "IncludeEventId";
private const string INCLUDE_SCOPES_KEY = "IncludeScopes";
private const string LOG_LEVEL_KEY = "LogLevel";
private const string DEFAULT_CATEGORY = "Default";
///
/// Flag to indicate if LogLevel should be part of logged message.
/// Default is true.
///
public bool IncludeLogLevel { get; set; }
///
/// Flag to indicate if Category should be part of logged message.
/// Default is true.
///
public bool IncludeCategory { get; set; }
///
/// Flag to indicate if logged messages should have a newline appended
/// to them, if one isn't already there.
/// Default is true.
///
public bool IncludeNewline { get; set; }
///
/// Flag to indicate if Exception should be part of logged message.
/// Default is false.
///
public bool IncludeException { get; set; }
///
/// Flag to indicate if EventId should be part of logged message.
/// Default is false.
///
public bool IncludeEventId { get; set; }
///
/// Gets or sets a value indicating whether scopes should be included in the message.
/// Defaults to false.
///
public bool IncludeScopes { get; set; }
///
/// Function used to filter events based on the log level.
/// Default value is null and will instruct logger to log everything.
///
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public Func Filter { get; set; }
///
/// Constructs instance of LambdaLoggerOptions with default values.
///
public LambdaLoggerOptions()
{
IncludeCategory = true;
IncludeLogLevel = true;
IncludeNewline = true;
IncludeException = false;
IncludeEventId = false;
IncludeScopes = false;
Filter = null;
}
///
/// Constructs instance of LambdaLoggerOptions with values from "Lambda.Logging"
/// subsection of the specified configuration.
/// The following configuration keys are supported:
/// IncludeLogLevel - boolean flag indicates if LogLevel should be part of logged message.
/// IncludeCategory - boolean flag indicates if Category should be part of logged message.
/// LogLevels - category-to-LogLevel mapping which indicates minimum LogLevel for a category.
///
///
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public LambdaLoggerOptions(IConfiguration configuration)
: this(configuration, DEFAULT_SECTION_NAME)
{ }
///
/// Constructs instance of LambdaLoggerOptions with values from specified
/// subsection of the configuration.
/// The following configuration keys are supported:
/// IncludeLogLevel - boolean flag indicates if LogLevel should be part of logged message.
/// IncludeCategory - boolean flag indicates if Category should be part of logged message.
/// LogLevels - category-to-LogLevel mapping which indicates minimum LogLevel for a category.
///
///
///
[CLSCompliant(false)] // https://github.com/aspnet/Logging/issues/500
public LambdaLoggerOptions(IConfiguration configuration, string loggingSectionName)
: this()
{
// Input validation
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (string.IsNullOrEmpty(loggingSectionName))
{
throw new ArgumentNullException(nameof(loggingSectionName));
}
var loggerConfiguration = configuration.GetSection(loggingSectionName);
if (loggerConfiguration == null)
{
throw new ArgumentOutOfRangeException(nameof(loggingSectionName), $"Unable to find section '{loggingSectionName}' in current configuration.");
}
// Parse settings
if (TryGetString(loggerConfiguration, INCLUDE_CATEGORY_KEY, out string includeCategoryString))
{
IncludeCategory = bool.Parse(includeCategoryString);
}
if (TryGetString(loggerConfiguration, INCLUDE_LOG_LEVEL_KEY, out string includeLogLevelString))
{
IncludeLogLevel = bool.Parse(includeLogLevelString);
}
if (TryGetString(loggerConfiguration, INCLUDE_EXCEPTION_KEY, out string includeExceptionString))
{
IncludeException = bool.Parse(includeExceptionString);
}
if (TryGetString(loggerConfiguration, INCLUDE_EVENT_ID_KEY, out string includeEventIdString))
{
IncludeEventId = bool.Parse(includeEventIdString);
}
if (TryGetString(loggerConfiguration, INCLUDE_NEWLINE_KEY, out string includeNewlineString))
{
IncludeNewline = bool.Parse(includeNewlineString);
}
if (TryGetSection(loggerConfiguration, LOG_LEVEL_KEY, out IConfiguration logLevelsSection))
{
Filter = CreateFilter(logLevelsSection);
}
if (TryGetString(loggerConfiguration, INCLUDE_SCOPES_KEY, out string includeScopesString))
{
IncludeScopes = bool.Parse(includeScopesString);
}
}
// Retrieves configuration value for key, if one exists
private static bool TryGetString(IConfiguration configuration, string key, out string value)
{
value = configuration[key];
return (value != null);
}
// Retrieves configuration section for key, if one exists
private static bool TryGetSection(IConfiguration configuration, string key, out IConfiguration value)
{
value = configuration.GetSection(key);
return (value != null);
}
// Creates filter for log levels section
private static Func CreateFilter(IConfiguration logLevelsSection)
{
// Empty section means log everything
var logLevels = logLevelsSection.GetChildren().ToList();
if (logLevels.Count == 0)
{
return null;
}
// Populate mapping of category to LogLevel
var logLevelsMapping = new Dictionary(StringComparer.Ordinal);
var defaultLogLevel = LogLevel.Information;
foreach (var logLevel in logLevels)
{
var category = logLevel.Key;
var minLevelValue = logLevel.Value;
LogLevel minLevel;
if (!Enum.TryParse(minLevelValue, out minLevel))
{
throw new InvalidCastException($"Unable to convert level '{minLevelValue}' for category '{category}' to LogLevel.");
}
if (category.Contains("*"))
{
// Only 1 wildcard is supported
var wildcardCount = category.Count(x => x == '*');
if (wildcardCount > 1)
{
throw new ArgumentOutOfRangeException($"Category '{category}' is invalid - only 1 wildcard is supported in a category.");
}
// Wildcards are only supported at the end of a Category name!
var wildcardLocation = category.IndexOf('*');
if (wildcardLocation != category.Length - 1)
{
throw new ArgumentException($"Category '{category}' is invalid - wilcards are only supported at the end of a category.");
}
var trimmedCategory = category.TrimEnd('*');
logLevelsMapping[trimmedCategory] = minLevel;
}
else if (category.Equals(DEFAULT_CATEGORY, StringComparison.OrdinalIgnoreCase))
{
defaultLogLevel = minLevel;
}
else
{
logLevelsMapping[category] = minLevel;
}
}
// Extract a reverse sorted list of categories. This allows us to quickly search for most specific match to least specific.
var orderedCategories = logLevelsMapping.Keys
.OrderByDescending(categoryKey => categoryKey)
.ToList();
// Filter lambda that examines mapping
return (string category, LogLevel logLevel) =>
{
LogLevel minLevel;
// Exact match takes priority
if (logLevelsMapping.TryGetValue(category, out minLevel))
{
return logLevel >= minLevel;
}
// Find the most specific wildcard or namespace prefix category that matches the logger category
var matchedCategory = orderedCategories.FirstOrDefault(category.StartsWith);
if (matchedCategory != null)
{
// If no log filters then default to logging the log message.
minLevel = logLevelsMapping[matchedCategory];
return logLevel >= minLevel;
}
else
{
// If no log filters then default to logging the log message.
return (logLevel >= defaultLogLevel);
}
};
}
}
}