Nlog: Adding LogEventInfo at Controller Action level for Asp.Net Core Application

Created on 12 May 2017  路  7Comments  路  Source: NLog/NLog

Hi, I have been able to hook nlog into an asp.net core application as described here

Now i need to do add log event info properties like described here but how do i do that in the action method. The issue is that Ilogger does not accept LogEventInfo object.

How do i pass this to the nloger through Ilogger interface? or otherwise?

.NET core question

All 7 comments

You could try and inject the LogEventInfo object as a parameter that is not referenced by the Log-Text:

LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "", "");
theEvent.Properties["MyValue"] = "My custom string";
_logger.LogInformation("Index page says hello", theEvent);

Then it should automatically merge the Properties from theEvent.

@snakefoot It does not seem to do that. I am trying to create a custom layout

[LayoutRenderer("custom")]
    public class CustomLayoutRenderer : LayoutRenderer
    {
        protected override void Append(StringBuilder builder, LogEventInfo logEvent)
        {          
            builder.Append(logEvent.Properties["MyValue"]);
        }
    }


But the logeventinfo object has null parameters when i debug. theEvents is not added to the parameters like you said.

@yehonathan I wrote:

automatically merge the Properties from theEvent

This means that the properties you add to theEvent will be appended to the Properties-dictionary of the actual LogEventInfo.

Instead of checking the LogEventInfo.Parameters then you should look at LogEventInfo.Properties.

@yehonathan and @304NotModified I now see my mistake. I thought that NLog was able to handle the Microsoft-ILogger parameters, but it only handles the formatted message.

The following code is missing from NLogLogger.cs (NLog.Extensions):

public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
      ...

      var eventInfo = LogEventInfo.Create(nLogLogLevel, _logger.Name, message);
      eventInfo.Exception = exception;
      // Only allocate LogEventInfo-Properties-Dictionary if needed
      if (eventId.Id != 0 || eventId.Name != null)
      {
            // TODO Cache string.Concat() result, instead of always making string-allocations
            eventInfo.Properties["EventId" + _options.EventIdSeparator + "Id"] = eventId.Id;
            eventInfo.Properties["EventId" + _options.EventIdSeparator + "Name"] = eventId.Name;
            eventInfo.Properties["EventId"] = eventId;
      }

      var formattedLogValues = state as IReadOnlyList<KeyValuePair<string, object>>;
      if (formattedLogValues != null)
      {
            for (int i = 0; i < formattedLogValues.Count; ++i)
            {
                  var logEvent = formattedLogValues[i].Value as NLog.LogEventInfo;
                  if (logEvent != null && logEvent.HasProperties)
                  {
                        foreach (var prop in logEvent.Properties)
                        {
                              eventInfo.Properties.Add(prop.Key, prop.Value);
                        }
                  }
            }
      }

      _logger.Log(eventInfo);
}

Ofcourse the above code is completely untested, so I have no idea if it works.

@yehonathan and @304NotModified I now see my mistake. I thought that NLog was able to handle the Microsoft-ILogger parameters, but it only handles the formatted message

I was also thinking it should work. But I think the formatter (see code) is breaking it.

var formattedLogValues = state as IReadOnlyList>;

I'm not keen on this. It's a breaking change and I think it's also influence performance. Also I doubting if we should use the code that's already in NLog (nlog.dll), e.g. by passing the logeventinfo to it.

Another option is to create a new class (e.g. LogProperties), that will be captures here.
(so var formattedLogValues = state as LogProperties;)
That is backwardscompatible.

also, isn't eventid=0 not a valid evenid?

@304NotModified Yes ofcourse that would be so much simpler:

public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
    ...
    var eventInfo = state as NLog.LogEventInfo;
    if (eventInfo == null)
    {
        if (formatter == null)
        {
            throw new ArgumentNullException(nameof(formatter));
        }

        var message = formatter(state, exception);
        eventInfo = LogEventInfo.Create(nLogLogLevel, _logger.Name, message);
        eventInfo.Exception = exception;
        // Only allocate LogEventInfo-Properties-Dictionary if needed
        if (eventId.Id != 0 || eventId.Name != null)
        {
            // TODO Cache string.Concat() result, instead of always making string-allocations
            eventInfo.Properties["EventId" + _options.EventIdSeparator + "Id"] = eventId.Id;
            eventInfo.Properties["EventId" + _options.EventIdSeparator + "Name"] = eventId.Name;
            eventInfo.Properties["EventId"] = eventId;
        }
    }
    else
    {
        eventInfo.LogLevel = nLogLogLevel;
        if (exception != null && eventInfo.Exception == null)
            eventInfo.Exception = exception;
    }

   _logger.Log(eventInfo);
}

Then it is just a matter of making an extension method like this:

private static Func<NLog.LogEventInfo, Exception, string> _nlogFormatter = (l,e) => 
l.FormattedMessage;

public static void LogEvent([NotNull] this ILogger logger, LogLevel logLevel, NLog.LogEventInfo state)
{
    logger.Log(logLevel, 0, state, state.Exception, _nlogFormatter);
}

also, isn't eventid=0 not a valid eventid?

I guess one could add a new option to NLogProviderOptions, whether it should attempt to hurt performance by capturing EventId = 0 (Two string allocations, dictionary-allocation and 3 dictionary-inserts for every log-message).

I assume your question has been answered, if not, let us know!

Was this page helpful?
0 / 5 - 0 ratings