12

I want save some addtitional info with my error message. For example it should be user query, or something else. How should I do it?

Is there any build it methods for logging collections, structurest or objects? Or I should serialize it myself?

Yavanosta
  • 1,480
  • 3
  • 18
  • 27

5 Answers5

24

No, there is nothing built-in for serializing objects. When you use formatted methods like Debug<T>(string message, T argument) internally (you can see class NLog.LogEventInfo) simple String.Format is used for creating formatted message (i.e. just ToString() is called on every parameter).

I use Json.NET for serializing objects and collections to JSON. It's easy to create extension method like

public static string ToJson(this object value)
{
    var settings = new JsonSerializerSettings { 
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
    };

    return JsonConvert.SerializeObject(value, Formatting.Indented, settings);
}

And then use it during logging:

Logger.Debug("Saving person {0}", person.ToJson());
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • 4
    It worth mention, that ToJson will always be called, no matter what log level is. Which may hurt production perfomance – dimaaan May 14 '19 at 13:34
  • 3
    @dimaaan yep, to improve performance there are overloads of logger methods which accept `LogMessageGenerator` delegate: `Logger.Debug(() => $"Saving person {person.ToJson()}");` I would also move `JsonSerializerSettings` to static field. – Sergey Berezovskiy May 21 '19 at 13:46
  • You can also wrap in an if block that checks the logger level before logging, but that is kind of nasty verbose. I'd prefer the delegate @SergeyBerezovskiy mentions above. – crush Mar 04 '20 at 20:17
5
/**
 * class used to optimize loggers
 *
 * Logger.Trace("info "+bigData.ToString());
 * Logger.Trace("info {0}",bigData.ToString());
 * both creates and parses bigData even if Trace is disabled
 * 
 * Logger.Trace("info {0}", LazyJsonizer.Create(bigData));
 * Logger.Trace(LazyJsonizer.Instance, "info {0}", bigData);
 * creates string only if Trace is enabled
 * 
 * http://stackoverflow.com/questions/23007377/nlog-serialize-objects-or-collections-to-log
 */
public class LazyJsonizer<T>
{
    T Value;

    public LazyJsonizer(T value)
    {
        Value = value;
    }

    override public string ToString()
    {
        return LazyJsonizer.Instance.Format(null, Value, null);
    }
}

public class LazyJsonizer : IFormatProvider, ICustomFormatter
{
    static public readonly LazyJsonizer Instance = new LazyJsonizer();

    static public LazyJsonizer<T> Create<T>(T value)
    {
        return new LazyJsonizer<T>(value);
    }

    public object GetFormat(Type formatType)
    {
        return this;
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        try
        {
            return JsonConvert.SerializeObject(arg);
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }
}
Fantastory
  • 1,891
  • 21
  • 25
  • Why do you need to implement IFormatProvider and ICustomFormatter when you are anyways passing null and not using it? I think of you remove those two parameters from the Format method (string format, object arg, IFormatProvider formatProvider), it will become simpler. Otherwise, I think it is a nice solution – Samarsh May 18 '19 at 09:47
  • I use them it when calling as `Logger.Trace(LazyJsonizer.Instance, "info {0}", bigData);` – Fantastory May 24 '19 at 10:17
5

This simplified example shows what I came to after playing with NLog. In my solution I use code-based configuration in order to mitigate duplicate xml nlog.config files for every asp.net project. Works with NLog v4.4.1.

Logger static helper:

private static readonly Logger DefaultLogger = LogManager.GetLogger("Application");

public static void Debug(Exception exception = null, string message = null, object data = null)
    => Write(DefaultLogger, LogLevel.Debug, message, exception, data);

private static void Write(
    Logger logger,
    LogLevel level,
    string message = null,
    Exception exception = null, 
    object data = null)
{
    var eventInfo = new LogEventInfo()
    {
        Level = level,
        Message = message,
        Exception = exception,
        Parameters = new[] { data, tag }
    };
    if (data != null) eventInfo.Properties["data"] = data.ToJson();
    eventInfo.Properties["level"] = eventInfo.GetLevelCode(); // custom level to int conversion

    logger.Log(eventInfo);
}

FileTarget configuration:

var jsonFileTarget = new FileTarget()
{
    Name = "file_json",
    Layout = new JsonLayout()
    {
        Attributes =
        {
            new JsonAttribute("level", "${event-context:item=level}"),
            new JsonAttribute("time", "${longdate}"),
            new JsonAttribute("msg", "${message}"),
            new JsonAttribute("error", "${exception:format=tostring}"),
            new JsonAttribute("data", "${event-context:item=data}", false),
        },
        RenderEmptyObject = false,
    },
    FileName = $"{LogFile.Directory}/json_{LogFile.Suffix}", // use settings from static LogFile class 
    ArchiveFileName = $"{LogFile.Directory}/json_{LogFile.ArchiveSuffix}",
    ArchiveAboveSize = LogFile.MaxSize
};

Output for custom object:

{ "level": "10", "time": "2017-02-02 16:24:52.3078", "data":{"method":"get","url":"http://localhost:44311/"}}
Egor Elagin
  • 86
  • 1
  • 3
4

NLog ver. 4.5 includes new features for structured logging:

var order = new Order
{
    OrderId = 2,
    Status = OrderStatus.Processing
};

logger.Info("Test {value1}", order);
// object Result:  Test MyProgram.Program+Order
logger.Info("Test {@value1}", order);
// object Result:  Test {"OrderId":2, "Status":"Processing"}
logger.Info("Test {value1}", new { OrderId = 2, Status = "Processing"});
// anomynous object. Result: Test { OrderId = 2, Status = Processing }
logger.Info("Test {@value1}", new { OrderId = 2, Status = "Processing"});
// anomynous object. Result:Test {"OrderId":2, "Status":"Processing"}

https://github.com/NLog/NLog/wiki/How-to-use-structured-logging

somethingRandom
  • 811
  • 11
  • 16
Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70
3

there is a very simple solution to implement structure logging with nlog.

try
{
    // bla bla bla 
}
catch (Exception ex)
{
    _logger.LogError(ex, "MyRequest{@0}", request);
}

the symbol @ serialize the request object

https://github.com/NLog/NLog/wiki/How-to-use-structured-logging

Tasos K.
  • 7,979
  • 7
  • 39
  • 63
dimmits
  • 1,999
  • 3
  • 12
  • 30