4

I am implementing a custom NLog AsyncTaskTarget, and I need to retrieve values that I have added using Microsoft.Extensions.Logging.ILogger.BeginScope:

using (var scope = logger.BeginScope(new []
{
    new KeyValuePair<string, object>("userId", this.UserId)
}))
{
    logger.Log(..);
}

Rendering this in a layout using ${mdlc:userId} works fine, but I would like to get them directly from MappedDiagnosticsLogicalContext or ScopeContext.

This is what I've tried:

protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
{
    // returns null
    ScopeContext.TryGetProperty("userId", out var userId);
    userId = MappedDiagnosticsLogicalContext.GetObject("userId");

    // this works fine
    var message = this.Layout.Render(logEvent);
}

How do I get the userId value from the scope?

NLog Version: 5.0.1 (newest)

This GitHub issue is related to this problem, but I found no real solution there.

marsze
  • 15,079
  • 5
  • 45
  • 61
  • What's the value of `this.Layout`? What are the NLog versions? And what is the result of `ScopeContext.GetAllNestedStates()`? – Julian Jun 29 '22 at 21:46
  • @Julian `this.Layout` is of type `TargetWithContextLayout`. You can use any layout you want, point is, when you use `${mdlc:userId}` in it, it will get properly rendered as the `userId` I added to the scope. NLog version is latest (`5.0.1`). `ScopeContext.GetAllNestedStates()` was empty. – marsze Jun 30 '22 at 08:21

3 Answers3

3

In your Target-constructor enable the option:

And then you can use GetScopeContextProperties() method:

protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken token)
{
    var contextProperties = GetScopeContextProperties(logEvent);
    contextProperties?.TryGetValue("userid", out var userId);
}

You can also get a combined dictionary of all properties, by using GetAllProperties() and the do the lookup (Still need to enable IncludeScopeProperties )

Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70
  • This is a step forward, but strangely though, this seems to return not the original object, but a ToString() representation, at least for reference types. – marsze Jul 03 '22 at 14:48
  • 1
    NLog cannot guarantee the object-lifetime of scope-properties (example they could have been disposed after scope exit). You can override [SerializeScopeContextProperty](https://nlog-project.org/documentation/v5.0.0/html/M_NLog_Targets_TargetWithContext_SerializeScopeContextProperty.htm) to exclude / include object-types that can be trusted as immutable (just like string-object). – Rolf Kristensen Jul 03 '22 at 15:26
  • I figured. My scope values are all immutable, so this will work perfectly. Thank you. – marsze Jul 04 '22 at 08:24
1

You could use GetAllProperties method to get dictionary of all configured properties for logEvent, and then retrieve specific property from the dictionary. Just enable IncludeScopeProperties of the target in NLog configuration.

Alexander
  • 4,420
  • 7
  • 27
  • 42
  • 1
    @marsze, I used it in [my NLog target implementation](https://gitlab.com/koryukov/nlog-graylog). Rolf Kristensen is an NLog developer and he provided the same solution in the second part of his post. – Alexander Jul 03 '22 at 15:13
  • 1
    Yes, `IncludeScopeProperties` was the crucial piece of information. – marsze Jul 05 '22 at 09:05
1

Full solution:

using NLog;
using NLog.Targets;

class AsyncScopeTarget : AsyncTaskTarget
{
    public AsyncScopeTarget()
    {
        this.IncludeScopeProperties = true;
    }

    protected override bool SerializeScopeContextProperty(LogEventInfo logEvent, string name, object value, out object serializedValue)
    {
        // return the original (immutable) value
        serializedValue = value;
        return true;
    }

    protected override async Task WriteAsyncTask(LogEventInfo logEvent, CancellationToken cancellationToken)
    {
        var scopeValues = GetScopeContextProperties(logEvent);
        if (scopeValues is not null)
        {
            scopeValues.TryGetValue("name", out object? scopeValue);
        }
    }
}

(Note: Also consider this post.)

marsze
  • 15,079
  • 5
  • 45
  • 61