Loading Logging Configuration

For many applications, it is useful to allow users to configure logging using a configuration file. Usually, this configuration resides in one dedicated file or the main application configuration contains a fixed section for the logging customization.

This page develops the two common arrangements separately. In both, the application opens a known file, obtains the value that represents the root of the logging schema, validates it, and installs the resulting LogConfiguration before ordinary work begins.

Give Logging One Fixed Place

A dedicated log.elcl is a good fit when logging has its own deployment lifecycle. Operations can replace or mount the file independently, and every value in the document belongs to logging. The root of the parsed document is therefore the root of the logging schema.

An application.elcl with a root Log section is usually more convenient when one team owns a single application configuration. Other sections can describe the application, services, and storage, while the startup code always reads Log for logging. The section name is part of the application’s schema just like any other required section.

With ELCL, both solutions work equally well. In the second form, the log configuration can still be held in a separate file and be included from the main file.

Load a Dedicated log.elcl

In a standalone logging file, Format, Queue, Trace Sections, and Writers begin at the document root. The compact fixture below configures a named console route. A real application can resolve log.elcl through its platform-specific configuration directory; after that path has been chosen, the loading sequence stays fixed.

# A dedicated logging configuration. The document root follows the logging schema.
--[ Format ]----------------------------------------------------------------------------------------------------------
Pattern                     : "{level} [{name}] {message}"

--*[ Writers ]*-------------------------------------------------------------------------------------------------------
Type                        : "console"
Levels                      : "information", "warning", "error"
Paths                       : "log"

Parser returns an ELCL document. Because the complete document follows the logging schema, the application uses that document directly without performing a name lookup. It parses the complete document, and installs it in the application-owned manager.

/// Load logging from a dedicated `log.elcl` file.
///
/// The whole document follows the logging schema. Validate the document root, parse the complete configuration, and
/// install it before the application starts its ordinary work.
/// @notest{Compiled and executed by the logging topics demo.}
void loadStandaloneLogConfiguration() {
    const auto path = el::Path{"demos/log/LoggingTopics/data/log.elcl"_el};
    const auto document = el::conf::Parser{}.parseFileOrThrow(path);

    // The log schema is automatically validated by the parser.
    auto configuration = el::LogConfigurationParser{el::application().terminal()}.parse(document);
    el::application().log().setConfiguration(std::move(configuration));

    const auto log = el::application().log().createStream("log/startup"_el);
    log->info("Loaded logging from log.elcl."_el);
}
INF [log/startup] Loaded logging from log.elcl.

Load Log From application.elcl

When logging is part of the main application configuration, you reserve a subsection for it. The example application schema reserves the root section Log. In your validation schema, you mark this value as NotValidated, so you can validate it separately.

# An application configuration with logging at the fixed root section "Log".
--[ Application ]-----------------------------------------------------------------------------------------------------
Name                        : "Explorer Guild"

--[ Log ]-------------------------------------------------------------------------------------------------------------
----[ . Format ]
Pattern                     : "{level} [{name}] {message}"

---*[ . Writers ]*
Type                        : "console"
Levels                      : "information", "warning", "error"
Paths                       : "application"

After parsing application.elcl, call valueOrThrow() with the selected name log if the log configuration is mandatory. A missing section is then a configuration error. The returned value becomes the logging-schema root for validation and parsing.

/// Load logging from the fixed `Log` section of `application.elcl`.
///
/// The application owns the section name as part of its configuration schema. Select that known section, validate it as
/// a logging configuration, and install the parsed snapshot before ordinary application work begins.
/// @notest{Compiled and executed by the logging topics demo.}
void loadApplicationLogConfiguration() {
    const auto path = el::Path{"demos/log/LoggingTopics/data/application.elcl"_el};
    const auto document = el::conf::Parser{}.parseFileOrThrow(path);
    const auto logSection = document->valueOrThrow("log"_el);

    // You can validate the Log section independently from configuration parsing, in case you like to keep
    // the whole application configuration validation in one place and fail early.
    el::LogConfigurationParser::validationRules()->validate(logSection, el::LogConfigurationParser::version());

    // Calling `parse` will nevertheless validate the configuration again.
    auto configuration = el::LogConfigurationParser{el::application().terminal()}.parse(logSection);
    el::application().log().setConfiguration(std::move(configuration));

    const auto log = el::application().log().createStream("application/startup"_el);
    log->info("Loaded logging from application.elcl."_el);
}
INF [application/startup] Loaded logging from application.elcl.

Validate the Log Configuration Before Parsing

validationRules() exposes the logging schema as reusable ELCL validation rules. Use its validate() function and pass the section value and version number of the logging configuration for verification. For a standalone file, the section is the document itself and if you embed it, use the value of the subsection.

This explicit step is particularly useful when startup validates every application section before it creates runtime objects. Logging errors then appear during the same preflight phase as errors in service or storage settings, before any writer opens a file or starts network delivery. The rules check the logging structure, value types, and documented bounds. The following parse still performs conversions and creates the configured writers, so errors that depend on a specific writer or an application resource can still arise there.

Calling the rules separately is optional for a smaller loader. parse() validates the supplied value itself before building the snapshot. Explicit validation does not make parsing safer a second time; it gives a larger application a clean phase boundary and a way to combine logging validation with the rest of its configuration checks.

Construct and Install the Complete Snapshot

LogConfigurationParser turns the validated value into one complete configuration. Supply the application’s terminal when the schema permits a console writer. That writer needs the terminal for styled, width-aware output. Supplying it keeps console, file, last-error, and syslog choices available to the configuration without changing the loading sequence.

Install the returned object with setConfiguration(). The manager replaces its writers, routes, line format, limits, and trace sections together, so accepted messages never observe a partially applied configuration.

If initialization can produce important messages before the configuration is loaded, pause the manager at the beginning of initialize() and resume it immediately after installation succeeds. The retained startup entries then pass through the intended routes. Configuring Logging in Code develops this lifecycle pattern in detail.

Use the Flexible Demo to Explore Scenarios

The configured_logging executable serves a different purpose from the two application patterns above. It is a scenario runner for this documentation: a command-line path selects a fixture, and an optional --branch lets the same executable exercise a root logging document, a nested logging value, and an invalid configuration. That flexibility keeps parser examples and error rendering easy to test. It is not a template for choosing the logging location in a production application.

The complete scenario implementation is shown below so its special role is visible rather than hidden. The first two runs demonstrate that LogConfigurationParser accepts the same schema at either value. The third run deliberately fails validation and confirms that configuration diagnostics reach the application boundary.

/// Load and install an ELCL logging configuration as part of application startup.
///
/// The configuration can occupy the document root or a branch selected on the command line. Validation and parsing
/// errors intentionally reach `Application::run()`, which presents the complete diagnostic and returns a failure exit
/// code.
void ConfiguredLoggingApp::initialize() {
    info().setApplicationName("Configured Explorer Guild"_el);
    enableTerminal();
}

void ConfiguredLoggingApp::registerCommandLineOptions(const el::OptionsPtr &options) {
    options->setHelpDescription("Load an application logging configuration from an ELCL file."_el);
    options->addOption("configuration"_el)
        .setRequired()
        .setHelpDescription("ELCL file containing the logging configuration."_el);
    options->addOption({"-b"_el, "--branch"_el, "branch"_el})
        .setType(el::OptionType::Text)
        .setHelpDescription("Optional dot-separated branch containing the logging configuration."_el);
}

void ConfiguredLoggingApp::parseCommandLine() {
    Application::parseCommandLine();
    if (optionValues() == nullptr) {
        return;
    }

    // Parse the application configuration file and select the requested branch.
    const auto path = el::Path::fromNativeOrThrow(optionValues()->getText("configuration"_el));
    const auto document = el::conf::Parser{}.parseFileOrThrow(path);
    auto branch = el::conf::ValuePtr{document};
    const auto branchName = optionValues()->getText("branch"_el);
    if (!branchName.isEmpty()) {
        branch = document->valueOrThrow(branchName);
    }

    // Pre-validation is useful when these rules are composed into broader application validation.
    el::LogConfigurationParser::validationRules()->validate(branch, 0);

    // A terminal is supplied because the selected configuration may create console writers.
    auto configuration = el::LogConfigurationParser{terminal()}.parse(branch);
    log().setConfiguration(std::move(configuration));
}

auto ConfiguredLoggingApp::main() -> el::ExitCode {
    const auto log = this->log().createStream("guild/configured"_el, el::LogTraceSection{"route-search"_el});
    if (log->traceEnabled()) {
        log->trace("Candidate route: Turku → Jääjärvi → Majakka"_el);
    }
    log->info("The explorer guild loaded its logging configuration."_el);
    log->warn("One route marker still needs confirmation."_el);
    return el::ExitCode::success();
}

$ log/configured_logging demos/log/ConfiguredLogging/data/root.elcl

INF [guild/configured] The explorer guild loaded its logging configuration.
WRN [guild/configured] One route marker still needs confirmation.

$ log/configured_logging demos/log/ConfiguredLogging/data/nested.elcl --branch application.logging

TRC configured: Candidate route: Turku → Jääjärvi → Majakka
INF configured: The explorer guild loaded its logging configuration.
WRN configured: One route marker still needs confirmation.

$ log/configured_logging demos/log/ConfiguredLogging/data/invalid.elcl

  Validating the Configuration Failed

  The value must be at least 1

Error Source:
  Path:     /var/folders/f2/rq38shx1797_b7m0vy142phw0000gn/T/erbsland-demo-doc-7r59jojt/fi
            xture-0.elcl
  Line:     3
  Column:   1
  Position: 159

Configuration Error Details:
  category:  Validation
  name path: queue.maximum_entries

Let Configuration Errors Reach the Application Boundary

The invalid scenario is as important as the successful ones. No startup method catches ConfError merely to replace it with a shorter message. The exception reaches Application::run(), where the standard application boundary renders the reason, source path, line and column, and logging-specific name path. It then returns a nonzero exit status to the shell or service manager.

Catch a configuration error inside startup only when the application can genuinely recover—for example, when an explicitly optional file is absent and a documented built-in configuration is acceptable—or when you can add useful context before rethrowing it. Otherwise, allowing the original diagnostic to reach the boundary gives the operator the most precise explanation and keeps error handling consistent with the rest of the application framework.

Once this loading path is in place, Writing an ELCL Logging Configuration explains how to write the logging section itself, from a minimal console setup to production writer routes and queue limits.