Migrating from glog to logme: LOG, CHECK, PLOG, and VLOG in a New Logging Model
Migrating from glog to logme does not have to start with a full rewrite of every logging call. In large C++ projects, that is usually the wrong first step. Logging statements are spread across the codebase, and they often sit next to checks, conditions, diagnostic messages, CHECK, PLOG, VLOG, and debug-only calls. Replacing everything at once can create a huge diff and make it harder to trust that the behavior of the program has not changed.
That is why the logme logging library supports two migration levels.
The first level is compatibility macros. They allow existing glog-style constructs to move over with minimal code changes: LOG(INFO), LOG_IF, CHECK, PLOG, PCHECK, DLOG, VLOG, LOG_FIRST_N, and LOG_EVERY_N. This layer is useful when a project needs to migrate from glog to logme without immediately redesigning every logging site.
The second level is the native logme model. After the initial migration, the code can gradually move to channels, backends, runtime control, _Do, _Once, _Every, _Collapse, trace points, and output formats. This is where logme becomes more than a replacement for glog macros. Logging turns into a controllable diagnostic subsystem rather than just a stream of text lines.
Why migrate from glog to logme
glog is well known and convenient for classic application logging. Its macros are simple and familiar: LOG(INFO) writes an informational message, CHECK verifies a condition, PLOG appends a system error, and VLOG controls verbose diagnostics.
Over time, however, large C++ systems often need more than a traditional logging API. Detailed diagnostics may need to be enabled without restarting the process. Different requests may have to be routed to different files. Log volume, rotation, and retention often need more precise control. Sometimes one specific trace point must be enabled for a short period of time. And in hot paths, expensive diagnostic data should not be built when the message will not be written.
This is where logme uses a different model. Familiar migration scenarios remain available through compatibility macros, but the library is not limited to them. Instead of one global stream of messages, logme provides a structure of channels and backends. Runtime control replaces permanent debug noise. Mechanisms such as _Once, _Every, and _Collapse replace manual repeat suppression. Just as importantly, all of this is built on a fast logging path. Current logbench measurements show that logme is significantly faster than glog in key scenarios, including file logging.
Performance matters too
A glog to logme migration is useful not only because of runtime control and a richer logging model. Performance is also a practical reason.
glog is convenient, but it is not the lightest option for high-volume logging. When millions of records or gigabytes of log data pass through the logger, the difference becomes visible. This is especially important for file logging, because writing to files is the primary production mode for many services.
According to current logbench measurements, logme is much faster than glog in several important scenarios. This applies not only to synthetic null-output tests, but also to real file output. For large C++ systems, this matters: the logging library should not become the main CPU consumer and should not interfere with the application under load.
At the same time, logme does not gain speed by removing useful features. Higher performance is combined with channels, backends, runtime control, rotation, retention, output formats, and macros for controlling message flow. Migrating from glog to logme is not a choice between speed and functionality. It is a way to get both.
LOG(INFO) and message levels
The simplest case is a regular LOG call.
In glog, code often looks like this:
LOG(INFO) << "request started: " << request_id;
LOG(WARNING) << "slow request: " << elapsed_ms;
LOG(ERROR) << "failed to open file: " << path;
With the compatibility header, this code can remain almost unchanged:
#include <Logme/GlogCompat.h>
LOG(INFO) << "request started: " << request_id;
LOG(WARNING) << "slow request: " << elapsed_ms;
LOG(ERROR) << "failed to open file: " << path;
Internally, the message already goes through logme. This is convenient during the first migration stage: the code still looks familiar, while the project starts moving to the new logging library.
Native logme code is usually better written explicitly:
LogmeI("request started: %s", requestId.c_str());
LogmeW("slow request: %u", elapsedMs);
LogmeE("failed to open file: %s", path.c_str());
or through the format-style API:
fLogmeI("request started: {}", requestId);
fLogmeW("slow request: {}", elapsedMs);
fLogmeE("failed to open file: {}", path);
At this level, the syntax change is straightforward. The important difference is not the name of the macro or function. In logme, a message does not have to go to one common log. It can go to a specific channel. That channel can be enabled, disabled, redirected, connected to different backends, or assigned a different level while the process is running.
LOG(FATAL), CHECK, and the fatal handler
In glog, LOG(FATAL) and a failed CHECK terminate the process. This is important behavior, and it should not be silently replaced with ordinary critical logging.
In logme, critical logging is separated from the process-termination policy. LogmeC writes a critical record, but by default it does not call abort() and does not terminate the program. This is intentional. Existing projects and tests may use critical messages as a normal logging level, and a logging library should not unexpectedly change program behavior.
If a project needs glog-like fatal semantics, it can enable them explicitly with a fatal handler:
Logme::Instance->SetFatalHandler([]()
{
std::abort();
});
Another policy can be used as well:
Logme::Instance->SetFatalHandler([]()
{
std::terminate();
});
Before calling the fatal handler, logme forces FlushAll(). This is important because the critical message and previous records should reach the output files before the process exits.
This approach gives more control than a hardcoded abort. Compatibility macros such as CHECK and LOG(FATAL) can behave like glog if a fatal handler is installed. If no handler is installed, they write a critical message, but the process is not terminated.
Example:
CHECK(ptr != nullptr) << "ptr is null";
CHECK_EQ(state, READY) << "unexpected state";
LOG(FATAL) << "unrecoverable error";
In logme, these calls go through the critical path. The application decides what a critical failure means: just a record, abort, terminate, or a custom emergency procedure.
CHECK_EQ, CHECK_NOTNULL, and single evaluation of arguments
glog provides a useful family of checks: CHECK_EQ, CHECK_NE, CHECK_LT, CHECK_LE, CHECK_GT, CHECK_GE, and CHECK_NOTNULL. During migration, two properties must be preserved.
First, expressions should be evaluated only once. This is critical when the arguments contain function calls, counters, or objects with side effects.
Second, the text after << should not be evaluated when the check succeeds. If the condition is true, the diagnostic message is not needed.
The logme compatibility layer covers these cases:
CHECK_EQ(left, right) << "values are different";
CHECK_NE(handle, INVALID_HANDLE_VALUE) << "bad handle";
CHECK_NOTNULL(connection)->Send(data);
For native logme code, native check macros can be used:
LogmeCheckEq(left, right) << "values are different";
LogmeCheckNotNull(connection)->Send(data);
When a check fails, the record is written as a critical message. Whether the process terminates depends on the configured fatal handler.
PLOG and PCHECK
PLOG is useful in glog because it automatically appends the system error. This is common near file operations, socket APIs, and system calls.
Typical glog code looks like this:
PLOG(ERROR) << "open failed";
PCHECK(fd >= 0) << "open failed";
The logme compatibility layer supports these macros too:
PLOG(ERROR) << "open failed";
PCHECK(fd >= 0) << "open failed";
One important detail is that the system error must be captured immediately. On POSIX, this is errno. On Windows, it is GetLastError(). If the error value is read later, intermediate code may overwrite it. For this reason, PLOG and PCHECK should capture the error value at the very beginning of macro processing.
In native logme style, the P-variants can be used:
LogmeE_P("open failed");
LogmePCheck(fd >= 0) << "open failed";
This makes it explicit that the message is related to the last system error.
DLOG and DCHECK
In glog, DLOG and DCHECK are intended for debug-only logging. In release builds, these calls usually disappear and should not evaluate their arguments.
This is not the same thing as an ordinary debug level. A debug level may still be useful in a release build when the application supports runtime diagnostics. For example, a production service may temporarily enable detailed debug output for one channel. Therefore, LogmeD and DLOG represent different ideas.
The compatibility layer preserves the familiar glog behavior:
DLOG(INFO) << "debug-only message";
DLOG_IF(WARNING, suspicious) << "debug-only warning";
DCHECK(ptr != nullptr) << "ptr is null";
For new logme code, the choice should be deliberate. If a message is needed only in debug builds, debug-only compatibility macros are appropriate. If the message should be available in production when diagnostics are enabled, it is better to use normal logme debug-level mechanisms and control them through channels, levels, and runtime control.
VLOG: compatibility, not the best model for new code
VLOG(n) in glog is numeric verbosity. For example, VLOG(1) may mean basic details, while VLOG(3) may mean much noisier diagnostics. This is usually controlled by a global verbosity level and sometimes by module-level settings.
For migration, this is useful. That is why the logme compatibility layer provides VLOG, VLOG_IF, and VLOG_IS_ON:
VLOG(1) << "request details";
VLOG(3) << "headers: " << headers;
if (VLOG_IS_ON(2))
{
BuildExpensiveDiagnostics();
}
For new code, however, numeric verbosity is not the best way to express diagnostics. The number 3 does not explain what is being enabled. A few years later, it may be difficult to understand how VLOG(2) differs from VLOG(4).
In logme, named diagnostics are usually a better fit: channels, subsystems, trace points, and _Do. They make the model easier to read and easier to control. Instead of enabling “verbosity level 3”, you can enable a trace point for HTTP headers or a subsystem for image decoding. That is clearer in code and more useful in production.
For this reason, VLOG should be treated as a migration layer. It helps old code move from glog to logme, but new logme code should gradually move toward named diagnostic points.
LOG_FIRST_N and LOG_EVERY_N
glog provides macros that limit how often messages are written. This is an important part of practical logging. Without rate limiting, one repeated problem can flood the log with millions of identical lines.
The logme compatibility layer supports familiar constructs:
LOG_FIRST_N(WARNING, 5) << "first warnings only";
LOG_EVERY_N(INFO, 100) << "periodic status";
Native logme also provides mechanisms for controlling message flow. _Once is useful when an event should be logged only once. _Every works well for time-based limiting. _Collapse and _CollapseIgnore help collapse repeated message series and keep logs readable.
In practice, this is one of the reasons to move from a simple logger to logme. The problem is not only write speed. The log must remain useful. If it is filled with repeated noise, performance alone does not solve the problem.
RAW_LOG should not be emulated by a normal logger
glog has RAW_LOG for low-level situations where the normal logging path cannot be used. This is a separate area: minimal runtime dependency, no ordinary locking, no regular allocation, and support for emergency scenarios.
The logme compatibility layer should not pretend that a normal logger is a full replacement for RAW_LOG. If a log message goes through channels, backends, formatting, and normal delivery mechanisms, then it is no longer raw logging in the glog sense.
For this reason, RAW_LOG should not be migrated mechanically. If a project uses it in an allocator, a signal handler, or low-level synchronization logic, those places should be reviewed separately. A simple replacement with LOG(ERROR) or LogmeE may be wrong.
Quick mapping table
| glog | logme |
|---|---|
LOG(INFO) |
LOG(INFO) through GlogCompat.h, or LogmeI |
LOG(WARNING) |
LogmeW |
LOG(ERROR) |
LogmeE |
LOG(FATAL) |
LogmeC plus an optional fatal handler |
LOG_IF |
Compatibility LOG_IF, or native conditional macros |
CHECK |
CHECK through compatibility, or LogmeCheck |
CHECK_EQ and family |
Compatibility macros, or LogmeCheckEq/Ne/Lt/Le/Gt/Ge |
CHECK_NOTNULL |
Compatibility macro, or LogmeCheckNotNull |
PLOG |
Compatibility PLOG, or Logme*_P |
PCHECK |
Compatibility PCHECK, or LogmePCheck |
DLOG / DCHECK |
Debug-only compatibility macros |
VLOG |
Compatibility layer; better to replace gradually with trace points |
LOG_FIRST_N |
Compatibility macro, or native first-N logic |
LOG_EVERY_N |
Compatibility macro, or native every-N logic |
RAW_LOG |
Not emulated by the normal logging path |
A practical migration strategy
The best first step is usually the compatibility header. It allows the project to build on logme without a complete rewrite of all logging sites.
After that, the code can be reviewed and split into groups. Simple LOG(INFO) calls can stay as they are for a while or be gradually replaced with LogmeI or fLogmeI. CHECK and PLOG can remain in the compatibility layer if their behavior is clear and covered by tests. VLOG is usually better to replace over time with channels, subsystems, and trace points.
Hot paths and expensive diagnostics deserve special attention. If a message requires building a large string, walking a complex data structure, or collecting additional context, it is often better to move that work into _Do. Then the diagnostic data is prepared only when the corresponding logging path is actually enabled.
Noisy messages should also be reviewed. Repeated output can often be replaced with _Once, _Every, LOG_FIRST_N, LOG_EVERY_N, or _Collapse. This usually gives more value than simply replacing one logging API with another. The log becomes not only faster, but also more useful.
Conclusion
A migration from glog to logme can be done gradually.
At the first stage, compatibility macros preserve the familiar style: LOG, CHECK, PLOG, DLOG, and VLOG. This reduces migration risk and keeps the initial diff smaller.
At the next stage, the project can start using the stronger parts of logme: channels, backends, runtime control, fast file output, output formats, rotation, retention, _Do, _Once, _Every, _Collapse, and trace points.
The goal is not just to replace one macro name with another. The goal is to get a faster and more controllable logging model. glog works well for classic application logging. logme goes further: it is faster in important scenarios, better suited for high-volume file logging, and designed to manage logs as part of a live production system.
You may also be interested in the article about migrating from spdlog to logme.