自定义何时在 NLog 中捕获堆栈跟踪

Customizing when to capture stack trace in NLog

我正在编写一个派生自 TargetWithLayout 的自定义 NLog 目标,我想让它根据特定的日志事件选择性地写入堆栈跟踪。我定义了这样的嵌套布局(语法可能不正确):

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

我会创建这样的事件:

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

然而,这不起作用,而且似乎违背了 NLog 的设计。从我所看到的一点点来看,NLog 会根据其注册的目标评估 GetStackTraceUsage(),然后总是生成堆栈跟踪或从不生成。这个对吗?有没有办法在事件级别自定义堆栈跟踪生成?

一个解决方案是使用两个目标实例,并且只使用 logger-name 来控制它是否应该使用配置为写入 StackTrace 的目标:

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

现在 NLog 仅根据 Logger-name 决定它是否应该包含 StackTrace(或不包含)。它会找到所有与记录器名称匹配的日志记录规则,如果其中一个包含带有堆栈跟踪的目标,则将完成捕获(独立于具有条件过滤器的规则)

修复 NLog 应该很容易,因此它还会在尝试捕获 StackTrace 之前检查过滤条件(允许对 logger-name 以外的其他内容进行过滤优化)。

确认已接受的答案所述:如果任何 NLog 目标需要任何消息的堆栈跟踪(不考虑过滤器),并且 LogEventInfo 实例,NLog.LoggerImpl.Write 内部方法将捕获堆栈跟踪还没有堆栈跟踪。每 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);
        }

        // ...
    }
}

如果已经明确设置,上述方法将避免捕获实际的堆栈跟踪,即使只是一个占位符。因此,将堆栈跟踪显式设置为占位符是防止 NLog 为那些特定日志条目生成堆栈跟踪的最简单方法。