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 { /// public class Log4NetLoggingEventFactory : ILog4NetLoggingEventFactory { /// /// The default property name for scopes that don't provide their own property name by implementing /// an where T is and where TKey /// is . /// protected const string DefaultScopeProperty = "scope"; /// public LoggingEvent CreateLoggingEvent( in MessageCandidate 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> formattedLogValues) { foreach (var pair in formattedLogValues) { loggingEvent.Properties[pair.Key] = pair.Value; } } EnrichWithScopes(loggingEvent, scopeProvider); return loggingEvent; } /// /// 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 where T is and where TKey is /// . /// /// /// 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. /// /// The the scope information will be added to. /// The external provider for the current logging scope. 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) { var keyValuePair = (KeyValuePair)item; string previousValue = @event.Properties[keyValuePair.Key] as string; @event.Properties[keyValuePair.Key] = JoinOldAndNewValue(previousValue, keyValuePair.Value); continue; } if (item is KeyValuePair) { var keyValuePair = (KeyValuePair)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; } } }