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

如何在log4net日志中添加堆栈跟踪、类及方法名?

解决log4net日志无法显示类/方法名及堆栈跟踪的问题

问题根源

你的封装Logger类直接调用log4net的ILog方法时,log4net默认捕获的是Logger类内部的方法(如Debug/Info),而非业务代码中实际调用日志的位置。同时默认的%M(方法名)、%C(类名)转换符只能识别直接调用log4net API的层级,导致日志显示的是封装类的信息,而非业务代码的调用位置。

解决方案

方案1:修改Logger类,传递真实调用者信息

利用C#的CallerMemberName特性或StackFrame获取业务代码的调用位置,手动注入到log4net的LoggingEvent中:

using System;
using System.IO;
using System.Reflection;
using log4net;
using log4net.Core;
using System.Runtime.CompilerServices;

public class Logger : ILogger
{
    private readonly ILog _logger;
    private readonly Type _declaringType;

    public Logger()
    {
        _declaringType = MethodBase.GetCurrentMethod()?.DeclaringType ?? typeof(Logger);
        _logger = LogManager.GetLogger(_declaringType);
    }

    public void Debug(string message, 
        [CallerMemberName] string memberName = "", 
        [CallerFilePath] string filePath = "", 
        [CallerLineNumber] int lineNumber = 0)
    {
        Log(Level.Debug, message, null, memberName, filePath, lineNumber);
    }

    public void Info(string message, 
        [CallerMemberName] string memberName = "", 
        [CallerFilePath] string filePath = "", 
        [CallerLineNumber] int lineNumber = 0)
    {
        Log(Level.Info, message, null, memberName, filePath, lineNumber);
    }

    public void Error(string message, Exception? ex = null, 
        [CallerMemberName] string memberName = "", 
        [CallerFilePath] string filePath = "", 
        [CallerLineNumber] int lineNumber = 0)
    {
        Log(Level.Error, message, ex, memberName, filePath, lineNumber);
    }

    public void Warning(string message, 
        [CallerMemberName] string memberName = "", 
        [CallerFilePath] string filePath = "", 
        [CallerLineNumber] int lineNumber = 0)
    {
        Log(Level.Warn, message, null, memberName, filePath, lineNumber);
    }

    private void Log(Level level, string message, Exception? ex, string memberName, string filePath, int lineNumber)
    {
        if (!_logger.IsEnabledFor(level))
            return;

        var log4netLogger = _logger.Logger;
        var loggingEvent = new LoggingEvent(_declaringType, log4netLogger.Repository, log4netLogger.Name, level, message, ex);

        // 通过StackFrame获取真实的业务类名和方法名(偏移2:跳过Log和Debug/Info等封装方法)
        var stackFrame = new StackFrame(2, false);
        var method = stackFrame.GetMethod();
        if (method != null)
        {
            loggingEvent.Properties["CallerClass"] = method.DeclaringType?.Name;
            loggingEvent.Properties["CallerMethod"] = method.Name;
        }

        // 注入调用文件和行号
        loggingEvent.Properties["CallerFile"] = Path.GetFileName(filePath);
        loggingEvent.Properties["CallerLine"] = lineNumber;

        log4netLogger.Log(loggingEvent);
    }
}

方案2:调整log4net配置,输出自定义信息

修改log4net.config中的conversionPattern,使用自定义属性输出真实调用位置,同时添加%exception显示异常堆栈:

<configuration>
<log4net>
    <appender name="Console" type="log4net.Appender.ConsoleAppender">
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%-4thread] %-5level %logger [%property{CallerClass}.%property{CallerMethod} (%property{CallerFile}:%property{CallerLine})] - %message%newline%exception" />
        </layout>
    </appender>

    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
        <file value="Log.txt" />
        <appendToFile value="false" />
        <rollingStyle value="Size" />
        <maxSizeRollBackups value="10" />
        <maximumFileSize value="50MB" />
        <staticLogFileName value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date [%-4thread] %-5level %logger [%property{CallerClass}.%property{CallerMethod} (%property{CallerFile}:%property{CallerLine})] - %message%newline%exception" />
        </layout>
    </appender>

    <root>
        <level value="INFO" />
        <appender-ref ref="Console" />
        <appender-ref ref="RollingFileAppender" />
    </root>
</log4net>
</configuration>

简化方案(无需修改代码)

如果不想改动Logger类,可以直接使用%stacktrace转换符并指定偏移量,跳过封装层:

<conversionPattern value="%date [%-4thread] %-5level %logger %stacktrace{2} - %message%newline%exception" />

%stacktrace{2}表示跳过堆栈中的前2层(即Logger类的封装方法),直接显示业务代码的调用位置,输出格式类似:at MyApp.MqttService.Connect() in D:\Projects\MyApp\Services\MqttService.cs:line 45

效果验证

修改后日志输出会显示类似内容:

2022-11-08 10:02:41,161 [1234] WARN  Logger [MqttService.Disconnect (MqttService.cs:67)] - Disconnected from the MQTT broker
2022-11-08 10:02:41,161 [1234] ERROR Logger [MqttService.Connect (MqttService.cs:45)] - Failed to connect to the MQTT broker
System.Net.Sockets.SocketException: 连接失败
   at System.Net.Sockets.TcpClient.Connect(String hostname, Int32 port)
   at MyApp.MqttService.Connect() in D:\Projects\MyApp\Services\MqttService.cs:line 42

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 22:55:16