许可优化
产品
解决方案
服务支持
关于
软件库
当前位置:服务支持 >  软件文章 >  Unity3d的Log系统重构

Unity3d的Log系统重构

阅读数 4
点赞 0
article_banner

 转载https://my.oschina.net/hava/blog/1618555

编者注

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

Unity3d的Log

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

Debug.Log("hello message");
cs
运行

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

Unity3d的Debug原理

原理分析

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

  1. public static void Log(object message)
  2. {
  3. Debug.unityLogger.Log(LogType.Log, message);
  4. }
cs
运行

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

  1. public static ILogger unityLogger
  2. {
  3. get
  4. {
  5. return Debug.s_Logger;
  6. }
  7. }
cs
运行

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

internal static ILogger s_Logger = (ILogger) new Logger((ILogHandler) new DebugLogHandler());
cs
运行

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

  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using UnityEngine.Scripting;
  4. namespace UnityEngine
  5. {
  6. internal sealed class DebugLogHandler : ILogHandler
  7. {
  8. [ThreadAndSerializationSafe]
  9. [GeneratedByOldBindingsGenerator]
  10. [MethodImpl(MethodImplOptions.InternalCall)]
  11. internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);
  12. [ThreadAndSerializationSafe]
  13. [GeneratedByOldBindingsGenerator]
  14. [MethodImpl(MethodImplOptions.InternalCall)]
  15. internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);
  16. public void LogFormat(LogType logType, Object context, string format, params object[] args)
  17. {
  18. DebugLogHandler.Internal_Log(logType, string.Format(format, args), context);
  19. }
  20. public void LogException(Exception exception, Object context)
  21. {
  22. DebugLogHandler.Internal_LogException(exception, context);
  23. }
  24. }
  25. }
cs
运行

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

  1. internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);
  2. internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);
cs
运行

代码测试

Debug.unityLogger.Log(LogType.Log,"hello message");
cs
运行

UnityEditor打印

  1. hello message
  2. UnityEngine.Logger:Log(LogType, Object)
  3. InitController:Start() (at Assets/Scripts/Controller/InitController.cs:14)
cs
运行

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

UnityEngine.Application

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

public delegate void LogCallback(string condition, string stackTrace, LogType type);
cs
运行

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

  1. using UnityEngine;
  2. using System.Collections;
  3. public class ExampleClass : MonoBehaviour {
  4. public string output = "";
  5. public string stack = "";
  6. void OnEnable() {
  7. Application.logMessageReceived += HandleLog;
  8. }
  9. void OnDisable() {
  10. Application.logMessageReceived -= HandleLog;
  11. }
  12. void HandleLog(string logString, string stackTrace, LogType type) {
  13. output = logString;
  14. stack = stackTrace;
  15. }
  16. }
cs
运行

原理分析

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

  1. public static event Application.LogCallback logMessageReceived
  2. {
  3. add
  4. {
  5. Application.s_LogCallbackHandler += value;
  6. Application.SetLogCallbackDefined(true);
  7. }
  8. remove
  9. {
  10. Application.s_LogCallbackHandler -= value;
  11. }
  12. }
  13. public static event Application.LogCallback logMessageReceivedThreaded
  14. {
  15. add
  16. {
  17. Application.s_LogCallbackHandlerThreaded += value;
  18. Application.SetLogCallbackDefined(true);
  19. }
  20. remove
  21. {
  22. Application.s_LogCallbackHandlerThreaded -= value;
  23. }
  24. }
cs
运行

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

  1. [RequiredByNativeCode]
  2. private static void CallLogCallback(string logString, string stackTrace, LogType type, bool invokedOnMainThread)
  3. {
  4. if (invokedOnMainThread)
  5. {
  6. Application.LogCallback logCallbackHandler = Application.s_LogCallbackHandler;
  7. if (logCallbackHandler != null)
  8. logCallbackHandler(logString, stackTrace, type);
  9. }
  10. Application.LogCallback callbackHandlerThreaded = Application.s_LogCallbackHandlerThreaded;
  11. if (callbackHandlerThreaded == null)
  12. return;
  13. callbackHandlerThreaded(logString, stackTrace, type);
  14. }
cs
运行

SetLogCallbackDefined

  1. [GeneratedByOldBindingsGenerator]
  2. [MethodImpl(MethodImplOptions.InternalCall)]
  3. private static extern void SetLogCallbackDefined(bool defined);
cs
运行

日志系统设计

需求

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

Log4Net

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

接口设计

LoggerFactory

  1. using log4net;
  2. namespace Assets.Scripts.Utils.Log4Unity
  3. {
  4. public class LoggerFactory
  5. {
  6. public static Log4Unity getLogger(string name)
  7. {
  8. return new Log4Unity(LogManager.GetLogger(name));
  9. }
  10. }
  11. }
