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
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
Sink
classes so users define their own sink behavior (Spdlog also allows custom sinks, so ourSink
class 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.