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

如何在Unity中配置log4net实现基于来源的上下文特定日志捕获

解决Unity日志上下文动态绑定问题的方案

核心思路是通过解析调用栈定位日志的实际来源类/方法,为每个来源生成对应log4net Logger实例,同时保持对Unity标准Debug.Log系列方法的兼容。

1. 从调用栈中提取真实日志来源

Unity的Application.logMessageReceivedThreaded回调会传入日志的调用栈字符串,我们可以解析这个字符串,过滤掉Unity内部类和LoggerService自身的调用帧,找到实际触发日志的类:

private void OnLogMessageReceivedThreaded(string logString, string stackTrace, LogType logType)
{
    var stackFrames = new StackTrace(stackTrace, true).GetFrames();
    foreach (var frame in stackFrames)
    {
        var method = frame.GetMethod();
        var declaringClass = method.DeclaringType;
        
        // 跳过Unity内部类和当前Logger服务类
        if (declaringClass?.Namespace != null && 
            !declaringClass.Namespace.StartsWith("UnityEngine") && 
            declaringClass != typeof(LoggerService))
        {
            // 用类的全名作为Logger名称,确保上下文唯一
            var logger = LogManager.GetLogger(declaringClass.FullName);
            
            // 映射Unity日志类型到log4net级别
            switch (logType)
            {
                case LogType.Log: logger.Info(logString); break;
                case LogType.Warning: logger.Warn(logString); break;
                case LogType.Error: logger.Error(logString); break;
                case LogType.Exception: logger.Fatal(logString); break;
                case LogType.Assert: logger.Debug(logString); break;
            }
            break;
        }
    }
}

2. 优化性能避免重复开销

频繁创建StackTrace和Logger实例会有性能损耗,加个缓存就能解决:

// 缓存已创建的Logger,避免重复调用LogManager.GetLogger
private static readonly Dictionary<string, ILog> _loggerCache = new Dictionary<string, ILog>();
// 预定义需要过滤的命名空间,减少判断逻辑
private static readonly HashSet<string> _filteredNamespaces = new HashSet<string>
{
    "UnityEngine", "UnityEditor", typeof(LoggerService).Namespace
};

private ILog GetTargetLogger(string stackTrace)
{
    var stackFrames = new StackTrace(stackTrace, true).GetFrames();
    foreach (var frame in stackFrames)
    {
        var declaringClass = frame.GetMethod().DeclaringType;
        if (declaringClass == null) continue;
        
        var className = declaringClass.FullName;
        // 跳过过滤名单里的类
        if (_filteredNamespaces.Any(ns => className.StartsWith(ns))) continue;
        
        // 缓存命中直接返回,否则创建新Logger并存入缓存
        if (!_loggerCache.TryGetValue(className, out var logger))
        {
            logger = LogManager.GetLogger(className);
            _loggerCache[className] = logger;
        }
        return logger;
    }
    // 兜底:找不到有效来源时用默认Logger
    return LogManager.GetLogger("LoggerService");
}

3. 配置log4net输出上下文信息

在你的log4net配置文件里,修改PatternLayout,加入%logger字段就能输出类全名,还可以加%method显示方法名(需要调用栈支持):

<layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger - %message %method%newline" />
</layout>

4. 处理特殊场景注意事项

  • 异步代码日志:异步方法的调用栈可能丢失原始上下文,要是遇到这种情况,可以给开发者提供扩展方法手动指定上下文,但尽量别改他们原来用Debug.Log的习惯
  • IL2CPP编译:IL2CPP会优化调用栈,要在PlayerSettings里开启Enable Script Debugging,Release模式下也得保留必要的调试信息
  • 性能敏感场景:如果是对性能要求极高的游戏,可以考虑用编译期注入(类似AOP的方式)来提前绑定日志上下文,避免运行时解析调用栈的开销

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 11:01:09