大家好,又见面了,我是全栈君。
编者注
由于要重写Unity3d的Log系统,变更为自定义方式,按照Log4j的显示的内容方法
Unity3d的Log
一般在Unity3d中编写日志入下代码
代码语言:javascript复制Debug.Log("hello message");
在UnityEditor和UnityEngine当中除了打印message以外,还会打印堆栈信息。性能低下,根据有经验的人讲解,在客户端打印大量日志,会严重降低渲染性能。
Unity3d的Debug原理
原理分析
在Rider中查看Debug.Log的实现,我们可以看到如下内容
代码语言:javascript复制public static void Log(object message)
{
Debug.unityLogger.Log(LogType.Log, message);
}
我们能够了解到,实质是调用了Debug.unityLogger
public static ILogger unityLogger
{
get
{
return Debug.s_Logger;
}
}
unityLogger实质是调用了Debug.s_Logger,而在下面就定义了s_Logger的实现
代码语言:javascript复制internal static ILogger s_Logger = (ILogger) new Logger((ILogHandler) new DebugLogHandler());
DebugLogHandler实质是调用的静态方法,根据UnityEngine各个平台的实现进行调用
代码语言:javascript复制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复制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复制Debug.unityLogger.Log(LogType.Log,"hello message");
UnityEditor打印
代码语言:javascript复制hello message
UnityEngine.Logger:Log(LogType, Object)
InitController:Start() (at Assets/Scripts/Controller/InitController.cs:14)
结论:无法解决减少堆栈信息打印的问题
UnityEngine.Application
根据UnityEngine.Application的LogCallback文档,我们能够了解和LogCallback相关的 Application.logMessageReceived和Application.logMessageReceivedThreaded:
代码语言:javascript复制public delegate void LogCallback(string condition, string stackTrace, LogType type);
由于LogCallback使用的是delegate委托方法,则需要指定实现方法,如下Unity官方推荐实现方法
代码语言:javascript复制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复制 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复制 [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复制 [GeneratedByOldBindingsGenerator]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void SetLogCallbackDefined(bool defined);
日志系统设计
需求
- 不影响Unity
- 文件方式输出
- 支持Unity Debug
- 支持输出日志级别
Log4Net
根据之前Java的方式,Log4j很好用,首先决定仿照slf4j的接口方式进行使用。其次使用Log4net的实现,实现需求,只要不影响Unity运行即可。实际测试并未影响Unity运行。
接口设计
LoggerFactory
代码语言:javascript复制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复制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复制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复制<?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="�te %-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复制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