Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Unity3d的Log系统重构

Unity3d的Log系统重构

作者头像
全栈程序员站长
发布于 2022-07-20 07:49:33
发布于 2022-07-20 07:49:33
1.5K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是全栈君。

编者注

由于要重写Unity3d的Log系统,变更为自定义方式,按照Log4j的显示的内容方法

Unity3d的Log

一般在Unity3d中编写日志入下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Debug.Log("hello message");

在UnityEditor和UnityEngine当中除了打印message以外,还会打印堆栈信息。性能低下,根据有经验的人讲解,在客户端打印大量日志,会严重降低渲染性能。

Unity3d的Debug原理

原理分析

在Rider中查看Debug.Log的实现,我们可以看到如下内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void Log(object message)
{
    Debug.unityLogger.Log(LogType.Log, message);
}

我们能够了解到,实质是调用了Debug.unityLogger

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static ILogger unityLogger
{
    get
    {
        return Debug.s_Logger;
    }
}

unityLogger实质是调用了Debug.s_Logger,而在下面就定义了s_Logger的实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
internal static ILogger s_Logger = (ILogger) new Logger((ILogHandler) new DebugLogHandler());

DebugLogHandler实质是调用的静态方法,根据UnityEngine各个平台的实现进行调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System;
using System.Runtime.CompilerServices;
using UnityEngine.Scripting;

namespace UnityEngine
{
  internal sealed class DebugLogHandler : ILogHandler
  {
    [ThreadAndSerializationSafe]
    [GeneratedByOldBindingsGenerator]
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);

    [ThreadAndSerializationSafe]
    [GeneratedByOldBindingsGenerator]
    [MethodImpl(MethodImplOptions.InternalCall)]
    internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);

    public void LogFormat(LogType logType, Object context, string format, params object[] args)
    {
      DebugLogHandler.Internal_Log(logType, string.Format(format, args), context);
    }

    public void LogException(Exception exception, Object context)
    {
      DebugLogHandler.Internal_LogException(exception, context);
    }
  }
}

最终实现仅仅只有两个方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);
internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);

代码测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Debug.unityLogger.Log(LogType.Log,"hello message");

UnityEditor打印

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hello message
UnityEngine.Logger:Log(LogType, Object)
InitController:Start() (at Assets/Scripts/Controller/InitController.cs:14)

结论:无法解决减少堆栈信息打印的问题

UnityEngine.Application

根据UnityEngine.Application的LogCallback文档,我们能够了解和LogCallback相关的 Application.logMessageReceivedApplication.logMessageReceivedThreaded:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public delegate void LogCallback(string condition, string stackTrace, LogType type);

由于LogCallback使用的是delegate委托方法,则需要指定实现方法,如下Unity官方推荐实现方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour {
    public string output = "";
    public string stack = "";
    void OnEnable() {
        Application.logMessageReceived += HandleLog;
    }
    void OnDisable() {
        Application.logMessageReceived -= HandleLog;
    }
    void HandleLog(string logString, string stackTrace, LogType type) {
        output = logString;
        stack = stackTrace;
    }
}

原理分析

logMessageReceived仅仅在main线程工作。也就是Unity的主线程当中。并且文档描述实现方法非线程安全。logMessageReceivedThreaded实现的代码必须是线程安全,支持从主线程之外进行访问。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public static event Application.LogCallback logMessageReceived
    {
      add
      {
        Application.s_LogCallbackHandler += value;
        Application.SetLogCallbackDefined(true);
      }
      remove
      {
        Application.s_LogCallbackHandler -= value;
      }
    }

    public static event Application.LogCallback logMessageReceivedThreaded
    {
      add
      {
        Application.s_LogCallbackHandlerThreaded += value;
        Application.SetLogCallbackDefined(true);
      }
      remove
      {
        Application.s_LogCallbackHandlerThreaded -= value;
      }
    }

