Logging
Everything logged by the engine, the editor, and your scripts goes through one logger. It writes to three places at once:
- the console attached to the process,
Lumina.log, next to the executable (rotated per run, five kept),- the editor’s Output Log panel (Ctrl+J).
Logging is asynchronous: the calling thread formats the message and hands it off, and a background thread does the writing. It’s cheap enough to call from gameplay code, and it’s safe from any thread.
Include Log/Log.h (already in the precompiled header for most modules) and use
the macros. The format string is std::format syntax.
LOG_INFO("Loaded {} assets in {:.2f} ms", Assets.size(), ElapsedMs);LOG_WARN("Mesh '{}' has no material slot {}", Mesh->GetName(), SlotIndex);LOG_ERROR("Failed to open {}", Path);Arguments are checked at compile time, so a mismatched placeholder is a build error, not a garbled line at runtime.
| Macro | Severity | Survives Shipping |
|---|---|---|
LOG_CRITICAL | critical | yes |
LOG_ERROR | error | yes |
LOG_WARN | warning | yes |
LOG_DISPLAY | info | yes |
LOG_INFO | info | no |
LOG_DEBUG | debug | no |
LOG_TRACE | trace | no |
LOG_TRACE / LOG_DEBUG / LOG_INFO compile to nothing in Shipping builds
(they’re gated on the VerboseLogging build feature), so their arguments cost
nothing there. Use them freely for everyday status.
LOG_DISPLAY is the one info-severity macro that survives Shipping. Reserve it
for one-shot boot and system milestones you’d want in a packaged game’s log,
like mounted PAKs, “loading startup map X”, or a linked plugin module. It is not a
general info channel; frequent use drowns out the signal.
Logging your own types
Section titled “Logging your own types”A single argument is logged verbatim, so braces in it are harmless:
LOG_ERROR(ErrorText); // ErrorText is an FString / const char* / FStringViewFString, FStringView, FName, FGuid, and FTransform already have a
std::formatter, so they can be passed straight in. Math types don’t, so use
Math::ToString:
LOG_INFO("Spawned {} at {}", Prefab->GetName(), Math::ToString(Location));For your own types, specialize std::formatter the same way the engine ones do.
Flushing
Section titled “Flushing”Log writes reach the OS at the end of every batch, so a crash won’t lose them.
If you need a hard guarantee before doing something dangerous, call
Logging::Flush(), which blocks until everything logged so far has landed. The
crash handler, the assert handler, and the hang watchdog already do this.
Use the static Debug class. It’s available anywhere in script code.
public override void OnReady(){ Debug.Log($"{Entity} ready with {Health} hp"); Debug.LogWarning("No spawn point assigned, falling back to origin"); Debug.LogError("Target prefab failed to load");}| Call | Severity |
|---|---|
Debug.Log | info |
Debug.LogWarning | warning |
Debug.LogError | error |
Use C# string interpolation ($"..."), the message is a plain string by the
time it reaches the engine.
Script messages are tagged [C#] in the log, so they’re easy to pick out:
[2026-07-28 14:51:22.147] [info ] [9128] [C#] Loaded C# scripts [generation 1]Unhandled exceptions in script callbacks are logged as errors automatically, with the stack trace, so you don’t need to wrap callbacks in try/catch just to see them.
Reading the output
Section titled “Reading the output”The file log carries the full detail; the console is the short form.
[2026-07-28 14:51:18.682] [info ] [9128] Job system online: 30 workers └─ date + time └─ severity └─ thread idThe thread id matters more than it looks: the engine runs gameplay, rendering, and job workers concurrently, so two interleaved lines are often two threads, not one confused system.
In the editor, the Output Log panel filters by severity and text, and doubles as the console-variable command line.