Designing the Logger Class
The discussion Designing the Logging Component ends in concluding that ParallelZone needs
a Logger class. This page details the design and architecture of the
Logger class and the underlying infrastructure needed to make it work.
What is the Logger Class?
The Logger class is the user-facing API of ParallelZone’s logging
component. The Logger class is used by users of ParallelZone to log
events, i.e., errors, warnings, progress, etc.
Why do we need the Logger Class?
As summarized in Designing the Logging Component, we are not aware of any existing logging
solutions that do everything we need. We have thus chosen to implement the
missing features, to our liking, within the Logger class. The
Logger class also provides a stable API for ParallelZone users in the event
we should need to add additional logging capabilities or switch out the backend.
The Logger class also separates the concern of “what to log” from “how to
log it”; “how to log it” is the responsibility of the sink (which is currently
an implementation detail of the Logger class).
Logger Considerations
The considerations for the Logger class are the considerations from
Designing the Logging Component not addressed there. Specifically:
Multiple logging levels.
Printing every message should be reserved for difficult debugging.
Severity level (info, warn, etc.) used to triage what to print.
Concurrency aware.
Thread-safe ideally.
Different “sinks”
Console, file, databases.
Enable/disable logging
Can be expensive, too verbose, etc.
Assume logging disabled for performance runs
Ideally logger configurable at compile and runtime.
Existing Logger Solutions
Existing Logging Solutions covered the existing logging solutions in
detail and will not be replicated here. Instead we point out that of the
available choices, Spdlog has most of the features we want and is heavily
supported. As seen in the next section we have opted to build our Logger
infrastructure around Spdlog.
Logger Design
Fig. 13 Architecture of the Logger class and its infrastructure.
For the first implementation of the logging component we adopted the simple
architecture shown in Figure Fig. 13. Users of ParallelZone see
one class, Logger. As a first pass, Logger simply provides APIs
for printing arbitrary data, already in a std::string (or implicitly
convertible to a std::string) at various severity levels. Eventually, this
may be expanded to support more fine-grained control over the log formatting.
Internally Logger is implemented by a LoggerPIMPL object. The literal
LoggerPIMPL object is an abstract base class which defines the API for
actual implementations. Our Spdlog-based implementation derives the
SpdlogPIMPL class from LoggerPIMPL to implement the parts of the PIMPL
API common to all Spdlog-based implementations.
For now we have two sinks, and they are implemented by further specializing the
SpdlogPIMPL. The sinks, StdoutSpdlog and FileSpdlog respectively
output logs to standard out and a specified file.
This design addresses the considerations remaining from Designing the Logging Component by:
Multiple logging levels.
Gained by using Spdlog.
Concurrency aware.
Spdlog is thread-safe.
Different “sinks”
Provided by Spdlog.
Enable/disable logging
Spdlog supports log filtering by severity.
Logger can have a null pointer to represent null logging.
Future Considerations
The current design gets the job done, while being amenable to extension. In particular we note:
Can add
Sinkclasses so users define their own sink behavior (Spdlog also allows custom sinks, so ourSinkclass would wrap theirs).Can add more formatting options to
Logger. Spdlog uses the C++ fmt library (which the C++20 extension is based heavily on). We can expose this down the road.Can add support for more types beyond
std::string. At the end of the day we ultimately need to print strings so supporting of other types is simply a convenience mechanism for channeling them into the string paths.Can add wide-character support. Spdlog already supports it so it’s just a matter of exposing it.