How to enable/disable spdlog logging in code? - c++

I am creating c++ library modules in my application. To do logging, I use spdlog. But in a production environment, I don't want my lib modules to do any logging. One way to achieve turning on/off would be to litter my code with #ifdef conditionals like...
#ifdef logging
// call the logger here.
#endif
I am looking for a way to avoid writing these conditionals. May be a wrapper function that does the #ifdef checking and write it. But the problem with this approach is that I have to write wrappers for every logging method (such as info, trace, warn, error, ...)
Is there a better way?

You can disable logging with set_level():
auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic.txt");
#if defined(PRODUCTION)
my_logger->set_level(spdlog::level::off);
#else
my_logger->set_level(spdlog::level::trace);
#endif
spdlog::register_logger(my_logger);

You can disable all logging before you compile the code by adding the following macro (before including spdlog.h):
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_OFF
#include<spdlog.h>
It is explained as a comment in the file https://github.com/gabime/spdlog/blob/v1.x/include/spdlog/spdlog.h :
//
// enable/disable log calls at compile time according to global level.
//
// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h):
// SPDLOG_LEVEL_TRACE,
// SPDLOG_LEVEL_DEBUG,
// SPDLOG_LEVEL_INFO,
// SPDLOG_LEVEL_WARN,
// SPDLOG_LEVEL_ERROR,
// SPDLOG_LEVEL_CRITICAL,
// SPDLOG_LEVEL_OFF
//
Using this macro will also speed up your productive code because the logging calls are completely erased from your code. Therefore this approach may be better than using my_logger->set_level(spdlog::level::off);
However, in order for the complete code removal to work you need to use either of the macros when logging:
SPDLOG_LOGGER_###(logger, ...)
SPDLOG_###(...)
where ### is one of TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL.
The latter macro uses the default logger spdlog::default_logger_raw(), the former can be used with your custom loggers. The variadic arguments ... stand for the regular arguments to your logging invocation: the fmt string, followed by some values to splice into the message.

I don't know spdlog.
However, you may define a macro in one of your common used include file, to replace the logcall by nothing, or a call to an empty inline function which the compiler optimizer will eliminate.
in "app.h"
#ifndef LOG
#ifdef logging
#define LOG spdlog
#endif
#ifndef logging
#define LOG noop
#endif
#endif
Did you get the idea?
This let most of your code untouched

Related

Technique to set trace level separately for every source file

