Unity日志管理
Unity的日志输出在编辑器中,而且格式诡异,不方便查看。着实不利于开发,在Java开发时候我们有Log4j
,Logback
等日志框架,格式定义舒服又方便。这不Unity好像没有,那就自己打造一个吧。
需求分析
- 可以输出到文件
- 异步打印日志,提高性能
- 追加打印而不是重启重头开始
- 易于使用
需求实现
做过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 ();
}
}
后续改进
- 多Appender支持,至少要扩展ConsoleAppender
- 可配置
网友评论