转载https://my.oschina.net/hava/blog/1618555
由于要重写Unity3d的Log系统,变更为自定义方式,按照Log4j的显示的内容方法
一般在Unity3d中编写日志入下代码
Debug.Log("hello message");
cs运行
在UnityEditor和UnityEngine当中除了打印message以外,还会打印堆栈信息。性能低下,根据有经验的人讲解,在客户端打印大量日志,会严重降低渲染性能。
在Rider中查看Debug.Log的实现,我们可以看到如下内容
- public static void Log(object message)
- {
- Debug.unityLogger.Log(LogType.Log, message);
- }
cs运行
我们能够了解到,实质是调用了Debug.unityLogger
- public static ILogger unityLogger
- {
- get
- {
- return Debug.s_Logger;
- }
- }
cs运行
unityLogger实质是调用了Debug.s_Logger,而在下面就定义了s_Logger的实现
internal static ILogger s_Logger = (ILogger) new Logger((ILogHandler) new DebugLogHandler());
cs运行
DebugLogHandler实质是调用的静态方法,根据UnityEngine各个平台的实现进行调用
- 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);
- }
- }
- }
cs运行
最终实现仅仅只有两个方法
- internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);
- internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);
cs运行
Debug.unityLogger.Log(LogType.Log,"hello message");
cs运行
UnityEditor打印
- hello message
- UnityEngine.Logger:Log(LogType, Object)
- InitController:Start() (at Assets/Scripts/Controller/InitController.cs:14)
cs运行
结论:无法解决减少堆栈信息打印的问题
根据UnityEngine.Application的LogCallback文档,我们能够了解和LogCallback相关的 Application.logMessageReceived和Application.logMessageReceivedThreaded:
public delegate void LogCallback(string condition, string stackTrace, LogType type);
cs运行
由于LogCallback使用的是delegate委托方法,则需要指定实现方法,如下Unity官方推荐实现方法
- 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;
- }
- }
cs运行
logMessageReceived仅仅在 main 线程工作。也就是Unity的主线程当中。并且文档描述实现方法非线程安全。logMessageReceivedThreaded实现的代码必须是线程安全,支持从主线程之外进行访问。
- 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;
- }
- }
cs运行
CallLogCallback从CSharp的注解来看,就是需要本地代码进行调用。直接调用的,也就是说,会优先调用主线程的logCallbackHandler实现,然后无论是否主线程都调用callbackHandlerThreaded的实现
- [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);
- }
cs运行
SetLogCallbackDefined
- [GeneratedByOldBindingsGenerator]
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern void SetLogCallbackDefined(bool defined);
cs运行
根据之前 Java 的方式,Log4j很好用,首先决定仿照slf4j的接口方式进行使用。其次使用Log4net的实现,实现需求,只要不影响Unity运行即可。实际测试并未影响Unity运行。
LoggerFactory
- using log4net;
-
- namespace Assets.Scripts.Utils.Log4Unity
- {
- public class LoggerFactory
- {
- public static Log4Unity getLogger(string name)
- {
- return new Log4Unity(LogManager.GetLogger(name));
- }
- }
- }
cs运行
Log4Unity:原本想直接使用logger,但是被Unity占用了
- 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();
- }
- }
-
-
- }
cs运行
Log4Unity初始化,使用Unity的MonoBehaviour来完成,同时打印些简单日志,检查日志文件位置
- 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();
- }
- }
- }
cs运行
注意放到exe
- <?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>
cs运行
注意:Unity在Windows上有两种运行时DotNet2.0和DotNet4.6,都需要加载正确的dll版本。还有UnityEditor的行为在两个 DotNet 版本,运行的目录不同,注意日志的输出位置。build之后不会出现问题。
- public class FpsCounter : MonoBehaviour
- {
- private static readonly Log4Unity logger = LoggerFactory.getLogger("FpsCounter");
-
- ....
-
- private void Start()
- {
- logger.info("method Start");
- ....
- }
-
cs运行
最近开的坑有点多,先把这个补上