You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用NLog包装类时,如何记录实际调用方的方法与行号?

解决NLog包装类导致callsite显示错误的问题

这个问题我之前在项目里也遇到过——NLog默认会捕获直接调用它的代码位置,也就是你包装类里的Log方法内部,而不是外部调用你Logger.Log的真实业务代码位置。这里有几个简单好用的修复方案,你可以根据场景选择:

方案1:调整NLog布局参数(最简单快捷)

不需要修改任何代码,只需要在你的NLog配置里给callsite和callsite-linenumber添加skipFrames参数,跳过包装类的那一层栈帧:

<column name="callsite" layout="${callsite:skipFrames=1}" />
<column name="callsite-linenumber" layout="${callsite-linenumber:skipFrames=1}" />

原理:skipFrames=1告诉NLog忽略最近的1层栈帧(也就是你的Logger.Log方法那一层),直接获取上一层的调用信息。如果你的包装类有多层嵌套,只需要调整这个数值即可(比如skipFrames=2)。

方案2:用C# Caller特性手动传递调用信息(最可靠)

如果担心后续代码调整导致栈帧层数变化,skipFrames失效,可以用C#的Caller系列特性,让编译器自动把调用方的信息传递给NLog:

首先修改你的包装类,引入System.Runtime.CompilerServices命名空间,并给Log方法添加Caller参数:

using System;
using System.Runtime.CompilerServices;
using NLog;

namespace Common.NLogEx
{
    public enum LoggerNames
    {
        Database, Thermal, StateMachine, App, Poll
    }
    public enum LogLevel
    {
        Trace, Debug, Info, Warn, Error, Fatal, Off, Warning
    }
    public static class Logger
    {
        // Caller参数由编译器自动填充,调用方无需手动传入
        public static void Log(LoggerNames name, LogLevel level, string message,
            [CallerMemberName] string callerMember = "",
            [CallerFilePath] string callerFilePath = "",
            [CallerLineNumber] int callerLine = 0)
        {
            if (level == LogLevel.Warning) level = LogLevel.Warn;
            var nLevel = NLog.LogLevel.FromString(level.ToString());
            var logger = LogManager.GetLogger(name.ToString());
            
            // 创建自定义日志事件,携带调用方信息
            var logEvent = new LogEventInfo(nLevel, logger.Name, message);
            logEvent.Properties["CallerMember"] = callerMember;
            logEvent.Properties["CallerFilePath"] = callerFilePath;
            logEvent.Properties["CallerLineNumber"] = callerLine;
            
            logger.Log(logEvent);
        }

        public static void Log(LoggerNames name, LogLevel level, Exception ex, string message,
            [CallerMemberName] string callerMember = "",
            [CallerFilePath] string callerFilePath = "",
            [CallerLineNumber] int callerLine = 0)
        {
            if (level == LogLevel.Warning) level = LogLevel.Warn;
            var nLevel = NLog.LogLevel.FromString(level.ToString());
            var logger = LogManager.GetLogger(name.ToString());
            
            var logEvent = new LogEventInfo(nLevel, logger.Name, message);
            logEvent.Properties["CallerMember"] = callerMember;
            logEvent.Properties["CallerFilePath"] = callerFilePath;
            logEvent.Properties["CallerLineNumber"] = callerLine;
            logEvent.Exception = ex;
            
            logger.Log(logEvent);
        }
    }
}

然后修改NLog配置里的CsvLayout,输出这些自定义属性:

<column name="callsite" layout="${event-properties:CallerMember}" />
<column name="callsite-file" layout="${event-properties:CallerFilePath}" /> <!-- 可选:输出调用文件路径 -->
<column name="callsite-linenumber" layout="${event-properties:CallerLineNumber}" />

优势:完全不受栈帧变化影响,编译器直接注入调用方信息,稳定性拉满。

方案3:使用NLog的WithCaller方法(NLog 4.6+适用)

如果你使用的是NLog 4.6及以上版本,可以直接用NLog官方提供的WithCaller方法跳过指定栈帧:

public static void Log(LoggerNames name, LogLevel level, string message)
{
    if (level == LogLevel.Warning) level = LogLevel.Warn;
    var nLevel = NLog.LogLevel.FromString(level.ToString());
    var logger = LogManager.GetLogger(name.ToString());
    // 跳过当前方法栈帧,获取外部调用位置
    logger.WithCaller(1).Log(nLevel, message);
}

public static void Log(LoggerNames name, LogLevel level, Exception ex, string message)
{
    if (level == LogLevel.Warning) level = LogLevel.Warn;
    var nLevel = NLog.LogLevel.FromString(level.ToString());
    var logger = LogManager.GetLogger(name.ToString());
    logger.WithCaller(1).Log(nLevel, ex, message, null);
}

这个方法不需要修改配置,代码改动也很小,适合已经升级到新版NLog的项目。


内容的提问来源于stack exchange,提问作者Dave

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:59:35