In Jalog the use of macros for logging is recommended (though not required). The primary benefit of macros is that they allow you to perform checks at the call site. Thus, for example, if logging is disabled the arguments of the logging call won't be evaluated at all.
It is a good idea for users to create their own macros, suitable for their project and needs.
This is the "flagship" way of doing logging with Jalog: the macros JALOG
and JALOG_SCOPE
which were mentioned and used in the previous doc pages. JALOG
is implementd with JALOG_SCOPE
by providing the default scope as a first argument (see more on scopes here).
JALOG_SCOPE(scope, unscoped_level, ...)
JALOG(unscoped_level, ...) => JALOG_SCOPE(::jalog::Default_Scope, unscoped_level, ...)
Here unscoped_level
is one of Jalog's levels but without the need to specify ::jalog::Level
before that. This is done in the macro for easer writing and reading.
The rest of the arguments are concatenated to produce the log message in a way similar to JavaScript's console.log
(note however that console.log
in JS adds spaces between arguments, while Jalog does not). Thus all of these examples will produce the same log message:
JALOG(Info, "Hello, World!")
JALOG(Info, "Hello, ", "World!")
JALOG(Info, "Hel", "lo,", " Wor", "ld!")
As for the types of those arguments, Jalog offers build-in support for:
- C++ built-in numeric types (
int
,long
,float
...) bool
- printed astrue
orfalse
. If you want bools printed as integers, you can cast them at the call site.- Pointers - printed as hexadecimal values
char
- printed as a character- Strings -
const char*
,std::string
,std::string_view
- Integers in a specified base via
jalog::base<>
- Any other type which has a defined
operator <<(std::ostream&, T)
As mentioned above Jalog will log types which have defined output for std::ostream
. If you want to log your own custom type, defining operator <<
for it will work. However this is not the preferred method of defining custom output for Jalog. It's only there for backwards compatibility.
The problems with std::ostream
are many and have been discussed in various places with various detail. We will synthesize them to two:
- Performance -
std::ostream
is slow and relies on many indirections and inefficiencies to do basic things - Correctness -
std::ostream
is bad when it has to display floating point numbers. It will mangle them and lose precision sometimes terribly so. And it's hard to impossible to prevent it from doing so.
That's why the preferred way of adding Jalog output to a custom type is overloading operator, (jalog::BasicStream&, T)
(that's operator comma). Inside use operator comma for all the parts of the custom type you want to log, much like you would use operator <<
in a custom std::ostream
output function.
Here's an example:
struct Person
{
std::string name;
int age;
};
jalog::BasicStream& operator,(jalog::BasicStream& s, const Person& p)
{
return s, p.name, '(', p.age, ')';
}
In most cases this will be significantly faster than using std::ostream
(and never slower). It will also display floating-point numbers correctly.
Jalog also supports stream-style logging with operator<<
. Note that this is not done by inheriting from std::ostream
. Jalog provides its own class jalog::Stream
which has an overload for operator<<
.
The benefit of jalog::Stream
is that it can be used to compose a single log message from multiple lines of code. However this comes at the cost of being a bit slower to compile than the Jalog Style logging from above and, most notably, even if it is disabled at compile-time the arguments of its operator<<
(the values you log) will still be evaluated when you run the program, potentially making it a bit slower.
To use Stream Style logging with macros (which makes it easier to disable at compile time), you must include jalog/LogStream.hpp
. The the available macros are JALOG_STREAM(level)
and JALOG_STREAM_SCOPE(scope, level)
for custom scopes.
You can use them inline like this:
JALOG_STREAM(Info) << "Logging something: " << 343;
... but the more useful approach, which helps with multi-line logging, is to create a variable like so:
auto log = JALOG_STREAM(Info);
log << "Logging something: ";
log << 343 << jalog::endl;
log << jalog::Level::Error << "Now an error ";
log << "error continued" << jalog::endl;
Note two things from the examples above.
jalog::Stream
allows changing of the level, by simply "logging" the new desired level. After this the level in which new things are logged will be changed.jalog::Stream
will flush the message to the scope (and subsequently to sinks) if one of three conditions is met:- Its destructor is called
- You use
jalog::endl
- You change the level. In this case the contents of the log, if any, will be fluished for the old level
This allows users to create a single jalog::Stream
instance which can be used for log period (say the entire lifetime of the application)
A complete, buildable example, of Stream Style logging can be found here.
You can log custom types with jalog::Stream
the same you can with Jalog Style logging. Both operator<<(std::ostream&, T)
and operator, (jalog::BasicStream&, T)
are supported, and the operator,
approach is preferred if possible.
If you have no intention to disable stream logs in compile time, or if you have an alternative way of doing so (for example putting your Stream Style logging in preprocessor #if
-s) there is no need to use the JALOG_STREAM
macros and instead you can simply include jalog/Stream.hpp
and use the jalog::Stream
like an ordinary C++ class. You must provide a scope to its constructor (more on scopes here)
jalog::Stream log(jalog::Default_Scope);
log << jalog::Level::Info << "This works" << jalog::endl;
log << jalog::Level::Error << "So does this" << jalog::endl;
Jalog supports printf
style logging.
If you are a fan of printf
you can use the macros from jalog/LogPrintf.hpp
which are easy to disable at compile-time. Note however that printf
doesn't support custom types and also floating point numbers won't appear in their full native precision (but must be formatted with %f
or %e
).
The main goal of the printf
-like interface is to allow users to provide a function pointer to libraries (including plain C ones) which support external printf
-like log functions.
With jalog/Printf.hpp
Jalog provides the following functions:
void Printf(Scope& scope, Level lvl, const char* format, ...);
void VPrintf(Scope& scope, Level lvl, const char* format, va_list args);
It also provides the unchecked variants of those: PrintfUnchecked
and VPrintfUnchecked
. They have the same arguments as their "checked" counterparts but perform no check whether the scope is currently enabled for that level. You can use them if you have performed the check otherwise (for example the JALOG_PRINTF
macros perform the check externally and use the unchecked variants of the functions)
A complete, buildable example, of using printf
logging to interface with an imaginary C library can be found here.
Logging styles are essentially different ways in which one can produce a string. If you have a custom way of producing strings, it can also be used with Jalog.
jalog::Scope
provides two methods for this:
addEntry(level, string)
- log string if level is enabledaddEntryUnchecked(level, string)
- log string with level disregarding whether level is enabled. You can use this if you have performed the check otherwise.
Thus one can create a custom style of logging with Jalog with another string-producing library. A good candidate for this is C++20's std::format
.
A complete, buildable example, of custom logging with std::ostringstream
can be found here.