CallLogCallback从CSharp的注解来看,就是需要本地代码进行调用。直接调用的,也就是说,会优先调用主线程的logCallbackHandler实现,然后无论是否主线程都调用callbackHandlerThreaded的实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    [RequiredByNativeCode]
    private static void CallLogCallback(string logString, string stackTrace, LogType type, bool invokedOnMainThread)
    {
      if (invokedOnMainThread)
      {
        Application.LogCallback logCallbackHandler = Application.s_LogCallbackHandler;
        if (logCallbackHandler != null)
          logCallbackHandler(logString, stackTrace, type);
      }
      Application.LogCallback callbackHandlerThreaded = Application.s_LogCallbackHandlerThreaded;
      if (callbackHandlerThreaded == null)
        return;
      callbackHandlerThreaded(logString, stackTrace, type);
    }

SetLogCallbackDefined

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    [GeneratedByOldBindingsGenerator]
    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern void SetLogCallbackDefined(bool defined);

日志系统设计

需求

  • 不影响Unity
  • 文件方式输出
  • 支持Unity Debug
  • 支持输出日志级别

Log4Net

根据之前Java的方式,Log4j很好用,首先决定仿照slf4j的接口方式进行使用。其次使用Log4net的实现,实现需求,只要不影响Unity运行即可。实际测试并未影响Unity运行。

接口设计

LoggerFactory

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using log4net;

namespace Assets.Scripts.Utils.Log4Unity
{
    public class LoggerFactory
    {
        public static Log4Unity getLogger(string name)
        {
            return new Log4Unity(LogManager.GetLogger(name));
        }
    }
}

Log4Unity:原本想直接使用logger,但是被Unity占用了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System;
using System.IO;
using log4net;
using log4net.Config;
using UnityEngine;

namespace Assets.Scripts.Utils.Log4Unity
{
    public class Log4Unity
    {
        private ILog log;
        public Log4Unity(ILog log)
        {
            this.log = log;
        }

        public void debug(string message)
        {
            this.log.Debug(message);
            LogConfigurator.refresh();
        }

        public void info(string message)
        {
            this.log.Info(message);
            LogConfigurator.refresh();
        }

        public void warning(string message)
        {
            this.log.Warn(message);
            LogConfigurator.refresh();
        }

        public void error(string message)
        {
            Debug.LogError(message);
            if(!LogConfigurator.lazy_mode)
                this.log.Error(message);
            LogConfigurator.refresh();
        }

        public void exception(Exception exception)
        {
            Debug.LogException(exception);
            if(!LogConfigurator.lazy_mode)
                this.log.Warn(exception.ToString());
            LogConfigurator.refresh();
        }

        public void fatal(string message)
        {
            Debug.LogAssertion(message);
            if(!LogConfigurator.lazy_mode)
                this.log.Fatal(message);
            LogConfigurator.refresh();
        }
    }

    
}

Log4Unity的初始化

Log4Unity初始化,使用Unity的MonoBehaviour来完成,同时打印些简单日志,检查日志文件位置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using System.IO;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using UnityEngine;

namespace Assets.Scripts.Utils.Log4Unity
{
public class LogConfigurator : MonoBehaviour
    {
        private static readonly string config_path = "log4unity.properties";
        private static readonly ILog logger = LogManager.GetLogger("LogConfigurator");
        private static bool config_load = false;
        public static bool refresh_realtime = true;
        public static bool lazy_mode = true;

        private static FileInfo fileInfo = new FileInfo(config_path);
        
        private void Awake()
        {
            if (fileInfo.Exists)
            {
                XmlConfigurator.Configure(fileInfo);
                IAppender appender = LogManager.GetRepository().GetAppenders()[0];
                if (appender.Name.Equals("FileAppender"))
                {
                    FileAppender fileAppender = (FileAppender) appender;

                    Debug.Log("[logpath]:" + fileAppender.File);
                }
                
                
                config_load = true;
            }
            else
            {
                Debug.LogError("class Log4Unity method Awake configfile " + fileInfo.FullName + " is not existed.");
            }
        }

        private void OnEnable()
        {
            logger.Debug("method OnEnable");
            if(config_load == true)
            {
                Application.logMessageReceivedThreaded += ThreadLog;
            }
        }

