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.
Place the Line Format in the Logging Configuration
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.
When the manager receives the finished configuration, every writer route in that snapshot uses the same format.
This gives console and file output a common vocabulary even when filters send them different entries.
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.
/// 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 applies that same line format to every writer route in the snapshot.
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();
}
INF [guild/routes] The route catalog is ready.
Arrange Fields With a Pattern
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
ParameterError 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.
/// 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);
}
Message only:
Selected the ridge route.
Level and name:
INF [guild/routes] Selected the ridge route.
Literal braces:
{guild} INF: Selected the ridge route.
Choose UTC or Local Timestamps
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.
/// 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);
}
UTC rendering:
2026-09-04 18:40:07Z - Expedition clock synchronized.
Local rendering:
2026-09-04 20:40:07+02:00 - Expedition clock synchronized.
Choose Compact or Descriptive Level Names
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.
/// 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);
}
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.
Show the Useful Part of a Stream Name
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.
/// 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);
}
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.
Bound Left-Truncated Stream Names
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.
/// 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::CpLength 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::CpLength{32U});
showLimit("18 characters"_el, el::CpLength{18U});
showLimit("Unlimited"_el, el::CpLength::zero());
}
32 characters:
…finland/lapland/northern-lights: Survey opened.
18 characters:
…d/northern-lights: Survey opened.
Unlimited:
guild/exploration/finland/lapland/northern-lights: Survey opened.
Keep Multiline Messages or Only Their First Line
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.
/// 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);
}
Complete message:
INF: Cloud cover is increasing.
The ridge team will wait below the summit.
First line only:
INF: Cloud cover is increasing.…
Limit the Message by Character Count
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.”
/// 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::CpLength 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::CpLength{48U});
showLimit("24 message characters"_el, el::CpLength{24U});
showLimit("Unlimited message"_el, el::CpLength::zero());
}
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.
Limit the Complete Rendered Line
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.
/// 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::CpLength{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);
}
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…
Choose a Clear Truncation Mark
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.
/// 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::CpLength{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);
}
Ellipsis:
The northern observation platform i…
Text marker:
The northern observation plat [more]
No marker:
The northern observation platform is
Keep Producer Byte Limits Separate
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.