141 lines
6.7 KiB
C#
141 lines
6.7 KiB
C#
using log4net.Core;
|
|
using Microsoft.Extensions.Logging.Log4Net.AspNetCore.Entities;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
|
|
namespace Microsoft.Extensions.Logging
|
|
{
|
|
/// <inheritdoc cref="ILog4NetLoggingEventFactory"/>
|
|
public class Log4NetLoggingEventFactory
|
|
: ILog4NetLoggingEventFactory
|
|
{
|
|
/// <summary>
|
|
/// The default property name for scopes that don't provide their own property name by implementing
|
|
/// an <see cref="IEnumerable{T}"/> where T is <see cref="KeyValuePair{TKey,TValue}"/> and where TKey
|
|
/// is <see cref="string"/>.
|
|
/// </summary>
|
|
protected const string DefaultScopeProperty = "scope";
|
|
|
|
/// <inheritdoc/>
|
|
public LoggingEvent CreateLoggingEvent<TState>(
|
|
in MessageCandidate<TState> messageCandidate,
|
|
log4net.Core.ILogger logger,
|
|
Log4NetProviderOptions options,
|
|
IExternalScopeProvider scopeProvider)
|
|
{
|
|
Type callerStackBoundaryDeclaringType = typeof(LoggerExtensions);
|
|
string message = messageCandidate.Formatter(messageCandidate.State, messageCandidate.Exception);
|
|
Level logLevel = options.LogLevelTranslator.TranslateLogLevel(messageCandidate.LogLevel, options);
|
|
|
|
if (logLevel == null || (string.IsNullOrEmpty(message) && messageCandidate.Exception == null))
|
|
return null;
|
|
|
|
var loggingEvent = new LoggingEvent(
|
|
callerStackBoundaryDeclaringType: callerStackBoundaryDeclaringType,
|
|
repository: logger.Repository,
|
|
loggerName: logger.Name,
|
|
level: logLevel,
|
|
message: message,
|
|
exception: messageCandidate.Exception);
|
|
|
|
// ref: https://github.com/huorswords/Microsoft.Extensions.Logging.Log4Net.AspNetCore/pull/126/commits/1ad3d429afb4417947863d1bc37231bf7a457f3c
|
|
|
|
if (messageCandidate.State is IEnumerable<KeyValuePair<string, object>> formattedLogValues)
|
|
{
|
|
foreach (var pair in formattedLogValues)
|
|
{
|
|
loggingEvent.Properties[pair.Key] = pair.Value;
|
|
}
|
|
}
|
|
|
|
EnrichWithScopes(loggingEvent, scopeProvider);
|
|
|
|
return loggingEvent;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the scopes from the external scope provider and converts them to the properties on the logging event.
|
|
/// This function will honor the convention that logging scopes can provide their own property name, by implementing
|
|
/// an <see cref="IEnumerable{T}"/> where T is <see cref="KeyValuePair{TKey,TValue}"/> and where TKey is
|
|
/// <see cref="string"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default implementation will call Convert.ToString(scope, CultureInfo.InvariantCulture) on all scope objects.
|
|
/// If you want to do this conversion inside the Log4Net Pipeline, e. g. with a custom layout, you can override this
|
|
/// method and change the behaviour.
|
|
/// </remarks>
|
|
/// <param name="loggingEvent">The <see cref="LoggingEvent"/> the scope information will be added to.</param>
|
|
/// <param name="scopeProvider">The external provider for the current logging scope.</param>
|
|
protected virtual void EnrichWithScopes(LoggingEvent loggingEvent, IExternalScopeProvider scopeProvider)
|
|
{
|
|
scopeProvider.ForEachScope((scope, @event) =>
|
|
{
|
|
// This function will add the scopes in the legacy way they were added before the IExternalScopeProvider was introduced,
|
|
// to maintain backwards compatibility.
|
|
// This pretty much means that we are emulating a LogicalThreadContextStack, which is a stack, that allows pushing
|
|
// strings on to it, which will be concatenated with space as a separator.
|
|
// See: https://github.com/apache/logging-log4net/blob/47aaf46d5f031ea29d781bac4617bd1bb9446215/src/log4net/Util/LogicalThreadContextStack.cs#L343
|
|
|
|
// Because string implements IEnumerable we first need to check for string.
|
|
if (scope is string)
|
|
{
|
|
string previousValue = @event.Properties[DefaultScopeProperty] as string;
|
|
|
|
@event.Properties[DefaultScopeProperty] = JoinOldAndNewValue(previousValue, scope.ToString());
|
|
return;
|
|
}
|
|
|
|
if (scope is IEnumerable col)
|
|
{
|
|
foreach (var item in col)
|
|
{
|
|
if (item is KeyValuePair<string, string>)
|
|
{
|
|
var keyValuePair = (KeyValuePair<string, string>)item;
|
|
string previousValue = @event.Properties[keyValuePair.Key] as string;
|
|
@event.Properties[keyValuePair.Key] = JoinOldAndNewValue(previousValue, keyValuePair.Value);
|
|
continue;
|
|
}
|
|
|
|
if (item is KeyValuePair<string, object>)
|
|
{
|
|
var keyValuePair = (KeyValuePair<string, object>)item;
|
|
string previousValue = @event.Properties[keyValuePair.Key] as string;
|
|
|
|
// The current culture should not influence how integers/floats/... are displayed in logging,
|
|
// so we are using Convert.ToString which will convert IConvertible and IFormattable with
|
|
// the specified IFormatProvider.
|
|
string additionalValue = Convert.ToString(keyValuePair.Value, CultureInfo.InvariantCulture);
|
|
@event.Properties[keyValuePair.Key] = JoinOldAndNewValue(previousValue, additionalValue);
|
|
continue;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (scope is object)
|
|
{
|
|
string previousValue = @event.Properties[DefaultScopeProperty] as string;
|
|
string additionalValue = Convert.ToString(scope, CultureInfo.InvariantCulture);
|
|
@event.Properties[DefaultScopeProperty] = JoinOldAndNewValue(previousValue, additionalValue);
|
|
return;
|
|
}
|
|
|
|
}, loggingEvent);
|
|
}
|
|
|
|
private static string JoinOldAndNewValue(string previousValue, string newValue)
|
|
{
|
|
if (string.IsNullOrEmpty(previousValue))
|
|
{
|
|
return newValue;
|
|
}
|
|
|
|
return previousValue + " " + newValue;
|
|
}
|
|
}
|
|
}
|