1. 项目概述:为什么Shipping模式会“吞掉”你的日志?
刚接触UE4开发的朋友,尤其是从蓝图转向C++或者开始处理线上问题的开发者,几乎都踩过这个坑:在编辑器(Editor)里运行得好好的,各种UE_LOG打印得满屏飞,调试信息一目了然。结果一打包成Shipping版本,准备发给测试或者部署,程序跑起来后一片寂静,关键的运行时状态、错误信息全都没了踪影,仿佛程序在“静默运行”。这感觉就像飞行员在晴空万里的训练场飞得好好的,一进实战夜航,所有仪表盘突然熄灭了,只能靠感觉盲飞。
这个问题直指UE4构建配置的核心之一:Shipping模式。它不是一个简单的“优化开关”,而是一整套为最终发布版本量身定制的严格配置集合。其首要目标是极致的安全性与性能。为了达成这个目标,它会默认剥离所有被认为“非必要”的调试和开发辅助功能,其中就包括了向控制台(Console)或日志文件(Log)输出信息的能力。因为从安全角度看,向终端输出字符串信息可能存在信息泄露风险;从性能角度看,频繁的日志I/O操作也会消耗资源。
所以,当你遇到“打包后看不到日志”时,这其实是UE4的预期行为,而非Bug。我们的目标不是改变Shipping模式的初衷,而是在需要的时候,安全、可控地重新打开这个“观察窗”,以便在准生产或特定测试环境下进行诊断。本教程将彻底拆解这个需求,从原理到实操,给出从“一键开关”到“精细调控”的完整解决方案。
2. 核心原理:UE4的日志系统与构建配置
要解决问题,得先明白日志是怎么没的。UE4的日志系统与它的宏定义和构建配置深度绑定。
2.1 日志宏与条件编译
你在代码中常用的UE_LOG(LogTemp, Warning, TEXT(“Hello”)),其背后是一系列宏。这些宏的核心部分被#if/#endif包裹,其编译条件取决于一系列预处理器定义。
例如,UE_LOG宏的内部会检查NO_LOGGING这个宏是否被定义。如果定义了,那么整个日志调用在编译期就会被移除,生成的二进制文件中根本不会有对应的代码,自然也就没有日志输出。
2.2 构建配置的“套餐”
UE4的构建配置(Build Configuration)不只是“Debug”和“Release”那么简单。在Visual Studio或你的构建脚本里,你常看到的是:
- DebugGame: 带有完整调试符号和日志的开发配置,用于项目调试。
- Development: 默认的“开发”配置,保留了日志和部分断言(Assert),优化级别较低。
- Shipping: “发货”配置,追求最小体积和最高运行效率,默认禁用日志、断言、分析器、控制台命令等。
- Test: 类似于Development,但可能启用更多测试用的检查。
当你选择“Shipping”配置进行打包时,Unreal Build Tool (UBT) 会自动为你定义一系列宏,比如UE_BUILD_SHIPPING、NDEBUG,并且最关键的是,它会定义NO_LOGGING。这就是日志消失的“总开关”。
2.3 控制台与日志输出的区别
这里需要厘清一个概念:控制台输出和日志文件输出是两套相关但独立的系统。
- 控制台输出: 指程序运行时,在终端窗口(Windows CMD, PowerShell, 或Linux Terminal)里打印出来的文本。对于Windows平台的可执行文件(.exe),默认的Shipping构建是不创建控制台窗口的(子系统设置为Windows)。即使有日志,你也看不到。
- 日志文件输出: 指日志内容被写入到磁盘文件,通常位于
Saved/Logs目录下,文件名为[ProjectName].log。在Shipping模式下,默认的日志记录器可能被禁用或级别设得很高。
我们的教程目标,是同时解决这两个问题:让Shipping版本的程序能创建一个控制台窗口用于实时查看输出,并且将日志内容有效地记录到文件中。
3. 方案选型:四种主流方法的利弊分析
根据不同的使用场景和需求,有从简单到复杂的多种方法。没有最好的,只有最适合的。
| 方法 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 1. 修改项目构建参数 | 在UBT编译参数中移除NO_LOGGING定义。 | 一劳永逸,项目级生效,无需修改引擎。 | 修改了Shipping模式的“纯净性”,需团队统一。 | 团队内部测试包,需要完整日志。 |
| 2. 使用启动命令行参数 | 通过-log等命令行参数在运行时启用日志。 | 无需重新编译,灵活可控。 | 需要记住参数,且无法开启所有调试功能。 | 快速临时调试已打包的程序。 |
| 3. 创建自定义构建配置 | 复制Shipping配置,创建如“ShippingWithLogs”的新配置。 | 灵活,可定制化程度高,不影响标准配置。 | 配置稍复杂,需要维护额外的构建配置。 | 频繁需要带日志的发布测试版本。 |
| 4. 修改引擎源码(不推荐) | 直接修改引擎中关于Shipping模式的默认设置。 | 全局生效,最彻底。 | 破坏引擎原始状态,升级维护灾难,绝对不推荐。 | 基本不适用,应避免。 |
对于绝大多数项目和开发者,方法1(修改项目参数)和方法3(自定义配置)是最实用和推荐的选择。方法2作为辅助调试手段。接下来,我们重点讲解方法1和3的详细操作步骤。
4. 实操详解:为Shipping模式开启日志输出
我们假设你的项目名为MyProject。以下操作均在项目根目录下进行。
4.1 方法一:修改项目构建配置文件(推荐)
这是最直接、最常用的方法。UE4项目的构建规则主要由.Target.cs文件控制。
步骤1:定位并编辑目标文件
- 打开你的项目文件夹,进入
Source目录。 - 找到
MyProject.Target.cs(游戏客户端)和MyProjectEditor.Target.cs(编辑器)。我们主要修改前者。 - 用文本编辑器(如VSCode、Notepad++)或Rider/Visual Studio打开
MyProject.Target.cs。
步骤2:修改构建规则在这个文件中,你会看到一个继承自TargetRules的类。找到CreateTargetRules方法内部,特别是针对ExtraModuleNames添加后的部分。我们需要修改的是if (Target.Configuration != UnrealTargetConfiguration.Development)之后的逻辑。
原始的Shipping配置部分可能不明显,但我们可以通过覆盖GlobalDefinitions来移除NO_LOGGING。更规范的做法是在构造函数中根据配置进行设置。
找到你的MyProjectTarget类的构造函数,通常如下所示:
public MyProjectTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; DefaultBuildSettings = BuildSettingsVersion.V2; ExtraModuleNames.AddRange( new string[] { “MyProject” } ); // 在这里添加自定义逻辑 if (Target.Configuration == UnrealTargetConfiguration.Shipping) { // 关键步骤:移除NO_LOGGING定义,允许日志 GlobalDefinitions.Remove(“NO_LOGGING”); // 同时,为了更完整的日志,建议也移除定义,但这不是必须的 // GlobalDefinitions.Remove(“UE_BUILD_SHIPPING”); } }注意:直接移除
UE_BUILD_SHIPPING定义可能会导致某些特定于Shipping的代码路径被改变,可能引入不确定性。通常只移除NO_LOGGING就足够了。有些更严格的检查可能依赖于UE_BUILD_SHIPPING,请谨慎操作。
步骤3:启用控制台窗口(仅Windows)只有日志还不够,我们需要一个窗口来看到它们。在同一个构造函数中,添加以下代码:
if (Target.Configuration == UnrealTargetConfiguration.Shipping) { GlobalDefinitions.Remove(“NO_LOGGING”); // 对于Windows平台,将子系统改为控制台,这样会自动弹出CMD窗口 if (Target.Platform == UnrealTargetPlatform.Win64) { bUseConsole = true; // 启用控制台 // 或者,更精确地控制子系统(二者效果类似,bUseConsole更直接) // WindowsPlatform.bUseConsole = true; } }bUseConsole = true这个设置会告诉链接器,将可执行文件的子系统设置为CONSOLE而非WINDOWS。这样运行.exe时就会自动打开一个控制台窗口。
步骤4:重新生成项目文件并编译
- 保存
MyProject.Target.cs文件。 - 右键点击你的
.uproject文件,选择 “Generate Visual Studio project files”。或者通过命令行在项目根目录运行UnrealBuildTool -projectfiles -project=“MyProject.uproject” -game -engine。 - 用Visual Studio或你常用的IDE打开生成的
.sln解决方案文件。 - 将解决方案配置设置为Shipping和Win64。
- 重新编译整个项目。
编译完成后,打包出来的游戏.exe文件在运行时就会弹出一个控制台窗口,所有UE_LOG的输出都会打印在里面,同时也会写入到Saved/Logs/MyProject.log文件中。
4.2 方法二:使用命令行参数快速启用
如果你已经有一个现成的Shipping包,不想重新编译,可以尝试命令行参数。但这依赖于项目运行时是否支持。
创建一个快捷方式指向你的
MyProject.exe。右键快捷方式 -> 属性,在“目标”一栏的末尾添加参数。
添加以下参数(用空格分隔):
“C:\Path\To\MyProject.exe” -log -windowed-log: 这是最关键的命令,它尝试告诉引擎启用日志系统。但请注意,如果引擎在编译时彻底移除了日志代码(即NO_LOGGING被定义),这个参数可能无效。-windowed: 以窗口化模式运行,方便观察。- 你还可以尝试
-verbose、-debug等参数,但它们在纯Shipping模式下通常无效。
运行这个快捷方式。如果幸运的话,你可能会在游戏窗口外看到一个控制台窗口,或者能在
Saved/Logs下找到日志文件。
这个方法成功率不高,但它是最快的验证手段。如果无效,说明日志代码已被彻底剥离,必须使用方法一重新编译。
4.3 方法三:创建自定义的构建配置
这是最专业、对项目工作流影响最小的方法。你创建一个类似Shipping但带日志的配置。
步骤1:复制并修改构建配置文件
- 在
Source目录下,复制MyProject.Target.cs为MyProjectShippingWithLogs.Target.cs。 - 打开新文件,将类名
MyProjectTarget改为MyProjectShippingWithLogsTarget。 - 在构造函数中,直接设置你想要的规则,无需检查
Target.Configuration:public MyProjectShippingWithLogsTarget(TargetInfo Target) : base(Target) { Type = TargetType.Game; DefaultBuildSettings = BuildSettingsVersion.V2; ExtraModuleNames.AddRange( new string[] { “MyProject” } ); // 始终应用我们自定义的规则 GlobalDefinitions.Remove(“NO_LOGGING”); if (Target.Platform == UnrealTargetPlatform.Win64) { bUseConsole = true; } // 你可以选择性地关闭一些极端优化,以换取更好的可调试性 bUsePCHFiles = true; bUseUnityBuild = false; // 关闭Unity Build,编译更慢但链接错误更清晰 }
步骤2:注册新的Target到UBT仅仅创建文件不够,需要让UBT知道它。在Source目录下的MyProject.Build.cs或MyProject.Target.cs同级,创建一个Program.cs并非必要。更简单的方式是:确保你的.uproject文件能关联到这个新Target。实际上,当你重新生成项目文件时,UBT会自动扫描Source目录下所有*.Target.cs文件。
步骤3:生成项目文件并选择新配置
- 重新生成Visual Studio项目文件。
- 在Visual Studio的解决方案配置下拉列表中,你除了看到
DebugGame|Win64、Development|Win64、Shipping|Win64,现在应该还能看到ShippingWithLogs|Win64。 - 选择这个新配置进行编译和打包。
这个方法的优势在于,你的标准Shipping配置保持原样,完全纯净。当你需要带日志的包时,就使用ShippingWithLogs配置。它完美地区分了“最终发布包”和“内部测试包”。
5. 高级配置与日志管理
开启了日志输出后,你可能会被海量的日志淹没。我们需要更精细地控制它。
5.1 控制日志详细程度(Verbosity)
UE_LOG的第二个参数就是日志级别(Verbosity):
Fatal: 致命错误,打印后崩溃。Error: 错误。Warning: 警告。Display: 默认的显示信息。Log: 一般日志。Verbose: 详细日志。VeryVerbose: 非常详细的日志。
在Shipping模式下,即使开启了日志,默认的日志级别也可能只显示Display及以上的信息。你可以在代码中通过LogTemp分类来控制,但更好的方法是通过命令行参数或配置文件。
使用-LogCmds=”Verbose”参数: 在游戏启动命令后添加此参数,可以将所有日志类别的默认级别设置为Verbose。你也可以指定特定类别,如-LogCmds=”LogGameMode Verbose, LogActor Warning”。
5.2 将日志输出到特定文件
默认日志会写在Saved/Logs/[ProjectName].log。你可以通过命令行参数改变:
-ABSLOG=C:\MyLogs\output.log: 设置绝对路径的日志文件。-LOG: 等同于-log,启用日志。
5.3 在运行时动态控制日志(仅限开发功能)
在非纯Shipping模式下(即我们修改后的配置),你还可以在游戏中通过控制台命令(需要按~键呼出控制台)来动态调整:
Log List: 列出所有日志类别及其当前级别。Log LogCategory Name: 设置某个类别的日志级别,例如Log LogTemp Verbose。
6. 常见问题与排查技巧实录
即使按照教程操作,你也可能会遇到一些“坑”。以下是我在实际项目中总结的常见问题及解决方法。
问题1:修改了.Target.cs文件,但重新编译后依然没有控制台窗口。
- 排查: 首先确认你是否正确选择了Shipping配置进行编译。在Visual Studio的工具栏上仔细检查。然后检查编译输出,看是否有错误或警告。
- 解决: 确保
bUseConsole = true;这行代码被正确执行。一个常见的错误是把它放在了错误的条件判断里。最稳妥的方式是像方法三那样,在新Target的构造函数中直接设置,不依赖条件判断。
问题2:控制台窗口出现了,但是里面一片空白,没有任何UE_LOG输出。
- 排查: 这说明控制台子系统设置成功了,但日志系统依然被禁用。问题几乎肯定出在
NO_LOGGING宏没有被成功移除。 - 解决:
- 检查你的
GlobalDefinitions.Remove(“NO_LOGGING”);语句是否拼写正确,是否在正确的构造函数中。 - 在Visual Studio中,打开项目属性(右键项目 -> 属性),转到C/C++ -> 预处理器 -> 预处理器定义,查看Shipping | Win64配置下是否还有
NO_LOGGING。如果有,说明你的修改未被UBT采纳,请检查.Target.cs文件语法,并确保重新生成了项目文件。 - 尝试在
GlobalDefinitions中添加LOG_VERBOSE=1等定义来强制开启,但核心还是移除NO_LOGGING。
- 检查你的
问题3:日志能输出到控制台,但没有生成Saved/Logs/MyProject.log文件。
- 排查: 这可能是因为程序没有对项目目录的写入权限(尤其是当程序安装在
Program Files这类受保护目录时),或者日志文件路径被重定向了。 - 解决:
- 以管理员身份运行程序试试。
- 使用
-ABSLOG=参数指定一个你有绝对写入权限的路径(如桌面),看是否能生成文件。 - 在代码中,你可以通过
FPlatformMisc::GetEnvironmentVariable(TEXT(“APPDATA”))获取用户目录,然后将日志指向%APPDATA%/[ProjectName]/Logs下,这是一个更可靠的目录。
问题4:打包(Pak)后,日志文件写在哪里?
- 排查: 当游戏被打包成
.pak文件后,Saved目录通常位于用户的“文档”或“AppData”文件夹下,而不是游戏安装目录。例如,在Windows上,路径通常是%USERPROFILE%\Documents\MyProject\Saved\Logs。 - 解决: 在游戏运行时,通过输出一行日志,其内容包含一个特定路径(如
UE_LOG(LogTemp, Display, TEXT(“Log Path: %s”), *FPaths::ProjectLogDir());),这样你就能在控制台看到确切的日志文件路径了。
问题5:开启日志后,游戏性能明显下降或体积增大。
- 解决: 这是必然的。日志I/O操作、字符串格式化都会消耗CPU时间。这就是为什么Shipping默认关闭它。因此,带日志的Shipping包仅用于内部测试、性能剖析或问题诊断,绝不可作为最终版本发布。在完成调试后,务必使用纯净的Shipping配置重新打包。
最后分享一个我个人的习惯:在团队协作中,我会严格使用方法三(自定义配置)。我会在项目的README或内部Wiki中明确写明:
Development: 日常开发调试用。Shipping: 纯净发布包,用于提交平台或最终用户。ShippingWithLogs(或Test): 质量保证(QA)测试、性能测试、自动化测试用包。任何提交给测试团队的包,都必须是这个配置,以便他们能捕获并上报完整的日志。
这样,每个配置的用途泾渭分明,既能保证最终产品的质量,又不牺牲开发调试的便利性。记住,工具是为人服务的,找到最适合你团队工作流的那把“扳手”,才能高效地解决问题。