Unity的Application.logMessageReceived
在Unity中,Application.logMessageReceived
是一个非常实用的事件委托,用于捕获和处理Unity运行时输出的日志信息(包括普通日志、警告和错误)。通过监听这个事件,开发者可以自定义日志的处理逻辑,比如将日志保存到本地文件、上传到服务器,或在游戏内UI中显示等。
基本定义
Application.logMessageReceived
的官方定义如下:
public static event Application.LogCallback logMessageReceived;
其中,Application.LogCallback
是一个委托类型,定义为:
public delegate void LogCallback(string condition, string stackTrace, LogType type);
委托的三个参数分别表示:
condition
:日志的具体内容(如Debug.Log("Hello")
中的"Hello"
)。stackTrace
:日志产生的堆栈跟踪信息(主要用于定位错误位置)。type
:日志类型,是LogType
枚举值(包括Log
、Warning
、Error
、Assert
、Exception
等)。
核心作用
监听 Application.logMessageReceived
事件后,所有通过 Debug.Log
、Debug.LogWarning
、Debug.LogError
等方法输出的日志,以及Unity内部产生的错误(如空引用异常),都会触发该事件的回调函数。
这一机制的典型应用场景包括:
- 日志持久化:将运行时日志保存到本地文件,方便后续调试(尤其适用于移动端或发布后的版本)。
- 远程日志上报:将关键错误日志上传到服务器,用于监控线上版本的稳定性。
- 游戏内日志显示:在游戏UI中实时显示日志(如调试面板),方便测试人员查看。
- 自定义日志过滤:根据日志类型(如只处理错误日志)执行特定逻辑(如弹出错误提示)。
使用示例
以下是一个简单的示例,演示如何监听 logMessageReceived
并将日志保存到本地文件:
using UnityEngine;
using System.IO;
public class LogHandler : MonoBehaviour
{
private string logFilePath;
void Start()
{
// 初始化日志文件路径(如Application.persistentDataPath目录)
logFilePath = Path.Combine(Application.persistentDataPath, "game_log.txt");
// 注册日志回调
Application.logMessageReceived += OnLogMessageReceived;
}
// 日志回调函数
void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
// 构建日志内容(包含时间、类型、信息和堆栈)
string log = $"[{System.DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{type}] {condition}\n";
if (type == LogType.Error || type == LogType.Exception)
{
// 错误日志附加堆栈信息
log += $"StackTrace: {stackTrace}\n";
}
// 写入文件(追加模式)
File.AppendAllText(logFilePath, log);
}
void OnDestroy()
{
// 注销回调(避免内存泄漏)
Application.logMessageReceived -= OnLogMessageReceived;
}
}
注意事项
-
线程安全:
logMessageReceived
的回调函数运行在主线程,因此可以直接操作Unity的API(如UI更新)。如果需要处理多线程日志,可使用logMessageReceivedThreaded
(在后台线程触发,需注意线程安全)。 -
注销事件:务必在对象销毁时(如
OnDestroy
中)注销事件(-=
),否则可能导致内存泄漏(因为事件会持有对象的引用,阻止其被GC回收)。 -
日志类型区分:通过
LogType
可以区分日志级别,例如只处理错误日志:if (type == LogType.Error || type == LogType.Exception) { // 处理错误逻辑(如弹窗提示) }
-
移动端权限:在移动端保存日志文件时,需确保应用有写入权限(
Application.persistentDataPath
目录通常是安全的)。 -
替代方案:Unity 2017+ 引入了
ILogHandler
接口,可通过自定义日志处理器实现更灵活的日志管理,但logMessageReceived
仍是简单场景下的首选。
扩展:logMessageReceivedThreaded
Application.logMessageReceivedThreaded
与 logMessageReceived
功能类似,但回调函数运行在后台线程(而非主线程)。适用于处理大量日志或耗时操作(如网络上传),但需注意:
- 不能在回调中直接操作Unity的API(如
GameObject
、UI
),否则会导致崩溃。 - 需要通过线程同步机制(如
lock
)处理共享资源(如文件写入)。
完整的代码示例:
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Text;
public class LogHandler : MonoBehaviour
{
// 单例模式,便于全局访问
public static LogHandler Instance { get; private set; }
[Header("配置选项")]
[SerializeField] private bool logToFile = true;
[SerializeField] private bool logToUI = true;
[SerializeField] private int maxUILogEntries = 20;
[SerializeField] private string logFileName = "game_log.txt";
private string logFilePath;
private readonly List<string> uiLogEntries = new List<string>();
private StringBuilder logBuilder = new StringBuilder();
private void Awake()
{
// 确保单例唯一性
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
// 初始化日志文件路径
logFilePath = Path.Combine(Application.persistentDataPath, logFileName);
// 注册日志回调
Application.logMessageReceived += HandleLog;
}
private void OnDestroy()
{
// 注销日志回调,防止内存泄漏
Application.logMessageReceived -= HandleLog;
}
private void HandleLog(string logString, string stackTrace, LogType type)
{
// 构建完整的日志条目
string logEntry = $"[{System.DateTime.Now:HH:mm:ss}] [{type}] {logString}";
// 如果是错误或异常,添加堆栈信息
if (type == LogType.Error || type == LogType.Exception || type == LogType.Assert)
{
logEntry += $"\nStackTrace: {stackTrace}";
}
// 记录到文件
if (logToFile)
{
WriteLogToFile(logEntry);
}
// 更新UI显示
if (logToUI)
{
UpdateUILogs(logEntry);
}
}
private void WriteLogToFile(string logEntry)
{
try
{
// 追加日志到文件
File.AppendAllText(logFilePath, logEntry + "\n");
}
catch (System.Exception e)
{
Debug.LogError($"无法写入日志文件: {e.Message}");
}
}
private void UpdateUILogs(string logEntry)
{
// 添加到日志列表
uiLogEntries.Add(logEntry);
// 限制日志数量
if (uiLogEntries.Count > maxUILogEntries)
{
uiLogEntries.RemoveAt(0);
}
}
// 用于在UI上显示日志
private void OnGUI()
{
if (!logToUI) return;
// 构建UI显示的日志文本
logBuilder.Length = 0;
foreach (string entry in uiLogEntries)
{
logBuilder.AppendLine(entry);
}
// 创建一个滚动视图显示日志
GUILayout.BeginArea(new Rect(10, 10, Screen.width - 20, Screen.height - 20));
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
GUILayout.TextArea(logBuilder.ToString(), GUI.skin.textArea, GUILayout.ExpandHeight(true));
GUILayout.EndScrollView();
GUILayout.EndArea();
}
private Vector2 scrollPosition = Vector2.zero;
// 获取日志文件路径(用于调试或导出)
public string GetLogFilePath()
{
return logFilePath;
}
// 清除UI日志
public void ClearUILogs()
{
uiLogEntries.Clear();
}
// 清除日志文件
public void ClearLogFile()
{
try
{
if (File.Exists(logFilePath))
{
File.Delete(logFilePath);
}
}
catch (System.Exception e)
{
Debug.LogError($"无法清除日志文件: {e.Message}");
}
}
}