        private void ThreadLog(string condition, string stackTrace, LogType type)
        {
            if (LogType.Warning.Equals(type))
            {
                logger.Warn(condition);
            }
            else if(LogType.Log.Equals(type))
            {
                logger.Info(condition);
            }
            
            // fixed:double print
            if(lazy_mode)
                if (LogType.Exception.Equals(type))
                {
                    logger.Warn(condition);
                }
                else if (LogType.Error.Equals(type))
                {
                    logger.Error(condition);
                }
                else if (LogType.Assert.Equals(type))
                {
                    logger.Fatal(condition);
                }

            refresh();
        }

        public static void refresh()
        {
            if(refresh_realtime)
                fileInfo.Refresh();
        }

        private void OnDisable()
        {
            logger.Debug("method OnDisable");
            if(config_load == true)
            {
                Application.logMessageReceivedThreaded -= ThreadLog;
            }
        }

        private void OnDestroy()
        {
            logger.Debug("method OnDestroy");
            fileInfo.Refresh();
        }
    }
}

其他配置

log4unity.properties

注意放到exe

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version='1.0' encoding='UTF-8'?>
<log4net>
	<appender name="FileAppender" type="log4net.Appender.FileAppender">
		<file value="log4unity.log" />
		<appendToFile value="true" />
		<layout type="log4net.Layout.PatternLayout">
			<conversionPattern value="%date %-5level --- [%-5thread]  %-20logger : %message%newline" />
		</layout>
	</appender>
	
	<root>
        <level value="DEBUG" />
		<appender-ref ref="FileAppender" />
    </root>
</log4net>

log4net.dll

注意:Unity在Windows上有两种运行时DotNet2.0和DotNet4.6,都需要加载正确的dll版本。还有UnityEditor的行为在两个DotNet版本,运行的目录不同,注意日志的输出位置。build之后不会出现问题。

