The <utilities/formatter.h> header speci# Logging Utilities
The header <utilities/log.h> provides a couple of macros for simple logging and debugging.
LOG(...)dispatches messages unless theNO_LOGSflag is set at compile time.DBG(...)dispatches a message only if theDEBUGflag is set at compile time.
These macros create and dispatch utilities::message objects, which hold an arbitrary user-supplied message. The instances also include source code location information.
The LOG and DBG macros automatically pass the correct source code location information to the message.
The message payload can be anything that works for the facilities in std::format.
The default message handler prints the source code location if there is no payload.
Warning
Microsoft's old traditional preprocessor is unhappy with some of our macros, but its newer, cross-platform-compatible one is fine.
Add the /Zc:preprocessor flag to use that upgrade at compile time.
Our CMake module compiler_init does that automatically for you.
The macros create and dispatch messages to a message handler immediately. The default handler prints the message and the location information to a default standard output stream. You can change that default stream, for example:
log_file = std::ofstream("log.txt");
utilities::message::stream = &log_file;For fancier usage, you can interpose your message handler.
void my_handler(const utilities::message& msg) { ... }
utilities::message::handler() = my_handler;Your handler can do whatever it likes with the messages it receives.
You can revert to the default handler by calling the class method utilities::message::use_default_handler().
Note
Everything here is deliberately rudimentary, with no consideration for rotating log files, etc., or for streaming efficiency.
The primary use case for the macros above and the underlying utilities::message class is easily printing useful messages during the development cycle.
The emphasis is on ease of use rather than great generality or speed.
During the development cycle, the typical output is going to be small anyway.
#include <utilities/log.h>
int add(int x, int y)
{
DBG("The DEBUG flag was set at compile time.");
LOG("x = {}, y = {}", x, y);
return x + y;
}
int main()
{
LOG(); // <1>
return add(10, 11);
}- A call to
LOGwithout any message will print the source location.
If that source was in a file called example.cpp, then the program will output:
[LOG] function 'main' (example.cpp, line 11)
[DBG] function 'add' (example.cpp, line 4): The DEBUG flag was set at compile time.
[LOG] function 'add' (example.cpp, line 5): x = 10, y = 11alises std::formatter for any type that has a to_string() method.
C++20 introduced std::format to create strings with interpolated values; a core feature for the std::print standard output library.
Moreover, you can extend the standard library for your own types by specialising the std::formatter class.
Many classes already implement a to_string() method that returns a string representation of an instance.
It seems like a good stopgap measure to connect all those types to std::format and its friends.
We have a concept that captures types with an accessible to_string(...) method that can be called without any arguments (of course, there can be lots of default arguments for the function):
template<typename T>
concept utilities::has_to_string = requires(const T& x) {
{ x.to_string() } -> std::convertible_to<std::string>;
};We then specialised std::formatter to work with any has_to_string compatible class.
#include <utilities/formatter.h>
#include <print>
struct Whatever {
std::string to_string() const { return "Whatever!"; }
};
int main()
{
Whatever w;
std::println("Struct with a `to_string()` method: '{}'", w);
return 0;
}Running that outputs:
Struct with a to_string() method: 'Whatever!'-
The
std::formatterspecialisation only works with default formatting. Sostd::println("{}", w)is fine, but something likestd::println("{:x}", w)will cause a program fault.
Yourto_stringmethod may handle various output options, but we only ever call it with no passed arguments. -
You can safely have a
to_string(...)method and provide your own specialisation ofstd::formatter.
The compiler will see your specialisation as a better fit than the blanket one provided in<utilities/formatter.h>.