日志
分类、可过滤的日志系统,支持按构建级别静音、轮转文件后端与可插拔输出。
面向游戏代码与框架的分类日志 · 第一个启动的服务 · 无操作替代:
NullLogger
功能概述
Logger 把 UnityEngine.Debug.Log 包装成一个统一、可过滤的诊断系统。它提供构建级别的静音(Release 构建自动丢弃 Debug 级别消息)、按分类的开/关开关,以及可插拔的输出后端——默认是 Unity Console,也可以是轮转日志文件,或你自己的崩溃上报接收端。你的游戏代码和每个框架服务不再到处散落 Debug.Log 调用,而是通过 Logger.Info(category, message) 输出诊断信息,格式一致,并且有一个集中控制一切的地方。
快速示例
只调试存档时抑制控制台噪音:
using CommonGameSystem.Core;
using UnityEngine;
using Logger = CommonGameSystem.Core.Logger; // Unity has its own Logger type
[DefaultExecutionOrder(100)] // Run after the framework has started
public class DebugConfig : MonoBehaviour
{
private void Awake()
{
// Silence everything except the Save category
Logger.MinimumLevel = LogLevel.Off;
Logger.SetCategoryFilter(Logger.Categories.Save, LogLevel.Debug);
}
}
// Elsewhere — only Save messages appear in the Console
Logger.Info(Logger.Categories.Net, "heartbeat ok"); // Silent
Logger.Info(Logger.Categories.Save, "slot 0 corrupted"); // Shown
你可以在任何地方使用静态 Logger 辅助方法而无需解析任何东西——它们会在启动时替你缓存日志后端。如果你偏好显式依赖(例如为了在测试中注入伪实现),可以从服务定位器解析 ILogger 并在 Awake 中缓存。
完整 API 一览
发射方法(全部为静态、线程安全)
Logger.Debug(string category, string message)—— Debug 级别;整个调用点会被编译器从 Release 构建中移除Logger.Debug(string message)—— 使用Categories.Default的 DebugLogger.Info(string category, string message)—— Info 级别Logger.Info(string message)—— 使用Categories.Default的 InfoLogger.Warning(string category, string message)—— Warning 级别Logger.Warning(string message)—— 使用Categories.Default的 WarningLogger.Error(string category, string message)—— Error 级别Logger.Error(string message)—— 使用Categories.Default的 ErrorLogger.Critical(string category, string message)—— Critical 级别Logger.Critical(string message)—— 使用Categories.Default的 CriticalLogger.Exception(string category, Exception ex, string message = null)—— 记录异常及其堆栈跟踪和可选的上下文消息;ex为 null 时抛出ArgumentNullException
任何发射方法收到 null 分类时都会回落到 Categories.Default。
过滤控制(仅限主线程)
Logger.MinimumLevel { get; set; }—— 全局级别下限。Editor 和 Development 构建中默认为Debug,Release 构建中默认为Warning。设为LogLevel.Off可静音一切——立即生效,静态辅助方法也包括在内。Logger.SetCategoryFilter(string category, LogLevel level)—— 覆盖某个分类的下限;分类为 null 时抛出ArgumentNullException。Logger.ClearCategoryFilter(string category)—— 移除某个分类的覆盖,回到全局下限;分类为 null 时抛出ArgumentNullException。
LogLevel 取值
| 取值 | 含义 |
|---|---|
Debug | 逐帧追踪与冗长细节。会从 Release 构建中完全剔除。 |
Info | 状态转换与生命周期事件。始终参与编译。 |
Warning | 已恢复的故障、可疑的输入、已弃用的路径。始终参与编译。 |
Error | 某个操作失败了,但游戏继续运行。始终参与编译。 |
Critical | 进程完整性已受损——下一次失败很可能是致命的。始终参与编译。 |
Off | 哨兵值——作为下限设置时会过滤掉所有级别。 |
标准分类(字符串常量)
Logger.Categories.Default、.Bootstrap、.ServiceLocator、.Save、.Audio、.UI、.Input、.AI、.Net、.Perf(性能:池、时间、定时器)、.Configuration、.Scene、.Localization、.Achievements。
分类键区分大小写——始终使用这些常量,绝不要用字符串字面量。
ILogger 接口(实现它以提供自定义后端)
void Log(LogLevel level, string category, string message)—— 发射一条消息void LogException(string category, Exception ex, string message)—— 发射异常及其堆栈跟踪LogLevel MinimumLevel { get; set; }—— 实例级的级别下限LogLevel GetEffectiveLevel(string category)—— 查询某个分类实际生效的下限;分类为 null 时抛出ArgumentNullException
内置后端
-
UnityConsoleLogger—— 默认后端。由启动引导注册;把格式化后的行写入 Unity Console。 -
FileLogger—— 可选启用的轮转文件日志器,用于上线后的支持工作。Unity 自带的Player.log是一条不加区分的单一流,而且在 Windows 上会在下次启动时被覆盖——等玩家把它发给你时,证据往往已经没了。FileLogger在每一行保留分类与级别,并轮转文件,让历史得以保存:using CommonGameSystem.Core; using UnityEngine; [DefaultExecutionOrder(100)] public class FileLoggerInstaller : MonoBehaviour { private void Awake() { var fileLog = new FileLogger(); // {persistentDataPath}/logs/cgs.log fileLog.CaptureUnityLogStream(true); // also record uncaught exceptions ServiceLocator.Replace<ILogger>(fileLog); } }FileLogger(long maxBytes = 4 MB, int maxFiles = 3)—— 日志写入{persistentDataPath}/logs/cgs.log,文件超过maxBytes时轮转,并保留包含当前文件在内的maxFiles个文件。请在主线程上构造它(它会读取一次Application.persistentDataPath)。FileLogger(string path, long maxBytes, int maxFiles)—— 日志写入显式指定的路径;目录不存在时会自动创建。路径为 null 或空白时抛出ArgumentException。FilePath—— 当前正在写入的文件的绝对路径。CaptureUnityLogStream(bool enabled)—— 把 Unity 自身的日志流镜像到文件中:未捕获的异常及其堆栈跟踪,以及任何从未经过ILogger的Debug.Log。当UnityConsoleLogger同时处于激活状态时不要启用它——那个日志器通过Debug.Log写出,因此每条框架日志都会被捕获两次。- 线程安全(每次写入都通过锁串行化)并且从不抛出:I/O 失败会被吞掉,连续 8 次写入失败后实例会自我禁用,而不是每次调用都重试一次注定失败的写入。通过
LogException记录的异常会绕过MinimumLevel——把唯一能解释崩溃原因的那条记录过滤掉,绝不是该设置的本意。 - 实现
IDisposable——在关闭时释放它以刷写并退订。
-
NullLogger—— 把每个日志调用都变成静默的无操作:// Mute everything at runtime — takes effect immediately, static helpers included: Logger.MinimumLevel = LogLevel.Off; // Or replace the backend with a silent no-op (picked up by new resolves): ServiceLocator.Replace<ILogger>(new NullLogger());用它从无头或自动化构建中剥离日志开销、在没有控制台刷屏的情况下测试游戏代码,或在没有日志噪音的情况下做基准测试。日志被静音时,其他所有服务照常工作——框架中没有任何东西依赖日志输出。
编写自己的后端
任何实现 ILogger 的类都可以充当后端。例如,把错误转发到你的崩溃上报 SDK:
using System;
using CommonGameSystem.Core;
public class CrashReportLogger : ILogger
{
public LogLevel MinimumLevel { get; set; } = LogLevel.Warning;
public void Log(LogLevel level, string category, string message)
{
if (level < MinimumLevel) return;
// Forward to your crash-reporting or analytics SDK here.
Console.WriteLine($"[{level}] [{category}] {message}");
}
public void LogException(string category, Exception ex, string message)
{
Console.WriteLine($"[Exception] [{category}] {message}\n{ex}");
}
public LogLevel GetEffectiveLevel(string category) => MinimumLevel;
}
用 ServiceLocator.Replace<ILogger>(new CrashReportLogger()) 注册它。在这次调用之后从服务定位器解析 ILogger 的代码会使用你的后端。一个注意点:静态 Logger 辅助方法在第一次运行时缓存后端,而这发生在框架启动期间——在 Play 会话中较晚做出的替换不会重定向静态辅助方法。它们会在下一次进入 Play、缓存重置时拾取你的后端。
行为与边界情况
-
Release 构建完全丢弃 Debug 调用。
Logger.Debug($"msg = {Expensive()}")的整个调用点会被编译器从 Release 构建中移除,因此Expensive()根本不会执行。免费的优化。 -
与 Unity 类型的命名冲突。 当文件同时有
using CommonGameSystem.Core;和using UnityEngine;时,Logger和ILogger会与UnityEngine.Logger、UnityEngine.ILogger冲突。在文件顶部添加别名:using Logger = CommonGameSystem.Core.Logger; using ILogger = CommonGameSystem.Core.ILogger; -
工作线程需要启动时的预热。 Bootstrap 在启动期间于主线程上进行一次 Logger 调用,这使得后续的工作线程调用(存档 I/O、网络 I/O)是安全的。如果某个工作线程在预热之前记录日志,该消息会回落到普通的
Debug.Log。 -
分类键区分大小写。
"Net"和"net"是两个不同的过滤器。始终使用Logger.Categories.*常量,绝不要用字符串字面量。 -
过滤 API 仅限主线程。 请在主线程上调用
SetCategoryFilter/ClearCategoryFilter。发射方法(Info、Warning、Error等)在任何线程上都是安全的。 -
GetEffectiveLevel要求非 null 分类。 与发射路径不同(后者把null当作Categories.Default),查询 API 在分类为 null 时抛出ArgumentNullException。这能尽早暴露配置错误。 -
会话中途替换后端不会重定向静态辅助方法。 参见上文"编写自己的后端"下的注意点。要立即在运行时静音,请使用
Logger.MinimumLevel = LogLevel.Off。