I wonder, what is the best trace technique for you.
Currently, I am defining trace level for each source witch macro just before including trace header, which is using defined macro.
For example:
trace.h:
#if DEBUG == 0
#define debug(...)
#define trace(...)
#elif DEBUG==1
#define debug(...)
#define trace(...) printf(__VA_ARGS__)
#elif DEBUG==2
#define debug(...) printf(__VA_ARGS__)
#define trace(...) printf(__VA_ARGS__)
#else
#error Bad DEBUG value
#endif
For all sources.c (trace_value varies)
#define DEBUG trace_value
#include "trace.h"
void func(){
debug("func()");
trace("func() ok");
reurn;
}
However, project grows and I want to use precompiled header. It would be great to include trace header in my precompiled header. So I am wondering, what are your trace techniques? Thank you.
EDIT:
I forgot to write important thing, I am interested in logging technique for latency critical application.
Logging is a complex issue and what it does/how you use it depends greatly on the needs of your application.
In small applications, I tend to use either injected std::ostream references, or custom code for logging specifically. I also stay away from the C formatted printing APIs (and define my own operator<< for things I need to trace).
In large applications, if you have complex tracing needs (rotate log files between executions, logs per category and configurable from outside the application, automatic log formatting, high-throughput/performance logging, and so on) use an external library (like log4cpp).
I also tend to use/define macros, only after all my code can be written without them.
Example implementation with injected logging stream:
#include<iosfwd>
class http_server {
public:
http_server(std::string server, std::uint16_t listening_port,
std::ostream& log = cnull); // cnull is as defined at
// http://stackoverflow.com/a/6240980/186997
private:
std::ostream& log_;
};
Main (console) code:
int main(...) {
http_server("localhost", 8080, std::clog);
}
Unit test code:
std::ostringstream server_log;
http_server("localhost", 8080, server_log);
// assert on the contents of server_log
Basically, I consider that if I need tracing, it is a part of the API interface and not an afterthought, something I would disable through macros, or something to hardcode.
If I need formatted logging, I would consider specializing a std::ostream, or (most probably) wrapping it into a specialized formatting class and injecting that around.
Macros for tracing code are usually reserved for performance-critical code (where you cannot afford to call stuff if it doesn't do anything).

Conditional compilation of class

Normally, for classes I don't intend to include in production code I have conditional operators such as the usual:
#ifdef DEBUG_VERSION
This could also be around certain chunks of code that performs additional steps in development mode.
I've just thought (after many years or using the above): What happens if a typo is introduced in the above? It could have great consequences. Pieces of code included (or not included) when the opposite was intended.
So I'm now wondering about alternatives, and thought about creating 2 macro's:
INCLUDE_IN_DEBUG_BUILD
END_INCLUDE_IN_DEBUG_BUILD
If a typo is ever created in these, an error message is created at compile time, forcing the user to correct it. The first would evaluate to "if (1){" in the debug build and "if (0){" in the production build, so any compiler worth using should optimise those lines out, and even if they don't, at least the code inside will never be called.
Now I'm wondering: Is there something I'm missing here? Why does no-one else use something like this?
Update: I replaced the header-based approach with a build-system based approach.
You want to be able to disable not just part of the code inside a function, but maybe also in other areas like inside a class or namespace:
struct my_struct {
#ifdef DEBUG_VERSION
std::string trace_prefix;
#endif
};
So the real question seems to be: How to prevent typos in your #ifdefs? Here's something which does not limit you and which should work well.
Modify your build system to either define DEBUG_VERSION or RELEASE_VERSION. It should be easy to ensure this. Define those to nothing, e.g. -DDEBUG_VERSION or -DRELEASE_VERSION for GCC/Clang.
With this, you can protect your code like this:
#ifdef DEBUG_VERSION
DEBUG_VERSION
// ...
#endif
or
#ifndef DEBUG_VERSION
DEBUG_VERSION
// ...
#else
RELEASE_VERSION
// ...
#endif
And voila, in the second example above, I already added a small typo: #ifndef instead of #ifdef - but the compiler would complain now as DEBUG_VERSION and RELEASE_VERSION are not defined (as in "defined away" by the header) in the corresponding branches.
To make it as safe as possible, you should always have both branches with the two defines, so the first example I gave should be improved to:
#ifdef DEBUG_VERSION
DEBUG_VERSION
// ...
#else
RELEASE_VERSION
#endif
even if the release branch contains no other code/statements. That way you can catch most errors and I think it is quite descriptive. Since the DEBUG_VERSION is replaced with nothing only in the debug branch, all typos will lead to a compile-time error. The same for RELEASE_VERSION.

Debugging in C/C++: cost of calling an empty function

One normal procedure to implement debugging is the use of
#ifdef DEBUG
...debugging tests...
#endif
Sometimes, a typical "checking" algorithm is needed in some parts of the code, and so one would normally like to wrap it on a function, such that the code is only called when DEBUG flag is defined. Let that function be called myDebug();
I see two natural approaches for this: either ifdef is added on each time myDebug() is called, like this:
#ifdef DEBUG
myDebug();
#endif
Or myDebug is defined as:
void myDebug()
{
#ifdef DEBUG
...code...
#endif
}
Basically, the first avoids the function calling, while the second avoid putting the code with a lot of #ifdef/#endif.
Is there any "standard" convention to choose one of the two, or another way that I don't know? Is is worth to choose the second from the "styling" point of view?
Thanks
I'd go with number 3:
#ifdef DEBUG
#define myDebug(x) myDebug(x)
#else
#define myDebug(x) {}
#endif
This way, there's no pesky #ifdefs everywhere in the code, and for a non-debug build no code will be generated.

Remove function in Release build only

Is there any methods to remove a line of code from a Release build, but leave it in the Debug build without ugly #if statements?
For example, is there some way to achieve the equivalent of the below code without using all these if statements?
#if DEBUG
Log.Log("I am in debug mode");
#endif
If I have a conditional, run-time check in the Log.Log function, then the string "I am in debug mode" will be preserved within my compiled executable, which is exactly what I do not want.
Define another macro in a common, shared header.
#ifdef DEBUG
#define LOG(m) Log.Log(m);
#else
#define LOG(m) do {} while(false);
#endif
Then replace your calls to Log.Log with LOG.
You'll ultimately need a preprocessor conditional somewhere, but you could apply it "upstream" in some shared header if you want to keep your application code clean. In that case, you'd have something like
#if DEBUG
#define DebugLog(m) Log.Log(m);
#else
#define DebugLog(m)
#endif
in the header associated with Log, and instead of calling Log.Log(m) inside a preprocessor conditional in your application code, you'd just call DebugLog(m). In a Debug build, the macro would expand to Log.Log(m), but otherwise it would just disappear entirely.
Have your Log.Log function #ifdef'd based on the build. So in DEBUG, it logs stuff, and in RELEASE, it's a no-op. For example:
namespace Log
{
void Log()
{
#if DEBUG
//Insert logging code here.
#endif
}
}

Per-file enabling of scope guards

Here's a little problem I've been thinking about for a while now that I have not found a solution for yet.
So, to start with, I have this function guard that I use for debugging purpose:
class FuncGuard
{
public:
FuncGuard(const TCHAR* funcsig, const TCHAR* funcname, const TCHAR* file, int line);
~FuncGuard();
// ...
};
#ifdef _DEBUG
#define func_guard() FuncGuard __func_guard__( TEXT(__FUNCSIG__), TEXT(__FUNCTION__), TEXT(__FILE__), __LINE__)
#else
#define func_guard() void(0)
#endif
The guard is intended to help trace the path the code takes at runtime by printing some information to the debug console. It is intended to be used such as:
void TestGuardFuncWithCommentOne()
{
func_guard();
}
void TestGuardFuncWithCommentTwo()
{
func_guard();
// ...
TestGuardFuncWithCommentOne();
}
And it gives this as a result:
..\tests\testDebug.cpp(121):
Entering[ void __cdecl TestGuardFuncWithCommentTwo(void) ]
..\tests\testDebug.cpp(114):
Entering[ void __cdecl TestGuardFuncWithCommentOne(void) ]
Leaving[ TestGuardFuncWithCommentOne ]
Leaving[ TestGuardFuncWithCommentTwo ]
Now, one thing that I quickly realized is that it's a pain to add and remove the guards from the function calls. It's also unthinkable to leave them there permanently as they are because it drains CPU cycles for no good reasons and it can quickly bring the app to a crawl. Also, even if there were no impacts on the performances of the app in debug, there would soon be a flood of information in the debug console that would render the use of this debug tool useless.
So, I thought it could be a good idea to enable and disable them on a per-file basis.
The idea would be to have all the function guards disabled by default, but they could be enabled automagically in a whole file simply by adding a line such as
EnableFuncGuards();
at the top of the file.
I've thought about many a solutions for this. I won't go into details here since my question is already long enough, but let just say that I've tried more than a few trick involving macros that all failed, and one involving explicit implementation of templates but so far, none of them can get me the actual result I'm looking for.
Another restricting factor to note: The header in which the function guard mechanism is currently implemented is included through a precompiled header. I know it complicates things, but if someone could come up with a solution that could work in this situation, that would be awesome. If not, well, I certainly can extract that header fro the precompiled header.
Thanks a bunch in advance!
Add a bool to FuncGuard that controls whether it should display anything.
#ifdef NDEBUG
#define SCOPE_TRACE(CAT)
#else
extern bool const func_guard_alloc;
extern bool const func_guard_other;
#define SCOPE_TRACE(CAT) \
NppDebug::FuncGuard npp_func_guard_##__LINE__( \
TEXT(__FUNCSIG__), TEXT(__FUNCTION__), TEXT(__FILE__), \
__LINE__, func_guard_##CAT)
#endif
Implementation file:
void example_alloc() {
SCOPE_TRACE(alloc);
}
void other_example() {
SCOPE_TRACE(other);
}
This:
uses specific categories (including one per file if you like)
allows multiple uses in one function, one per category or logical scope (by including the line number in the variable name)
compiles away to nothing in NDEBUG builds (NDEBUG is the standard I'm-not-debugging macro)
You will need a single project-wide file containing definitions of your category bools, changing this 'settings' file does not require recompiling any of the rest of your program (just linking), so you can get back to work. (Which means it will also work just fine with precompiled headers.)
Further improvement involves telling the FuncGuard about the category, so it can even log to multiple locations. Have fun!
You could do something similar to the assert() macro where having some macro defined or not changes the definition of assert() (NDEBUG in assert()'s case).
Something like the following (untested):
#undef func_guard
#ifdef USE_FUNC_GUARD
#define func_guard() NppDebug::FuncGuard __npp_func_guard__( TEXT(__FUNCSIG__), TEXT(__FUNCTION__), TEXT(__FILE__), __LINE__)
#else
#define func_guard() void(0)
#endif
One thing to remember is that the include file that does this can't have include guard macros (at least not around this part).
Then you can use it like so to get tracing controlled even within a compilation unit:
#define USE_FUNC_GUARD
#include "funcguard.h"
// stuff you want traced
#undef USE_FUNC_GUARD
#include "funcguard.h"
// and stuff you don't want traced
Of course this doesn't play 100% well with pre-compiled headers, but I think that subsequent includes of the header after the pre-compiled stuff will still work correctly. Even so, this is probably the kind of thing that shouldn't be in a pre-compiled header set.