Unity日志管理

作者: Aodota | 来源:发表于2016-12-17 16:16 被阅读312次

    Unity日志管理

    Unity的日志输出在编辑器中,而且格式诡异,不方便查看。着实不利于开发,在Java开发时候我们有Log4jLogback等日志框架,格式定义舒服又方便。这不Unity好像没有,那就自己打造一个吧。

    需求分析

    1. 可以输出到文件
    2. 异步打印日志,提高性能
    3. 追加打印而不是重启重头开始
    4. 易于使用

    需求实现

    做过Java开发的人都知道,Java中的Log4j等日志框架都通过各种各样的Appender来扩展输出实现。
    这里只是以一种实现讲解一下思路:

    先定义一个Appender的接口

        /// <summary>
        /// 日志Appender
        /// </summary>
        public interface ILogAppender
        {
            /// <summary>
            /// 记录日志
            /// </summary>
            /// <param name="log">Log.</param>
            void Log(LogData logData);
        }
    

    来个RollingFileAppender的实现:

    public class RollingFileAppender : AsyncTask, ILogAppender
        {
            #if UNITY_EDITOR
            string logRootPath = Application.dataPath + "/Log";
            #elif UNITY_STANDALONE_WIN
            string logRootPath = Application.dataPath + "/Log";
            #elif UNITY_STANDALONE_OSX
            string logRootPath = Application.dataPath + "/Log";
            #else
            string logRootPath = Application.persistentDataPath + "/Log";
            #endif
            // 时间格式化
            protected const string TIME_FORMATER = "yyyy-MM-dd hh:mm:ss,fff";
    
            /// <summary>
            /// 文件最大值 10M
            /// </summary>
            private int maxFileSize = 10 * 1024 * 1024;
            // 文件Writer
            private StreamWriter streamWriter;
            // 文件流
            private FileStream fileStream;
            // 文件路径
            private string filePath;
            // 写日志队列
            private List<LogData> writeList;
            // 等待队列
            private List<LogData> waitList;
            // 锁
            private object lockObj;
            // 是否停止的标志
            private bool stopFlag;
            /// <summary>
            /// 构造函数
            /// </summary>
            public RollingFileAppender ()
            {
                this.filePath = Path.Combine (logRootPath, "game.log");
                if (File.Exists (filePath)) {
                    this.fileStream = new FileStream (filePath, FileMode.Append);
                    this.streamWriter = new StreamWriter (this.fileStream);
                    this.streamWriter.AutoFlush = true;
    
                } else {
                    if (!Directory.Exists (logRootPath)) {
                        Directory.CreateDirectory (logRootPath);
                    }
                    this.fileStream = new FileStream (filePath, FileMode.Create);
                    this.streamWriter = new StreamWriter (this.fileStream);
                    this.streamWriter.AutoFlush = true;
                }
                this.writeList = new List<LogData> ();
                this.waitList = new List<LogData> ();
                this.lockObj = new object ();
                this.stopFlag = false;
    
                // 开始执行
                MultiThreadMgr.AddAsyncTask (this);
            }
            /// <summary>
            /// 记录日志
            /// </summary>
            /// <param name="log">Log.</param>
            /// <param name="logData">Log data.</param>
            public void Log (LogData logData)
            {
                lock (lockObj) {
                    waitList.Add (logData);
                    Monitor.PulseAll (lockObj);
                }
            }
            /// <summary>
            /// 关闭执行
            /// </summary>
            public override void Close ()
            {
                this.stopFlag = true;
                if (null != this.fileStream) {
                    this.fileStream.Close ();
                }
            }
            /// <summary>
            /// 开始运行
            /// </summary>
            public override void Run ()
            {
                while (!stopFlag) {
                    lock (lockObj) {
                        if (waitList.Count == 0) {
                            Monitor.Wait (lockObj);            
                        }
                        this.writeList.AddRange (this.waitList);
                        this.waitList.Clear ();
                    }
    
                    foreach (LogData data in writeList) {
                        // 写日志
                        this.streamWriter.WriteLine (String.Format ("{0}#{1}#{2}", System.DateTime.Now.ToString (TIME_FORMATER), data.Tag, data.Log));
    
                        // 写堆栈
                        if (null != data.Track) {
                            this.streamWriter.WriteLine (data.Track);
                        }
    
                        // 判断是否触发策略
                        HandleTriggerEvent ();
                    }
                }
    
            }
            /// <summary>
            /// 处理Trigger事件
            /// </summary>
            private void HandleTriggerEvent() {
                if (this.fileStream.Length >= maxFileSize) {
                    // 文件超过大小,重头开始写
                    this.streamWriter.Close ();
                    this.fileStream.Close ();
    
                    // 重新开始写
                    this.fileStream = new FileStream (this.filePath, FileMode.Create);
                    this.streamWriter = new StreamWriter (this.fileStream);
                    this.streamWriter.AutoFlush = true;
                }
            }
        }
    

    LogData定义

        /// <summary>
        /// Log的具体内容
        /// </summary>
        public class LogData
        {
            /// <summary>
            /// Log具体内容
            /// </summary>
            /// <value>The log.</value>
            public string Log{ get; set; }
            /// <summary>
            /// Log标记
            /// </summary>
            /// <value>The tag.</value>
            public string Tag{ get; set; }
            /// <summary>
            /// Log堆栈信息
            /// </summary>
            /// <value>The track.</value>
            public string Track{ get; set; }
        }
    

    该实现用到了上一篇讲到的Unity多线程管理,基本思路如下:

    • 双队列,一个队列用于添加日志内容,一个队列用于写日志
    • 超过文件大小,重头开始写

    我对外暴露一个Log接口,实现如下:

    public class Log
        {
            // logAppender
            private ILogAppender logAppender;
            /// <summary>
            /// 日志等级,为不同输出配置用
            /// </summary>
            public const int DEBUG = 0;
            public const int INFO = 1;
            public const int WARNING = 2;
            public const int ERROR = 3;
            public const int FATAL = 4;
            // Log实例
            protected static Log instance = null;
            /// <summary>
            /// 获取实例
            /// </summary>
            /// <value>The instance.</value>
            public static Log Instance {
                get{ 
                    if (null == instance) {
                        instance = new Log ();
                    }
                    return instance;
                }
            } 
    
            /// <summary>
            /// 设置日志级别
            /// </summary>
            /// <value>The log level.</value>
            public int LogLevel {
                get;
                set;
            }
            /// <summary>
            /// 构造函数
            /// </summary>
            private Log() {
                LogLevel = INFO;
                logAppender = new RollingFileAppender();
            }
            /// <summary>
            /// debug Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void debug(object log) {
                if (LogLevel > DEBUG) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "DEBUG";
                logAppender.Log (data);
            }
            /// <summary>
            /// info Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void info(object log) {
                if (LogLevel > INFO) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "INFO";
                logAppender.Log (data);
            }
            /// <summary>
            /// warn Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void warn(object log) {
                if (LogLevel > WARNING) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "WARN";
                logAppender.Log (data);
            }
            /// <summary>
            /// warn Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void warn(object log, string track) {
                if (LogLevel > WARNING) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "WARN";
                data.Track = track;
                logAppender.Log (data);
            }
            /// <summary>
            /// warn Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void warn(object log, Exception e) {
                if (LogLevel > WARNING) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "WARN";
                data.Track = GetExceptionTrack(e);
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void error(object log) {
                if (LogLevel > ERROR) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "ERROR";
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void error(object log, string track) {
                if (LogLevel > ERROR) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "ERROR";
                data.Track = track;
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void error(object log, Exception e) {
                if (LogLevel > ERROR) {
                    return;
                }
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "ERROR";
                data.Track = GetExceptionTrack(e);
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void fatal(object log) {
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "FATAL";
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void fatal(object log, string track) {
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "FATAL";
                data.Track = track;
                logAppender.Log (data);
            }
            /// <summary>
            /// error Log
            /// </summary>
            /// <param name="log">Log.</param>
            public void fatal(object log, Exception e) {
                LogData data = new LogData ();
                data.Log = log.ToString();
                data.Tag = "FATAL";
                data.Track = GetExceptionTrack(e);
                logAppender.Log (data);
            }
            /// <summary>
            /// 获取异常堆栈
            /// </summary>
            /// <returns>The exception track.</returns>
            /// <param name="e">E.</param>
            private string GetExceptionTrack(Exception e) {
                StringBuilder builder = new StringBuilder (120);
                builder.Append (e.Message).Append("\n");
                if (!string.IsNullOrEmpty (e.StackTrace)) {
                    builder.Append (e.StackTrace);
                }
                return builder.ToString ();
    //
    //            StackTrace stackTrace = e.StackTrace;
    //            StackFrame[] stackFrames = stackTrace.GetFrames ();
    //            foreach (StackFrame frame in stackFrames) {
    //                builder.Append ("\t at ")
    //                    .Append (frame.GetFileName())
    //                    .Append (".")
    //                    .Append(frame.GetMethod())
    //                    .Append(":")
    //                    .Append(frame.GetFileLineNumber())
    //                    .Append("\n");
    //            }
    //            return builder.ToString ();
            }
        }
    

    后续改进

    1. 多Appender支持,至少要扩展ConsoleAppender
    2. 可配置

    相关文章

      网友评论

        本文标题:Unity日志管理

        本文链接:https://www.haomeiwen.com/subject/iqaomttx.html