Nlog: How to override a logger to enrich the LogEventInfo with a property

Created on 21 Apr 2021  路  7Comments  路  Source: NLog/NLog

Hi!
I've not found any way to do this pattern, since it's the second project where i found it very useful I'll try to explain my needs.
I would like to use one instance of Logger with the name of the class, and I have many instances of this class, this pattern is repeated many times.
I need to add some context information, that is different for each class instance, i see two way to do it
1) Create a Logger with the name of the class + the context differentiator.

  • Pro : it works
  • Cons : it creates a lot of loggers,
  • Cons : it mixes the logger name with the context

2) I would like to create one logger per each class as usually, but on each instance, wrap this logger with some thing that i did not find, where I can inject a parameter to the LogEventInfo at the last possible moment.

  • Pro : minimal code change. From LogManager.GetCurrentLogger() to a factory method
  • Pro : Fewer loggers
  • Pro : ability to inject custom context

Example of code that could be written just to communicate the intent better, any other idea is ofc welcome

```c#
class LoggerWrapper : SomeMagicBaseClass
{
string _contextId;
public LoggerWrapper(Logger logger, string contextId) : base(logger)
{
_contextId = contextId;
}
override void OnBeforeSomething(LogEventInfo eventInfo)
{
eventInfo.Properties.Add("ContextId", _contextId);
}

    public static ILogger CreateLogger(string contextId, object currentObject)
    {
        var className = currentObject.GetType().FullName;
        var logger = LogManager.GetLogger(className);
        var wrapper = new LoggerWrapper(logger, contextId);
        return wrapper;
    }
}

```

question

All 7 comments

Hi! Thanks for opening your first issue here! Please make sure to follow the issue template - so we could help you better!

What about an event on the Logger to give this possibility in this point of the process ?
https://github.com/NLog/NLog/blob/eeba89e49de6bdaefe47f8cedeb931fb5fea42cb/src/NLog/Logger.cs#L497

something like this
https://github.com/NLog/NLog/pull/4405

Could your requirement be handled like this:

c# var globalLogger = LogManager.GetLogger(className); var loggerWithContext = globalLogger.WithProperty("ContextId", contextId);

See also: https://github.com/NLog/NLog/wiki/Context#logevent-properties

Hi,
thanks @snakefoot it does works, i'm a little bit concerned about the recommendation in the wiki :
_Minimize the number of calls to LogManager.GetLogger() and LogManager.GetCurrentClassLogger() by using static readonly variables_
since the number of log will increase with the data the application have, since the WithProperty method creates a new logger internally.
```c#
public Logger WithProperty(string propertyKey, object propertyValue)
{
if (string.IsNullOrEmpty(propertyKey))
throw new ArgumentException(nameof(propertyKey));

        Logger newLogger = Factory.CreateNewLogger(GetType()) ?? new Logger();
        newLogger.Initialize(Name, _configuration, Factory);
        newLogger._contextProperties = CreateContextPropertiesDictionary(_contextProperties);
        newLogger._contextProperties[propertyKey] = propertyValue;
        newLogger._contextLogger = _contextLogger;  // Use the LoggerConfiguration of the parent Logger
        return newLogger;
    }

```

EDIT : It also requires this code to run multiple times if we need to add more properties.

The performance issue is mostly for very critical appplicattion where memory-allocation is not allowed. Especially LogManager.GetCurrentClassLogger() is super expensive because it captures callsite-stacktrace to extract logger-name from class-type-name. The LogManager.GetLogger() is not as expensive, but has to acquire a "global"-lock to register the global-logger, which can hurt when having 8 threads (or more) that constantly calls LogManager.GetLogger(). But WithProperty is quite cheap since there is no global-lock or stacktrace-capture (Just allocation of Logger-object and Dictionary-object)

And when you have accepted the performance-overhead with structured-logging (LogEventInfo-Properties-dictionary-allocation for every LogEvent), then calling WithProperty does not introduce any real overhead. Instead of 2 million logevents/sec then you only get 1 million logevents/sec (Very few applications generates more than 50000 logevents/sec)

Thank you for explaining it, it makes sense !

No problem. Happy that Logger.WithProperty was an acceptable solution :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ranjan-2209 picture ranjan-2209  路  3Comments

ericnewton76 picture ericnewton76  路  3Comments

carkov1990 picture carkov1990  路  3Comments

npandrei picture npandrei  路  3Comments

haythamabutair picture haythamabutair  路  3Comments