使用方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class FpsCounter : MonoBehaviour
    {
        private static readonly Log4Unity logger = LoggerFactory.getLogger("FpsCounter");
        
        ....
        
        private void Start()
        {
            logger.info("method Start");
            ....
        }

吐槽

最近开的坑有点多,先把这个补上

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/107717.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年3月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
unity3d:打印日志至文件
立羽
2023/08/24
3440
Unity基础(15)-Application与SceneManager类
StreamingAssets在各个平台上的文本支持读取方式 string path = System.IO.Path.Combine(Application.streamingAssetsPath,"version.txt");
孙寅
2020/06/02
1.5K0
Unity 自定义日志保存「建议收藏」
之前unity5.x在代码中写了debug.log..等等,打包之后在当前程序文件夹下会有个对应的”outlog.txt”,2017之后这个文件被移到C盘用户Appdata/LocalLow/公司名 文件夹下面。觉得不方便就自己写了个
全栈程序员站长
2022/09/14
5970
【5】基于Log4Net的日志系统
阅读目录 日志系统应具备的特性 Log4Net 配置文件:log4net.config 初始化 输出信息 对Log4Net的封装 log4net.config复杂配置 不管是Web应用程序还是WinForm应用程序,Visual Studio所带的调试功能都是足够强大,足以应付开发中的各种调试需求。但是,对于已经发布的应用,要记录错误、记载运行中的各种状态信息,就需要依靠日志系统了。 日志系统应具备的特性 一个好的日志系统,应该具备以下的特性: 1、运行稳定。因为日志的作用就是要在系统出现各种
用户1075292
2018/01/23
1.7K0
【5】基于Log4Net的日志系统
.NetCore 中使用Log4Net
Log4Net 使用 安装2个包 Microsoft.Extensions.Logging Microsoft.Extensions.Logging.Log4Net.AspNetCore 在Program中配置日志,请自行添加相关using internal static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) //配置log4net
鱼找水需要时间
2023/02/16
1K0
Log4Net ,.net和SQL Server的完美结合
不过,我在我这个项目中,具体使用的方法有点不同。 这个学生处学生信息管理系统,业务日志比较简单,其实并不需要使用log4net。所以在这个项目中,只用log4net来记录错误日志。 1.建立配置文件。
用户1258909
2018/07/03
9830
ASP.NET Core 2.0下使用log4net记录文件日志
我们知道log4net的日志功能非常强大,而使用方法也比较复杂;在ASP.NET Core 2.0下,可以通过一个第三方的扩展方法来降低我们的使用难度,具体使用方法如下: 我们先新建一个自己的静态类Log4Net,用于之后调用记录日志:
徐大嘴
2019/03/21
1.4K0
log4net使用解析
这边篇文章的目的是训练我们在项目中使用log4net,为了更加全面的使用log4net的功能,我们假设在app里面定义:
小蜜蜂
2019/07/15
8260
log4net使用注意事项
1配置Log4net Log4net的配置文件有几种使用方式,这里将配置log4net的部分独立出来,即关于log4net的配置独立成文件log4net.config。 1)写入Mysql log4net.config文件配置 <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> <configSections> <secti
甜橙很酸
2018/03/08
1.5K0
log4net使用注意事项
Log4Net异常日志记录在asp.net mvc3.0的应用
log4net是.Net下一个非常优秀的开源日志记录组件。log4net记录日志的功能非常强大。它可以将日志分不同的等级,以不同的格式,输出到不同的媒介。本文主要是简单的介绍如何在Visual Studio2010(Asp.Net Mvc3.0)中使用log4net快速创建系统日志,如何扩展以输出自定义字段。
aehyok
2018/09/11
7060
Log4Net异常日志记录在asp.net mvc3.0的应用
log4net原理解析
在任何项目中使用log4net,首先需要在web.config(app.config)文件中配置log4net相关信息。一般情况下,如下:
小蜜蜂
2019/07/15
1.6K0
log4net原理解析
Log4net用法
这里设置的目的,有两个,一为了得到log4net.config的文件,另一个就是日记的开关,日记是否开启
全栈程序员站长
2022/09/07
4160
Log4net用法
Unity封装定义自己喜欢的Log类型
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
bering
2019/12/03
1.1K0
.Net魔法堂:log4net详解
一、作用                              提供一个记录日志的框架,可以将日志信息记录到文件、控制台、Windows事件日志和数据库(MSSQL、Acess、Oracle、DB
^_^肥仔John
2018/01/18
6660
c# log4net 配置使用
新增配置文件log4net.config,内容如下 <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections>
冰封一夏
2019/09/11
1.6K0
WPF开发-全局异常捕获及日志记录
捕获异常 public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { RegisterEvents(); base.OnStartup(e); } private void RegisterEvents() { //Task线程内未捕获异常处理事件 TaskScheduler.UnobservedTaskE
码客说
2021/12/10
1.8K1
C# winform之Log4Net的使用
当我们将asp程序部署到远程服务器上的时候,如果遇到程序错误,如何能快速的判断我们程序的错误呢。所以--> Log4Net作为记录日志的一大神器,不得不学会熟练使用啊! 没有那么多的原理,照猫画虎的使用,保证你也能成功的使用这么简单的日志记录工具。
zls365
2020/08/19
3.7K0
C# winform之Log4Net的使用
使用Topshelf部署Windows服务
TopShelf支持使用Log4net,使用Nuget安装Topshelf.Log4Net:
yaphetsfang
2020/07/30
8190
Log4Net日志记录两种方式
     log4net库是Apache log4j框架在Microsoft .NET平台的实现,是一个帮助程序员将日志信息输出到各种目标(控制台、文件、数据库等)的工具。      log4net是Apache软件基金会Apache Logging Services工程的一部分。Apache日志服务工程致力于为程序调试和审计提供跨语言的日志服务。(f:百度百科)
跟着阿笨一起玩NET
2018/09/19
1.4K0
Log4Net日志记录两种方式
[C#]使用log4net记录日志
说明:本程序演示如何利用log4net记录程序日志信息。log4net是一个功能著名的开源日志记录组件。利用log4net可以方便地将日志信息记录到文件、控制台、Windows事件日志和数据库(包括MS SQL Server, Access, Oracle9i,Oracle8i,DB2,SQLite)中。
CNXY
2019/05/24
2.7K0
相关推荐
unity3d:打印日志至文件
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验