Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing when to capture stack trace in NLog

I am writing a custom NLog target deriving from TargetWithLayout, and I would like to make it optionally write the stack trace, depending on the specific log event. I've defined a nested layout like this (syntax might not be correct):

Layout layout = "${when:${event-properties:StackTraceEnabled}==true:${stacktrace}}";

And I would be creating events like this:

var logEventInfo = new NLog.LogEventInfo(NLog.LogLevel.Error, "Test", "Test")
{
    Properties =
    {
        { "StackTraceEnabled", true },
    }
};

However, this doesn't work, and seems to go against the design of NLog. From the little I've seen, NLog evaluates a GetStackTraceUsage() against its registered targets, and then either always generates the stack trace or never does. Is this correct? Is there a way of customizing stack trace generation at event level?

like image 237
Þórir Sachie Avatar asked Jun 10 '26 05:06

Þórir Sachie


2 Answers

One solution is to use two target instances, and just use the logger-name to control whether it should use the target configured to write StackTrace:

<logger name="StackTraceEnabled" writeTo="target1_stacktrace" final="true">
<logger name="*" writeTo="target2_default" />

Right now NLog decides only based on Logger-name whether it should include StackTrace (or not). It finds all logging-rules matching the logger-name, and if one includes a target with stacktrace then capture will be done (independent of the rule has condition-filters)

It should be easy to fix NLog so it also checks filter-conditions before trying to capture StackTrace (Allow filter-optimization on other stuff than logger-name).

like image 131
Rolf Kristensen Avatar answered Jun 11 '26 19:06

Rolf Kristensen


Confirming what the accepted answer stated: The NLog.LoggerImpl.Write internal method captures the stack trace if any NLog target requires the stack trace for any message (irrespective of filters), and if the LogEventInfo instance does not already have a stack trace. Per LoggerImpl.cs:

internal static class LoggerImpl
{
    internal static void Write([NotNull] Type loggerType, TargetWithFilterChain targets, LogEventInfo logEvent, LogFactory factory)
    {
        if (targets == null)
            return;

        StackTraceUsage stu = targets.GetStackTraceUsage();
        if (stu != StackTraceUsage.None && !logEvent.HasStackTrace)
        {
            var stackTrace = new StackTrace(StackTraceSkipMethods, stu == StackTraceUsage.WithSource);
            var stackFrames = stackTrace.GetFrames();
            int? firstUserFrame = FindCallingMethodOnStackTrace(stackFrames, loggerType);
            int? firstLegacyUserFrame = firstUserFrame.HasValue ? SkipToUserStackFrameLegacy(stackFrames, firstUserFrame.Value) : (int?)null;
            logEvent.GetCallSiteInformationInternal().SetStackTrace(stackTrace, firstUserFrame ?? 0, firstLegacyUserFrame);
        }

        // ...
    }
}

The above method would refrain from capturing the actual stack trace if one had already been explicitly set, even if just a placeholder. Thus, explicitly setting the stack trace to a placeholder is the easiest way to prevent NLog from generating the stack trace for those specific log entries.

like image 33
Þórir Sachie Avatar answered Jun 11 '26 20:06

Þórir Sachie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!