Logging

Purpose

Logging records operational information in named destinations with explicit configuration, filtering and sink lifetimes. Ordinary logs support diagnosis; required failure propagation and bounded emergency reporting remain independent.

Interface

Types below are in CE::Logging unless another namespace is shown.

Types and operations

API and purpose

Logger, Log
Select a named category and initialize, emit, filter, flush, close or reopen it. Runtime settings and ownership.

Config
Choose destinations, rotation, levels and queue overflow behavior before producers start. Configuration and ownership.

FileEventHandlers
Supply scoped file-open/close callbacks while retaining borrowed targets through final closure. Callback and reentry boundaries.

QueueStats
Observe queued/discarded work in the shared logging pool. Queue observations.

Levels and policy enums

Type

Choices and details

Level

trace, debug, info, warn, err, critical, off for runtime logger/sink thresholds. Filtering.

Profile

Developer, Support, Release presets through Config::for_logger(). Runtime defaults.

OverflowPolicy

Block waits for queue capacity; DiscardNew rejects/counts new work without evicting accepted items. Queue admission.

Compile::LogLevel

Compile-time severity bits used by logging policy; runtime thresholds cannot restore stripped calls. Compile policy; declarations.

Usage

Application composition owns logging lifetime and configures each used category before producers begin. Writers use a named Logging::Logger; an explicitly owned Logging::Log is another option when the caller needs a separate facade/backend lifetime.

        ---
config:
  theme: base
  themeVariables:
    fontFamily: system-ui, sans-serif
    fontSize: 14px
  flowchart:
    htmlLabels: true
    curve: linear
    nodeSpacing: 22
    rankSpacing: 38
    padding: 12
---
flowchart TB
    accTitle: Logging · application configuration and writers
    accDescr: Local names omit Logging::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices.
    k0(["Application<br/>composition"]):::app
    k1["<b>ProcessLifetime</b><br/><br/>ProcessLifetime()"]:::focal
    k2["<b>Config</b><br/><br/>for_logger(name, Profile): Config"]:::focal
    k3["<b>Log</b><br/><br/>Log(FileEventHandlers, Config)<br/><br/>info(format, arguments...)<br/><br/>close(timeout)"]:::focal
    k4["Profile"]:::focal
    k5["Level"]:::focal
    k6["OverflowPolicy"]:::focal
    k7["FileEventHandlers"]:::focal
    k8(["Application<br/>producer"]):::app
    k9["<b>Logger</b><br/><br/>initialize(FileEventHandlers, Config): Log&lt;name&gt;<br/><br/>info(fmt, args...)<br/><br/>warn(fmt, args...)"]:::focal
    k10(["Application<br/>report"]):::app
    k11["<b>LogHandle</b><br/><br/>shared_queue_stats(): QueueStats<br/><br/>backend_stats(): BackendStats<br/><br/>report_diagnostics()"]:::focal
    k12["QueueStats"]:::neighbor
    k13(["Application<br/>cleanup / quiesce()"]):::app
    k14["BackendStats"]:::neighbor
    k0 --> k1
    k0 --> k2
    k0 --> k3
    k2 --> k4
    k2 --> k5
    k2 --> k6
    k3 --> k7
    k8 --> k9
    k9 --> k3
    k10 --> k11
    k11 --> k12
    k13 --> k3
    k0 --> k9
    k3 --> k2
    k9 --> k7
    k9 --> k2
    k11 --> k14
    classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px
    classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px
    classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3
    classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px
    linkStyle default stroke:#7e8fa7,stroke-width:1.2px

    

Local names omit Logging:: where unambiguous. Explore this diagram and its other views →

The engine dependency view follows subsystem writers into the public logging interfaces.

Configure, write and finish

Boundary

Application action

Result or limit

Process setup

Keep Logging::ProcessLifetime alive around all producers and diagnostic readers. The supplied Startup already owns a guard.

Nested guards share the lifetime; the last guard closes live logs and joins the private worker. Shutdown is terminal.

Before first category access

Build Logging::Config with for_logger() and call Logger<name>::initialize(handlers, config).

Configuration is per category. Repeated explicit initialization rejects, including after lazy creation.

Ordinary writes

Use CE_LOG_* or Logger<name>::write_lazy() when diagnostic argument preparation is costly or can throw.

Compile policy and runtime gates run before guarded argument preparation; direct severity-call arguments are evaluated first.

