Log system reconstruction of unity3d

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

Similar Posts: