Formatting Log Lines
A useful log line gives its reader enough context to understand an event without making the event itself disappear in
decoration.
Interactive tools may need little more than the message, while a long-running service usually benefits from a timestamp,
severity, and stream name on every entry.
This page shows how to describe both styles with LogLineFormat, and how to
keep long names and messages readable without confusing presentation limits with the manager’s memory limits.
Setting the Default Log Line Format
A line format is a small value object that belongs to a complete
LogConfiguration.
Construct it locally, change the settings that matter to your application, and pass it to
LogConfiguration::setLineFormat.
This selects the default for routes without a custom format.
Writers use this format unless you give a destination its own layout, as described later on this page.
The following demo uses a standalone manager so the complete sequence is visible.
The console writer is not part of line formatting; it is added because a manager needs a destination before the result
can be seen.
An Application follows the same configuration sequence with its
application-owned manager.
log/LoggingTopics/LineFormats.cpp
/// Install one line format as part of a complete logging configuration.
///
/// Create `LogLineFormat` as a value, customize it, and move it into `LogConfiguration`. The manager takes another
/// complete configuration value and uses this default for routes without their own line-format override.
void lineFormats() {
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level} [{name}] {message}"_el);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
const auto log = manager->createStream("guild/routes"_el);
log->info("The route catalog is ready."_el);
manager->shutdown();
}
Output
INF [guild/routes] The route catalog is ready.
Line Patterns and Field Placeholders
The pattern determines which pieces of an entry appear and how they are arranged.
A new LogLineFormat starts with {time} {level} - {message}.
Call LogLineFormat::setPattern when another layout better suits
your readers.
The pattern is not a general-purpose formatting language; it deliberately has four semantic placeholders:
Placeholder |
Inserted value |
|---|---|
|
The entry timestamp, rendered in the selected time zone. |
|
The entry severity, rendered in the selected level format. |
|
The hierarchical stream path, rendered in the selected name format. It is empty for the root stream. |
|
The prepared message text, including any rendering-time truncation. |
Literal text may surround the placeholders, and a placeholder may be omitted or repeated.
Write {{ or }} when the result needs a literal opening or closing brace.
The setter validates the pattern immediately; an empty pattern, an unknown placeholder, or an unmatched brace raises
ParameterFailure while the configuration is being assembled.
The demo compares three patterns: message-only output for a compact command, level and stream context for a larger application, and a labelled format containing literal braces.
log/LoggingTopics/LinePatterns.cpp
/// Arrange semantic log fields with a validated placeholder pattern.
///
/// `setPattern()` accepts time, level, name, and message placeholders. A compact command can show only the message,
/// while larger applications often add level and name. Doubled braces produce literal braces in the output.
void linePatterns() {
const auto showPattern = [](const el::String &label, el::String pattern) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern(std::move(pattern));
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/routes"_el)->info("Selected the ridge route."_el);
manager->shutdown();
};
showPattern("Message only"_el, "{message}"_el);
showPattern("Level and name"_el, "{level} [{name}] {message}"_el);
showPattern("Literal braces"_el, "{{guild}} {level}: {message}"_el);
}
Output
Message only:
Selected the ridge route.
Level and name:
INF [guild/routes] Selected the ridge route.
Literal braces:
{guild} INF: Selected the ridge route.
UTC and Local Timestamp Formats
Log entries retain their timestamp in UTC, which makes entries from different machines and time zones straightforward to
compare.
Presentation is a separate choice.
LogLineFormat::setTimestampZone accepts either
LogTimestampZone::Utc or LogTimestampZone::Local and affects only text inserted for {time}.
UTC is the default and ends its ISO timestamp with Z.
Local rendering converts the same kind of retained instant through the process-local time zone and includes the
applicable numeric offset.
Local output is friendly in tools operated from one location; UTC is generally easier to correlate across services.
On a machine configured for UTC, both choices naturally display the same clock time.
log/LoggingTopics/LineTimestampZones.cpp
/// Render retained UTC timestamps either in UTC or in the process-local time zone.
///
/// `setTimestampZone()` changes presentation only; the entry keeps its original UTC instant. ISO output includes `Z`
/// for UTC or the applicable numeric offset for local time.
void lineTimestampZones() {
const auto showZone = [](const el::String &label, const el::LogTimestampZone zone) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{time} - {message}"_el).setTimestampZone(zone);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("Expedition clock synchronized."_el);
manager->shutdown();
};
showZone("UTC rendering"_el, el::LogTimestampZone::Utc);
showZone("Local rendering"_el, el::LogTimestampZone::Local);
}
Output
UTC rendering:
2026-11-06 10:32:15Z - Expedition clock synchronized.
Local rendering:
2026-11-06 11:32:15+01:00 - Expedition clock synchronized.
Severity Name Formats
A severity can be terse in a dense terminal view or deliberately conspicuous in a report.
LogLineFormat::setLevelFormat controls the spelling inserted
for {level}: ThreeLetterUpper produces values such as WRN and is the default, ShortLower produces
wrn, FullLower produces warning, and FullUpper produces WARNING.
Only the rendered spelling changes. The entry remains a warning in all four cases, so writer routing and the console writer’s semantic styling continue to use the original severity. This makes it safe to choose a format for readability without changing which entries reach a destination.
log/LoggingTopics/LineLevelFormats.cpp
/// Choose a compact or descriptive spelling for severity levels.
///
/// `setLevelFormat()` affects only the text inserted by `{level}`. The entry keeps the same warning level, so routing
/// and console styling remain unchanged across all four representations.
void lineLevelFormats() {
const auto showLevel = [](const el::String &label, const el::LogLevelFormat levelFormat) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level}: {message}"_el).setLevelFormat(levelFormat);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->warn("One route marker needs repainting."_el);
manager->shutdown();
};
showLevel("Three uppercase letters"_el, el::LogLevelFormat::ThreeLetterUpper);
showLevel("Three lowercase letters"_el, el::LogLevelFormat::ShortLower);
showLevel("Full lowercase name"_el, el::LogLevelFormat::FullLower);
showLevel("Full uppercase name"_el, el::LogLevelFormat::FullUpper);
}
Output
Three uppercase letters:
WRN: One route marker needs repainting.
Three lowercase letters:
wrn: One route marker needs repainting.
Full lowercase name:
warning: One route marker needs repainting.
Full uppercase name:
WARNING: One route marker needs repainting.
Stream Name Formats
Hierarchical stream paths are most valuable when they reveal where an entry originated, but the most useful portion
depends on the surrounding output.
Call LogLineFormat::setNameFormat to select the representation
inserted for {name}.
Full is the default and preserves the complete path.
Leaf keeps only its final segment, which works well when an application has already separated output by subsystem.
HeadAndLeaf combines the first and final segments with /…/; it retains both the broad domain and the specific
producer while discarding intermediate organization.
LeftTruncated preserves as much of the right side as fits within the name limit, where the most specific segments
are usually found.
The following output uses the same stream for every entry, making the information retained by each choice easy to compare.
log/LoggingTopics/LineNameFormats.cpp
/// Select how much of a hierarchical stream path appears in `{name}`.
///
/// `setNameFormat()` can preserve the full path, keep its leaf, combine its first and final segments, or retain the
/// right side of a long path within the default 40-character name limit.
void lineNameFormats() {
const auto showName = [](const el::String &label, const el::LogNameFormat nameFormat) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{name}: {message}"_el).setNameFormat(nameFormat);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/exploration/finland/lapland/northern-lights"_el)->info("Survey opened."_el);
manager->shutdown();
};
showName("Full path"_el, el::LogNameFormat::Full);
showName("Leaf segment"_el, el::LogNameFormat::Leaf);
showName("First and final segments"_el, el::LogNameFormat::HeadAndLeaf);
showName("Left-truncated path"_el, el::LogNameFormat::LeftTruncated);
}
Output
Full path:
guild/exploration/finland/lapland/northern-lights: Survey opened.
Leaf segment:
northern-lights: Survey opened.
First and final segments:
guild/…/northern-lights: Survey opened.
Left-truncated path:
…oration/finland/lapland/northern-lights: Survey opened.
Left-Truncated Stream Name Limits
The LeftTruncated representation becomes especially useful when stream hierarchies can grow with plugins, jobs, or
other runtime data.
LogLineFormat::setNameLimit sets its maximum length in Unicode
code points.
The default is 40 code points.
The formatter keeps the right side and places the configured truncation mark at the beginning, so a reader can see both
that context was removed and which specific producer emitted the entry.
The name limit is consulted only when LogNameFormat::LeftTruncated is selected.
It does not silently shorten Full, Leaf, or HeadAndLeaf names.
A limit of zero disables shortening and therefore shows the complete path even in LeftTruncated mode.
log/LoggingTopics/LineNameLimits.cpp
/// Bound left-truncated stream names by a code-point count.
///
/// `setNameLimit()` is consulted only for `LogNameFormat::LeftTruncated`. Keeping the right side preserves the most
/// specific path segments; a zero limit disables name shortening even when that format is selected.
void lineNameLimits() {
const auto showLimit = [](const el::String &label, const el::CpCount limit) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{name}: {message}"_el)
.setNameFormat(el::LogNameFormat::LeftTruncated)
.setNameLimit(limit);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/exploration/finland/lapland/northern-lights"_el)->info("Survey opened."_el);
manager->shutdown();
};
showLimit("32 characters"_el, el::CpCount{32U});
showLimit("18 characters"_el, el::CpCount{18U});
showLimit("Unlimited"_el, el::CpCount::zero());
}
Output
32 characters:
…finland/lapland/northern-lights: Survey opened.
18 characters:
…d/northern-lights: Survey opened.
Unlimited:
guild/exploration/finland/lapland/northern-lights: Survey opened.
Multiline Messages and First-Line Truncation
Multiline messages are useful for structured explanations and diagnostics, yet they can be awkward in destinations where
one physical line is expected to represent one event.
LogLineFormat::setMessageTruncation chooses how the
message is prepared before it is inserted into the pattern.
LogMessageTruncation::None is the default and preserves the complete message, including line breaks.
LogMessageTruncation::FirstLine stops at the first newline and appends the truncation mark when later lines were
removed.
This mode does not consult the numeric message limit: its boundary is the first line, not a character count.
log/LoggingTopics/LineFirstLineTruncation.cpp
/// Choose whether multiline messages remain complete or stop after their first line.
///
/// `LogMessageTruncation::None` is the default and preserves line breaks. `FirstLine` keeps the text before the first
/// newline and appends the configured truncation mark without consulting the message limit.
void lineFirstLineTruncation() {
const auto showMessage = [](const el::String &label, const el::LogMessageTruncation truncation) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level}: {message}"_el).setMessageTruncation(truncation);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("Cloud cover is increasing.\nThe ridge team will wait below the summit."_el);
manager->shutdown();
};
showMessage("Complete message"_el, el::LogMessageTruncation::None);
showMessage("First line only"_el, el::LogMessageTruncation::FirstLine);
}
Output
Complete message:
INF: Cloud cover is increasing.
The ridge team will wait below the summit.
First line only:
INF: Cloud cover is increasing.…
Message Character Limits
When the message itself must fit into a predictable space, select LogMessageTruncation::CharacterCount and set the
budget with LogLineFormat::setMessageLimit.
The limit counts Unicode code points in the message, rather than encoded bytes.
Pattern literals, the level, the timestamp, and the stream name sit outside this particular budget.
The truncation mark occupies part of the available count, keeping the final message within the requested length.
A zero limit disables character-count shortening instead of producing an empty message.
This makes it convenient to expose 0 as an application setting meaning “unlimited.”
log/LoggingTopics/LineMessageLimits.cpp
/// Limit the rendered message while leaving the other pattern fields outside the budget.
///
/// Select `CharacterCount` with `setMessageTruncation()`, then pass the code-point budget to `setMessageLimit()`. A
/// zero message limit disables this shortening rule.
void lineMessageLimits() {
const auto showLimit = [](const el::String &label, const el::CpCount limit) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level} [{name}] {message}"_el)
.setMessageTruncation(el::LogMessageTruncation::CharacterCount)
.setMessageLimit(limit);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/weather"_el)
->warn("A snow front will reach the northern platform before dusk."_el);
manager->shutdown();
};
showLimit("48 message characters"_el, el::CpCount{48U});
showLimit("24 message characters"_el, el::CpCount{24U});
showLimit("Unlimited message"_el, el::CpCount::zero());
}
Output
48 message characters:
WRN [guild/weather] A snow front will reach the northern platform b…
24 message characters:
WRN [guild/weather] A snow front will reach…
Unlimited message:
WRN [guild/weather] A snow front will reach the northern platform before dusk.
Total Rendered Line Limits
Sometimes the destination, rather than the message, imposes the useful boundary.
LogMessageTruncation::TotalLineLength treats
LogLineFormat::setMessageLimit as a code-point budget for the
complete rendered line.
The formatter first accounts for pattern literals and the rendered time, level, and name, then gives the remaining space
to {message}.
A detailed prefix therefore leaves less room for the same message, as the demo illustrates.
If {message} occurs more than once, its remaining budget is shared between those occurrences.
If fixed fields already consume the complete budget, the message becomes empty; fixed fields themselves are never
shortened by this setting.
As with character-count mode, a zero limit disables the rule.
log/LoggingTopics/LineTotalLimits.cpp
/// Limit the complete rendered line by giving the message the space left by fixed fields.
///
/// `TotalLineLength` counts pattern literals, the level, the rendered name, and every message occurrence against the
/// `setMessageLimit()` budget. A richer prefix therefore leaves fewer characters for the message.
void lineTotalLimits() {
const auto showPattern = [](const el::String &label, el::String pattern) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern(std::move(pattern))
.setMessageTruncation(el::LogMessageTruncation::TotalLineLength)
.setMessageLimit(el::CpCount{56U});
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/weather"_el)
->warn("A snow front will reach the northern platform before dusk."_el);
manager->shutdown();
};
showPattern("Message-only pattern"_el, "{message}"_el);
showPattern("Level-and-name pattern"_el, "{level} [{name}] {message}"_el);
}
Output
Message-only pattern:
A snow front will reach the northern platform before du…
Level-and-name pattern:
WRN [guild/weather] A snow front will reach the norther…
Truncation Marks
A shortened value should look shortened.
The default mark is the single ellipsis character …, a compact choice that works well in ordinary console and file
output.
LogLineFormat::setTruncationMark can replace it with text
that better suits a plain-text destination, or with an empty string when the surrounding format already makes the limit
obvious.
The same mark is used for first-line messages, message character limits, complete-line limits, and left-truncated names. For the numeric limits it is part of the code-point budget, so a longer marker leaves less room for retained text. An empty marker saves that space, but it also removes the reader’s visible clue that text is missing.
log/LoggingTopics/LineTruncationMarks.cpp
/// Choose the visible mark that tells readers text was shortened.
///
/// `setTruncationMark()` replaces the default ellipsis for first-line, character-count, total-line, and left-truncated
/// name formatting. The mark occupies part of a character-count budget and may also be empty.
void lineTruncationMarks() {
const auto showMark = [](const el::String &label, el::String mark) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{message}"_el)
.setMessageTruncation(el::LogMessageTruncation::CharacterCount)
.setMessageLimit(el::CpCount{36U})
.setTruncationMark(std::move(mark));
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("The northern observation platform is closing for the night."_el);
manager->shutdown();
};
showMark("Ellipsis"_el, "…"_el);
showMark("Text marker"_el, " [more]"_el);
showMark("No marker"_el, ""_el);
}
Output
Ellipsis:
The northern observation platform i…
Text marker:
The northern observation plat [more]
No marker:
The northern observation platform is
Per-Writer Line Formats
A terminal and a log file often serve different readers.
The terminal can show a compact progress message, while a file keeps timestamps and stream names for later
investigation.
Each writer route can have its own LogLineFormat, so the same entry can
appear differently at each destination.
The first example on this page sets a default format for the configuration.
To give its console route a message-only layout, replace the console addWriter() call with this small variation:
auto consoleFormat = el::LogLineFormat{};
consoleFormat.setPattern("{message}"_el);
configuration.addWriter(
el::LogWriter::createForConsole(el::application().terminal()), {}, std::move(consoleFormat));
The second argument, {}, keeps the filter that accepts all levels and paths.
The third argument supplies the console format.
Any other writer added without that third argument continues to use the configuration’s default format.
Writing an ELCL Logging Configuration shows a complete, parser-exercised configuration with different console and file layouts.
A route’s custom format supplies all of its presentation settings, including truncation and timestamp choices.
It does not inherit settings from the configuration default.
When you want to change only one setting, copy the shared format and adjust that copy before passing it to
LogConfiguration::addWriter.
Formatting leaves the original entry intact: shortening a message for the console does not shorten the file writer’s
copy.
Producer Message Limits versus Rendered Line Limits
All limits on LogLineFormat are presentation choices applied when a writer
renders an entry.
They do not reduce the message retained in the manager’s queue, and their units are Unicode code points.
Choose them to keep logs pleasant to scan and compatible with a destination’s line conventions.
LogManagerOptions::setMaximumMessageBytes solves a
different problem.
It places a producer-side byte limit on the sanitized message before the entry enters the queue, protecting memory even
when a writer later uses an unlimited line format.
Configure that boundary as part of Configuring Log Manager Limits, independently of the line layout readers see.
Complete demo sources
log/LoggingTopics/LineFormats.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogWriter.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Install one line format as part of a complete logging configuration.
///
/// Create `LogLineFormat` as a value, customize it, and move it into `LogConfiguration`. The manager takes another
/// complete configuration value and uses this default for routes without their own line-format override.
void lineFormats() {
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level} [{name}] {message}"_el);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
const auto log = manager->createStream("guild/routes"_el);
log->info("The route catalog is ready."_el);
manager->shutdown();
}
}
log/LoggingTopics/LinePatterns.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Arrange semantic log fields with a validated placeholder pattern.
///
/// `setPattern()` accepts time, level, name, and message placeholders. A compact command can show only the message,
/// while larger applications often add level and name. Doubled braces produce literal braces in the output.
void linePatterns() {
const auto showPattern = [](const el::String &label, el::String pattern) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern(std::move(pattern));
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/routes"_el)->info("Selected the ridge route."_el);
manager->shutdown();
};
showPattern("Message only"_el, "{message}"_el);
showPattern("Level and name"_el, "{level} [{name}] {message}"_el);
showPattern("Literal braces"_el, "{{guild}} {level}: {message}"_el);
}
}
log/LoggingTopics/LineTimestampZones.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogTimestampZone.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Render retained UTC timestamps either in UTC or in the process-local time zone.
///
/// `setTimestampZone()` changes presentation only; the entry keeps its original UTC instant. ISO output includes `Z`
/// for UTC or the applicable numeric offset for local time.
void lineTimestampZones() {
const auto showZone = [](const el::String &label, const el::LogTimestampZone zone) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{time} - {message}"_el).setTimestampZone(zone);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("Expedition clock synchronized."_el);
manager->shutdown();
};
showZone("UTC rendering"_el, el::LogTimestampZone::Utc);
showZone("Local rendering"_el, el::LogTimestampZone::Local);
}
}
log/LoggingTopics/LineLevelFormats.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLevelFormat.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Choose a compact or descriptive spelling for severity levels.
///
/// `setLevelFormat()` affects only the text inserted by `{level}`. The entry keeps the same warning level, so routing
/// and console styling remain unchanged across all four representations.
void lineLevelFormats() {
const auto showLevel = [](const el::String &label, const el::LogLevelFormat levelFormat) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level}: {message}"_el).setLevelFormat(levelFormat);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->warn("One route marker needs repainting."_el);
manager->shutdown();
};
showLevel("Three uppercase letters"_el, el::LogLevelFormat::ThreeLetterUpper);
showLevel("Three lowercase letters"_el, el::LogLevelFormat::ShortLower);
showLevel("Full lowercase name"_el, el::LogLevelFormat::FullLower);
showLevel("Full uppercase name"_el, el::LogLevelFormat::FullUpper);
}
}
log/LoggingTopics/LineNameFormats.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogNameFormat.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Select how much of a hierarchical stream path appears in `{name}`.
///
/// `setNameFormat()` can preserve the full path, keep its leaf, combine its first and final segments, or retain the
/// right side of a long path within the default 40-character name limit.
void lineNameFormats() {
const auto showName = [](const el::String &label, const el::LogNameFormat nameFormat) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{name}: {message}"_el).setNameFormat(nameFormat);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/exploration/finland/lapland/northern-lights"_el)->info("Survey opened."_el);
manager->shutdown();
};
showName("Full path"_el, el::LogNameFormat::Full);
showName("Leaf segment"_el, el::LogNameFormat::Leaf);
showName("First and final segments"_el, el::LogNameFormat::HeadAndLeaf);
showName("Left-truncated path"_el, el::LogNameFormat::LeftTruncated);
}
}
log/LoggingTopics/LineNameLimits.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/CpCount.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogNameFormat.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Bound left-truncated stream names by a code-point count.
///
/// `setNameLimit()` is consulted only for `LogNameFormat::LeftTruncated`. Keeping the right side preserves the most
/// specific path segments; a zero limit disables name shortening even when that format is selected.
void lineNameLimits() {
const auto showLimit = [](const el::String &label, const el::CpCount limit) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{name}: {message}"_el)
.setNameFormat(el::LogNameFormat::LeftTruncated)
.setNameLimit(limit);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/exploration/finland/lapland/northern-lights"_el)->info("Survey opened."_el);
manager->shutdown();
};
showLimit("32 characters"_el, el::CpCount{32U});
showLimit("18 characters"_el, el::CpCount{18U});
showLimit("Unlimited"_el, el::CpCount::zero());
}
}
log/LoggingTopics/LineFirstLineTruncation.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogMessageTruncation.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Choose whether multiline messages remain complete or stop after their first line.
///
/// `LogMessageTruncation::None` is the default and preserves line breaks. `FirstLine` keeps the text before the first
/// newline and appends the configured truncation mark without consulting the message limit.
void lineFirstLineTruncation() {
const auto showMessage = [](const el::String &label, const el::LogMessageTruncation truncation) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level}: {message}"_el).setMessageTruncation(truncation);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("Cloud cover is increasing.\nThe ridge team will wait below the summit."_el);
manager->shutdown();
};
showMessage("Complete message"_el, el::LogMessageTruncation::None);
showMessage("First line only"_el, el::LogMessageTruncation::FirstLine);
}
}
log/LoggingTopics/LineMessageLimits.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/CpCount.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogMessageTruncation.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Limit the rendered message while leaving the other pattern fields outside the budget.
///
/// Select `CharacterCount` with `setMessageTruncation()`, then pass the code-point budget to `setMessageLimit()`. A
/// zero message limit disables this shortening rule.
void lineMessageLimits() {
const auto showLimit = [](const el::String &label, const el::CpCount limit) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{level} [{name}] {message}"_el)
.setMessageTruncation(el::LogMessageTruncation::CharacterCount)
.setMessageLimit(limit);
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/weather"_el)
->warn("A snow front will reach the northern platform before dusk."_el);
manager->shutdown();
};
showLimit("48 message characters"_el, el::CpCount{48U});
showLimit("24 message characters"_el, el::CpCount{24U});
showLimit("Unlimited message"_el, el::CpCount::zero());
}
}
log/LoggingTopics/LineTotalLimits.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/CpCount.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogMessageTruncation.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Limit the complete rendered line by giving the message the space left by fixed fields.
///
/// `TotalLineLength` counts pattern literals, the level, the rendered name, and every message occurrence against the
/// `setMessageLimit()` budget. A richer prefix therefore leaves fewer characters for the message.
void lineTotalLimits() {
const auto showPattern = [](const el::String &label, el::String pattern) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern(std::move(pattern))
.setMessageTruncation(el::LogMessageTruncation::TotalLineLength)
.setMessageLimit(el::CpCount{56U});
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->createStream("guild/weather"_el)
->warn("A snow front will reach the northern platform before dusk."_el);
manager->shutdown();
};
showPattern("Message-only pattern"_el, "{message}"_el);
showPattern("Level-and-name pattern"_el, "{level} [{name}] {message}"_el);
}
}
log/LoggingTopics/LineTruncationMarks.cpp
// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
// SPDX-License-Identifier: Apache-2.0
#include <DemoCommon.hpp>
#include <erbsland/all_log.hpp>
#include <erbsland/CpCount.hpp>
#include <erbsland/LogConfiguration.hpp>
#include <erbsland/LogLineFormat.hpp>
#include <erbsland/LogManager.hpp>
#include <erbsland/LogMessageTruncation.hpp>
#include <erbsland/LogWriter.hpp>
#include <erbsland/StandardStreams.hpp>
#include <erbsland/String.hpp>
#include <memory>
#include <utility>
namespace demo {
/// Choose the visible mark that tells readers text was shortened.
///
/// `setTruncationMark()` replaces the default ellipsis for first-line, character-count, total-line, and left-truncated
/// name formatting. The mark occupies part of a character-count budget and may also be empty.
void lineTruncationMarks() {
const auto showMark = [](const el::String &label, el::String mark) -> void {
el::io::printLine(label, ":"_el);
auto lineFormat = el::LogLineFormat{};
lineFormat.setPattern("{message}"_el)
.setMessageTruncation(el::LogMessageTruncation::CharacterCount)
.setMessageLimit(el::CpCount{36U})
.setTruncationMark(std::move(mark));
auto configuration = el::LogConfiguration{};
configuration.setLineFormat(std::move(lineFormat))
.addWriter(el::LogWriter::createForConsole(el::application().terminal()));
const auto manager = el::LogManager::create();
manager->setConfiguration(std::move(configuration));
manager->rootStream()->info("The northern observation platform is closing for the night."_el);
manager->shutdown();
};
showMark("Ellipsis"_el, "…"_el);
showMark("Text marker"_el, " [more]"_el);
showMark("No marker"_el, ""_el);
}
}