Investigation

Read shared_queue_stats() and backend_stats() on the existing log.

Queue loss spans categories; degradation/failure counters identify the individual log. Neither query proves output completion.

Shutdown/recovery

Stop producers, complete close(), then reopen only if recovery is needed and the process lifetime remains active.

A timed-out close leaves Closing intact. flush() queues a request; it does not acknowledge file completion or durability.

Follow Enable useful output for a complete category configuration. Choose Block when waiting for shared queue capacity is acceptable, or DiscardNew when counting lost new records is preferable; the latter can also discard a flush request.

Uses

Consumer

Dependency

Relationship

Logging::Logger<name>

Logging::Log<name>

Provides lazy named access to centrally registered state keyed by category text across libraries.

Logging::Log<name>

Logging::LogHandle

Derives the public lifecycle/filtering/observation operations; it is not a State::Singleton facade.

Logging::LogHandle

Logging::Config and Logging::FileEventHandlers

Owns configured backend state and callbacks through final closure. Callback captures protect borrowed targets.

Logging::Config

Logging::Profile, Logging::Level and Logging::OverflowPolicy

Separates initial runtime thresholds and queue admission from the target’s compile mask.

Guarded emission and backend failure paths

Bounded Diagnostics failure reporting

Reports required failure context independently of ordinary log filtering or sink health.

The compiled backend owns its queue, worker and sink graph. Applications do not replace native sinks or keep native logger owners through public API handles.

Used by

Consumer

Named destination

What to look for

Runtime::GameRuntime, Workers::Pool, dispatchers and event buses

Logging::Categories::engine → engine.log

Session timing/lifecycle and explicit work/delivery summaries. Runtime, Workers, Dispatch, Events.

Platform::GLFW::DisplaySystem and native input

Logging::Categories::platform → os-platform.log

Window/context/input capability and focus observations. Display, Input.

Rendering::OpenGL::Renderer and graphics resources

Logging::Categories::rendering → rendering.log

Renderer, shader and native resource observations. Rendering, Resources.

Assets::Loading::Loader and caches

Logging::Categories::assets → assets.log

Preparation, upload/publication and reload outcomes. Assets.

Memory::Manager

Logging::Categories::memory → memory.log

Explicit preallocation and copied bookkeeping reports. Memory.

These files are separate destinations, not an aggregate mirror. Configuring engine alone does not configure the other categories. The category guide owns default paths and custom names.

Ordinary output and required failure context

Ordinary records can be compiled out, filtered, delayed, discarded or suppressed by degraded sinks. Preserve the owning exception/future independently. Native callbacks, final deleters and noexcept cleanup use the bounded emergency path, outside ordinary logger acquisition. Keep file callbacks free of logger lifecycle reentry and waits for work that needs the backend.

Contracts

Initialize explicitly before lazy access/writers when configuration matters. Severity filters cannot restore code removed by compilation policy. Queue admission, rotation and callback failures follow the configured contract; logging is not a substitute for the operation’s exception or future.

Sink callbacks must respect backend reentry guards and their documented borrowed arguments. Do not emit ordinary logging while holding sensitive subsystem locks or from native/noexcept failure paths that require bounded emergency reporting.

Lifecycle

Configure the used categories before starting producers, run those producers, stop/settle them, then flush and close logging. Close/reopen serializes state transitions; a timed close can report an incomplete operation rather than pretending all queued work has completed. Keep callback targets alive until the backend has finished with them. Startup composition owns its logging lifetime when using the supplied startup path.

Execution

Writers may enqueue from supported producer threads. Sink callbacks run according to synchronous/asynchronous configuration, not necessarily on simulation. Compile/filter checks should precede costly argument construction where the selected frontend supports that. Named logger registry state is separate from game/thread ownership.

Integration

The application can emit through a built-in named category after startup:

CE::Logging::Logger<CE::Logging::Categories::engine>::info("Game initialized");

Select the declared category spelling from the logging umbrella and configure it before producers. For a known file/console outcome, follow Enable useful output before this first write. If it is absent, use the missing-records checklist. The detailed guide also covers custom names, handlers, profiles and controlled shutdown.

Limits and further reading

Logging does not promise recovery from a native failure or durable completion merely because a write returned. Read configuration, emission and filtering, failure reporting, logging acceptance and the logger interface.