cs
运行

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

  1. using System;
  2. using System.IO;
  3. using log4net;
  4. using log4net.Config;
  5. using UnityEngine;
  6. namespace Assets.Scripts.Utils.Log4Unity
  7. {
  8. public class Log4Unity
  9. {
  10. private ILog log;
  11. public Log4Unity(ILog log)
  12. {
  13. this.log = log;
  14. }
  15. public void debug(string message)
  16. {
  17. this.log.Debug(message);
  18. LogConfigurator.refresh();
  19. }
  20. public void info(string message)
  21. {
  22. this.log.Info(message);
  23. LogConfigurator.refresh();
  24. }
  25. public void warning(string message)
  26. {
  27. this.log.Warn(message);
  28. LogConfigurator.refresh();
  29. }
  30. public void error(string message)
  31. {
  32. Debug.LogError(message);
  33. if(!LogConfigurator.lazy_mode)
  34. this.log.Error(message);
  35. LogConfigurator.refresh();
  36. }
  37. public void exception(Exception exception)
  38. {
  39. Debug.LogException(exception);
  40. if(!LogConfigurator.lazy_mode)
  41. this.log.Warn(exception.ToString());
  42. LogConfigurator.refresh();
  43. }
  44. public void fatal(string message)
  45. {
  46. Debug.LogAssertion(message);
  47. if(!LogConfigurator.lazy_mode)
  48. this.log.Fatal(message);
  49. LogConfigurator.refresh();
  50. }
  51. }
  52. }
cs
运行

Log4Unity的初始化

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

  1. using System.IO;
  2. using log4net;
  3. using log4net.Appender;
  4. using log4net.Config;
  5. using log4net.Core;
  6. using UnityEngine;
  7. namespace Assets.Scripts.Utils.Log4Unity
  8. {
  9. public class LogConfigurator : MonoBehaviour
  10. {
  11. private static readonly string config_path = "log4unity.properties";
  12. private static readonly ILog logger = LogManager.GetLogger("LogConfigurator");
  13. private static bool config_load = false;
  14. public static bool refresh_realtime = true;
  15. public static bool lazy_mode = true;
  16. private static FileInfo fileInfo = new FileInfo(config_path);
  17. private void Awake()
  18. {
  19. if (fileInfo.Exists)
  20. {
  21. XmlConfigurator.Configure(fileInfo);
  22. IAppender appender = LogManager.GetRepository().GetAppenders()[0];
  23. if (appender.Name.Equals("FileAppender"))
  24. {
  25. FileAppender fileAppender = (FileAppender) appender;
  26. Debug.Log("[logpath]:" + fileAppender.File);
  27. }
  28. config_load = true;
  29. }
  30. else
  31. {
  32. Debug.LogError("class Log4Unity method Awake configfile " + fileInfo.FullName + " is not existed.");
  33. }
  34. }
  35. private void OnEnable()
  36. {
  37. logger.Debug("method OnEnable");
  38. if(config_load == true)
  39. {
  40. Application.logMessageReceivedThreaded += ThreadLog;
  41. }
  42. }
  43. private void ThreadLog(string condition, string stackTrace, LogType type)
  44. {
  45. if (LogType.Warning.Equals(type))
  46. {
  47. logger.Warn(condition);
  48. }
  49. else if(LogType.Log.Equals(type))
  50. {
  51. logger.Info(condition);
  52. }
  53. // fixed:double print
  54. if(lazy_mode)
  55. if (LogType.Exception.Equals(type))
  56. {
  57. logger.Warn(condition);
  58. }
  59. else if (LogType.Error.Equals(type))
  60. {
  61. logger.Error(condition);
  62. }
  63. else if (LogType.Assert.Equals(type))
  64. {
  65. logger.Fatal(condition);
  66. }
  67. refresh();
  68. }
  69. public static void refresh()
  70. {
  71. if(refresh_realtime)
  72. fileInfo.Refresh();
  73. }
  74. private void OnDisable()
  75. {
  76. logger.Debug("method OnDisable");
  77. if(config_load == true)
  78. {
  79. Application.logMessageReceivedThreaded -= ThreadLog;
  80. }
  81. }
  82. private void OnDestroy()
  83. {
  84. logger.Debug("method OnDestroy");
  85. fileInfo.Refresh();
  86. }
  87. }
  88. }
cs
运行

其他配置

log4unity.properties

注意放到exe

  1. <?xml version='1.0' encoding='UTF-8'?>
  2. <log4net>
  3. <appender name="FileAppender" type="log4net.Appender.FileAppender">
  4. <file value="log4unity.log" />
  5. <appendToFile value="true" />
  6. <layout type="log4net.Layout.PatternLayout">
  7. <conversionPattern value="%date %-5level --- [%-5thread] %-20logger : %message%newline" />
  8. </layout>
  9. </appender>
  10. <root>
  11. <level value="DEBUG" />
  12. <appender-ref ref="FileAppender" />
  13. </root>
  14. </log4net>
cs
运行

log4net.dll

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

使用方法

  1. public class FpsCounter : MonoBehaviour
  2. {
  3. private static readonly Log4Unity logger = LoggerFactory.getLogger("FpsCounter");
  4. ....
  5. private void Start()
  6. {
  7. logger.info("method Start");
  8. ....
  9. }
cs
运行

吐槽

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


免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删
相关文章
QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空