Files
LogViewerControl/Resources/3rd Party/Microsoft.Extensions.Logging.Log4Net.AspNetCore/Log4NetLoggingEventFactory.cs
T
Matthias Heil 6bed9b284c initial commit
2026-04-04 13:30:13 +02:00

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;
}
}
}