-
Notifications
You must be signed in to change notification settings - Fork 3
Choosing Logging Macros
This page answers the practical question: which logme macro should be used when a program needs to write a message to the log?
The short answer is simple: start with LogmeI, LogmeW, LogmeE, LogmeD, or LogmeC, then decide whether the message needs a special channel, a different formatting style, rate limiting, or protection from unnecessary work. The important part is not memorizing every macro name. The important part is choosing the form that matches the cost and routing of the message.
For the complete macro reference, see Logging Macros.
The last letter of the macro name is the log level:
LogmeD("request parsed: id=%d", requestId);
LogmeI("server started on port %d", port);
LogmeW("slow request: %d ms", elapsedMs);
LogmeE("failed to open %s", path.c_str());
LogmeC("fatal configuration error: %s", reason.c_str());Use DEBUG for diagnostic detail that is normally disabled. Use INFO for normal lifecycle events. Use WARN when the program can continue, but something deserves attention. Use ERROR when an operation failed. Use CRITICAL for failures that normally make the process or component unusable.
This first choice should be about meaning, not about performance. Performance is handled by the form of the macro and by channel selection.
logme has three common ways to write a message.
The classic form is printf-style formatting:
LogmeI("client %s connected from %s", user.c_str(), ip.c_str());This is the natural choice for existing C/C++ code that already uses % format strings, and it is available in the normal LogmeX family.
The stream form is selected when the macro is called without a format string:
LogmeI() << "client " << user << " connected from " << ip;Use it when the code is easier to read as a stream expression, or when values already have useful operator<< support.
The std::format-style form uses the fLogmeX family:
fLogmeI("client {} connected from {}", user, ip);This form uses {} placeholders. Depending on the build configuration, it is backed by std::vformat or fmt::vformat. If format support is disabled, the fLogmeX macros are intentionally unavailable.
Do not treat these three forms as different logging systems. They only describe how the final text is produced. Routing, levels, subsystems, overrides, and filtering still follow the same logme model.
When no channel is passed to the macro, logme uses the normal channel context for the call site. That is convenient and it should remain the default for simple application code:
LogmeI("configuration loaded");If a source file or class should normally write to a named channel, declare the channel and let the regular macros use that CH context:
LOGME_CHANNEL(CH, "http");
void StartHttpServer(int port)
{
LogmeI("HTTP server started on port %d", port);
}This is different from always writing to the global default channel. The call site carries its channel identity, and runtime control can enable, disable, filter, or route that channel separately.
For one particular message, pass a channel explicitly. That is useful when a function normally belongs to one channel, but a specific record should be routed elsewhere:
LOGME_CHANNEL(CHTLS, "tls");
LogmeW(CHTLS, "certificate expires in %d days", daysLeft);A Logme::ID such as CHTLS is easy to write and is usually fine outside hot paths. In hot code, prefer a ChannelPtr when you already have it:
Logme::ChannelPtr pchHttp = Logme::Instance->CreateChannel(Logme::ID{"http"});
LogmeD(pchHttp, "request state=%d", state);This does two useful things. First, it avoids repeated channel name lookup. Second, when ChannelPtr is the first macro argument, the macro can check the channel state before the remaining arguments are evaluated. That is the fastest normal form for a disabled or filtered-out message.
A common mistake is to rely only on the fact that logging is disabled at runtime. That does not automatically make all expressions inside the macro call free. If the macro has to evaluate a function argument before the logger can decide, that work has already happened.
This is not ideal:
LogmeD("payload=%s", BuildPayload(request).c_str());If DEBUG is disabled, the text may not be written, but BuildPayload() still has to be evaluated before the function call can receive the argument.
When the message belongs to a known channel, pass the ChannelPtr first:
LogmeD(pchHttp, "payload=%s", BuildPayload(request).c_str());Now the macro can reject the call earlier when the channel is inactive or the level is filtered out. This is why explicitly using a ChannelPtr matters for performance.
When some preparation code must run only if the message will really be emitted, use the *_Do form:
std::string payload;
LogmeD_Do(
pchHttp
, payload = BuildPayload(request)
, "payload=%s"
, payload.c_str()
);The preparation block is inside the macro’s checked path. If the channel will not log the record, the assignment is not executed.
The same idea exists for std::format style:
std::string payload;
fLogmeD_Do(
pchHttp
, payload = BuildPayload(request)
, "payload={}"
, payload
);Use *_Do in hot paths, parsers, loops, network tracing, or anywhere the message needs temporary strings, dumps, conversions, or other expensive work. For a cheap integer or a literal message, normal LogmeX is simpler and usually the right choice.
The _If suffix is for an explicit condition:
LogmeW_If(retryCount > 3, "retry count is high: %d", retryCount);This is readable when the condition itself is the reason for logging. It is not the same thing as the channel precheck. The condition decides whether the logging call is attempted; it does not by itself make expensive message arguments disappear behind a channel-state test.
For expensive preparation, prefer ChannelPtr as the first argument or use *_Do.
Some messages are useful once, but useless a thousand times. For those cases, use the once-per-call-site macros:
LogmeW_Once("using fallback configuration");For messages that should still appear periodically, use _Every:
LogmeW_Every(5000, "queue is still full: size=%d", queueSize);The interval is in milliseconds. These macros are especially useful in loops and retry paths. They keep the diagnostic signal without flooding the output.
There are fLogmeX_Once and fLogmeX_Every variants for std::format style as well.
_Once and _Every are good when the decision is time-based or one-time. Use _Collapse when the first occurrence should be visible immediately, but consecutive repeats should be summarized instead of printed one by one.
LogmeW_Collapse(10, "backend is still unavailable");Use _CollapseIgnore when the messages are logically the same but contain volatile fields:
LogmeW_CollapseIgnore(
"request_id=[0-9]+"
, 10
, "request_id=%llu backend is still unavailable"
, requestId
);Choose between the repetition-control macros like this:
| Situation | Macro |
|---|---|
| Show only the first occurrence | LogmeX_Once(...) |
| Show periodically by time interval | LogmeX_Every(ms, ...) |
| Show the first occurrence and summarize identical consecutive repeats | LogmeX_Collapse(limit, ...) |
| Summarize repeats while ignoring ids, timestamps, counters, or other volatile text | LogmeX_CollapseIgnore(ignoreRegex, limit, ...) |
See Collapse Logging for exact behavior.
Choose fLogmeX when the message is naturally written with std::format syntax:
fLogmeI("accepted connection from {}:{}", host, port);The same suffix rules apply:
fLogmeW_Once("configuration key '{}' is deprecated", key);
fLogmeD_Every(1000, "processed {} packets", packets);
fLogmeE(pchTls, "TLS error: {}", errorText);Do not use fLogmeX just because it looks modern. It builds the formatted string through the configured format backend and then logs it. For old code with % strings, LogmeX is direct and clear. For new code with types that format well with {}, fLogmeX is often nicer.
The regular macros use the CH visible at the call site. This is usually what you want, because a class or source file can define its own channel context.
In static methods or places where a class member named CH would confuse lookup, use the g suffix to force global ::CH and ::SUBSID:
void Utility::DumpState()
{
LogmeIg("utility state dump");
}Do not use LogmeIg as a better LogmeI. It is a specific tool for the global-channel lookup case.
LogmeP and LogmePV are not general message macros. They create a procedure-scope object that writes function entry and exit records, including duration. Use them when the question is “where did time go?” or “which function returned what?”:
int LoadConfig()
{
int result = 0;
LogmeP(result);
result = ReadConfigFile();
return result;
}
void RefreshCache()
{
LogmePV();
RebuildCache();
}Level-specific variants exist: LogmePD, LogmePI, LogmePW, LogmePE, LogmePC, and the corresponding void-return forms LogmePVD, LogmePVI, LogmePVW, LogmePVE, LogmePVC.
For a single event inside a function, use LogmeX or fLogmeX. Use procedure macros when the scope itself is the thing being logged.
The regular LogmeX macros support stream syntax, and because of that they expand in a way that can interact badly with an unbraced if/else.
Do not write this:
if (connected)
LogmeI("connected");
else
LogmeW("not connected");Use braces:
if (connected)
{
LogmeI("connected");
}
else
{
LogmeW("not connected");
}The fLogmeX family is safer in this particular pattern because it is implemented as a statement-like macro, but using braces is still the clearest style.
For ordinary code, start with the simplest macro:
LogmeI("started");When the message has a specific destination, pass the channel:
LogmeI(CHHTTP, "started");When the code is performance-sensitive, keep a ChannelPtr and pass it first:
LogmeD(pchHttp, "state=%d", state);When the message needs expensive preparation, use *_Do:
LogmeD_Do(
pchHttp
, dump = MakeRequestDump(request)
, "request dump: %s"
, dump.c_str()
);When the message is repeated noise, use _Once, _Every, _Collapse, or _CollapseIgnore depending on whether it should be hidden, rate-limited, or aggregated. When the syntax should be {}-based, use fLogmeX. When the goal is function entry/exit tracing, use LogmeP or LogmePV.
That is the intended choice model: level first, then formatting, then routing, then cost control.
logme — flexible runtime logging system
Home · Getting Started · Architecture · Output · Backends · Configuration
GitHub: https://github.com/efmsoft/logme
- Home
- Getting Started
- Why logme?
- Core Concepts
- Logging Macros
- Fatal Handling
- Crash Logging
- glog Compatibility
- C API
- Choosing Logging Macros
- Function tracing
- Trace Points
- Override Scopes
- Advanced Features
- Collapse Logging
- Feature Map
- Overview
- Console Backend
- Debugger Backend
- File Backend
- File Rotation & Retention
- Buffer Backend
- Ring Buffer Backend
- SharedFile Backend
- Callback Backend
- Windows Event Log Backend
- Custom Backends
- Runtime Control
- Configuration
- Configuration JSON
- Control Server
- Environment Control
- Control Policies
- Trace Points
- Message Filtering