Editor’s note
Because the log system of unity3d needs to be rewritten, it is changed to the custom mode, and the content method of log4j is used
Log of unity3d
In general, write the log entry code in unity3d
Debug.Log("hello message");
In unityeditor and unityengine, besides message, stack information is also printed. The performance is low. According to an experienced person, printing a large number of logs on the client will seriously reduce the rendering performance
Debug principle of unity3d
Principle analysis
Check the implementation of debug. Log in rider, and we can see the following
public static void Log(object message)
{
Debug.unityLogger.Log(LogType.Log, message);
}
We can understand that the essence is to call debug. Unitylogger
public static ILogger unityLogger
{
get
{
return Debug.s_Logger;
}
}
Unitylogger actually calls debug. S_ Logger, and S is defined below_ Implementation of logger
internal static ILogger s_Logger = (ILogger) new Logger((ILogHandler) new DebugLogHandler());
The essence of debugglohandler is to call static methods, which are called according to the implementation of various platforms of 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);
}
}
}
There are only two ways to achieve it
internal static extern void Internal_Log(LogType level, string msg, [Writable] Object obj);
internal static extern void Internal_LogException(Exception exception, [Writable] Object obj);
Code testing
Debug.unityLogger.Log(LogType.Log,"hello message");
Unityeditor printing
hello message
UnityEngine.Logger:Log(LogType, Object)
InitController:Start() (at Assets/Scripts/Controller/InitController.cs:14)
Conclusion: the problem of reducing stack information printing cannot be solved
UnityEngine.Application
According to the logcallback document of unityengine. Application, we can understand the application. Logmessage received and application. Logmessage received threaded related to logcallback
public delegate void LogCallback(string condition, string stackTrace, LogType type);
Since logcallback uses the delegate delegate method, you need to specify the implementation method, which is officially recommended by unity as follows
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;
}
}
Principle analysis
Logmessage received only works on the main thread. That’s the main line of unity. And the document describes the implementation method is not thread safe. The implementation code of logmessage receivedthreaded must be thread safe and support access from outside the main thread
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;
}
}
According to CSharp’s annotation, calllogcallback is called by local code. In other words, the logcallbackhandler implementation of the main thread will be called first, and then the callbackhandlerthreaded implementation will be called regardless of whether the main thread is called or not
[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
[GeneratedByOldBindingsGenerator]
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void SetLogCallbackDefined(bool defined);
Design of log system
Demand
It doesn’t affect unity
File output
Support unity debug
Support output log level
Log4Net
According to the previous Java way, log4j is easy to use. First, we decided to use it in the way of slf4j interface. Secondly, log4net is used to realize the requirements, as long as it does not affect the running of unity. The actual test did not affect the unity operation
Interface design
LoggerFactory
using log4net;
namespace Assets.Scripts.Utils.Log4Unity
{
public class LoggerFactory
{
public static Log4Unity getLogger(string name)
{
return new Log4Unity(LogManager.GetLogger(name));
}
}
}
Log4unity: originally, I wanted to use logger directly, but it was occupied by 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();
}
}
}
Initialization of log4unity
Log4unity initialization, using unity’s monobehavior to complete, while printing some simple logs, check the log file location
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
<?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
Note: unity has two runtimes dotnet2.0 and dotnet4.6 on windows, both of which need to load the correct DLL version. In addition, the behavior of unityeditor is different in the two dotnet versions. Pay attention to the output location of the log. There is no problem after build
How to use it
public class FpsCounter : MonoBehaviour
{
private static readonly Log4Unity logger = LoggerFactory.getLogger("FpsCounter");
....
private void Start()
{
logger.info("method Start");
....
}
make complaints about
There are a lot of holes recently. Fill this up first