After a long time I started doing some C++ development again. Right now I'm struggling with my logging class. It's already working quite nicely, but I want to introduce some log levels. To be honest, I'm not quite sure how to continue in the right way.
My logger works with five simple macros:
#define PLUGINLOG_INIT(path, fileName) logInstance.initialise(path, fileName);
#define LOG_ERROR (logInstance << logInstance.prefix(error))
#define LOG_WARN (logInstance << logInstance.prefix(warn))
#define LOG_INFO (logInstance << logInstance.prefix(info))
#define LOG_DEBUG (logInstance << logInstance.prefix(debug))
The first one opens the file stream and the other four write log entries into the file. It's a rather simple approach. The prefix methods writes a datetime stamp and the log level as text:
2021-05.26 12:07:23 WARN File not found!
For the log level, I created an enum and store the current log level in my class. My questions is however, how can I avoid logging if the log level is set lower?
Example:
LogLevel in class = warn
When I log an info entry, I'd put the following line into my source code:
LOG_INFO << "My info log entry" << std::endl;
Since the LogLevel is set to warning, this info entry should not be logged. I could try putting an if statement into the LOG_INFO macro, but I would rather avoid complicated macros. Is there a better way to achieve what I need?
Many thanks,
Marco
Complete header file of my logger:
#ifndef PLUGINLOGGER_H_
#define PLUGINLOGGER_H_
// Standard
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
// Forward declarations
class PluginLogger;
extern PluginLogger logInstance;
// Enumerations
enum LogLevel {
error = 0,
warn = 1,
info = 2,
debug = 3
};
// Macros
#define PLUGINLOG_INIT(path, fileName) logInstance.initialise(path, fileName);
#define LOG_ERROR (logInstance << logInstance.prefix(error))
#define LOG_WARN (logInstance << logInstance.prefix(warn))
#define LOG_INFO (logInstance << logInstance.prefix(info))
#define LOG_DEBUG (logInstance << logInstance.prefix(debug))
class PluginLogger {
public:
PluginLogger();
void initialise(std::string_view path, std::string_view fileName);
void close();
template<typename T> PluginLogger& operator<<(T t);
// to enable std::endl
PluginLogger& operator<<(std::ostream& (*fun) (std::ostream&));
std::string prefix(const LogLevel logLevel);
private:
std::string m_fileName;
std::ofstream m_stream;
LogLevel m_logLevel;
};
template<typename T> inline PluginLogger& PluginLogger::operator<<(T t) {
if (m_stream.is_open())
m_stream << t;
return* this;
}
inline PluginLogger& PluginLogger::operator<<(std::ostream& (*fun)( std::ostream&)) {
if (m_stream.is_open())
m_stream << std::endl;
return* this;
}
#endif // PLUGINLOGGER_H_
Complete source file of my logger:
#include <chrono>
#include "PluginLogger.h"
PluginLogger logInstance;
PluginLogger::PluginLogger() {
m_fileName = "";
m_logLevel = error;
}
void PluginLogger::initialise(std::string_view path, std::string_view fileName) {
if (!path.empty() && !fileName.empty()) {
m_fileName = std::string(path) + std::string(fileName) + "_log.txt";
m_stream.open(m_fileName);
unsigned char bom[] = { 0xEF,0xBB,0xBF };
m_stream.write((char*)bom, sizeof(bom));
}
}
void PluginLogger::close() {
if (m_stream.is_open())
m_stream.close();
}
std::string PluginLogger::prefix(const LogLevel logLevel) {
// add a date time string
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::string dateTimeStr(25, '\0');
std::strftime(&dateTimeStr[0], dateTimeStr.size(), "%Y-%m-%d %H:%M:%S", std::localtime(&now));
// add log level
std::string logLevelText;
switch (logLevel) {
case error:
logLevelText = " ERROR ";
break;
case warn:
logLevelText = " WARN ";
break;
case info:
logLevelText = " INFO ";
break;
case debug:
logLevelText = " DEBUG ";
break;
}
return dateTimeStr + logLevelText;
}
Here is the code:
void DBG() { cerr << "]" << endl; }
template<class H, class... T> void DBG(H h, T... t) {
cerr << to_string(h); if (sizeof...(t)) cerr << ", ";
DBG(t...); }
#ifdef _DEBUG
#define dbg(...) cerr << "LINE(" << __LINE__ << ") -> [" << #__VA_ARGS__ << "]: [", DBG(__VA_ARGS__)
#else
#define dbg(...) 0
#endif
Im currently using g++ on vs code on a mac and whenever I run my program using this and for example
I write dbg(10) on main the program does not even run and does not do anything(it outputs nothing).
How can I change this code so it would work on g++ without having to download clang?
Note: I saw some people use #ifdef LOCAL instead of #ifdef _DEBUG but it still doesn't work for me(does not output anything). How do I get #ifdef LOCAL or #ifdef _DEBUG to work on g++?
I'm trying to use a macro to queue up a single log line locally in an ostringstream and then dump the entire contents of that ostringstream when the line is over. I want to still use stream insert syntax, however. So I want to turn a log line like this:
std::cerr << "Some error in my function. Code is " << errCode << " exiting" << std::endl;
...into this
SERR("Some error in my function. Code is " << errCode << " exiting);
I've got something simple that works well. That is, until I put it in an if-else statement. Obviously my macro is bad but I am at a loss as to what to do.
Here is a small sample program, hacked up to illustrate the problem.:
#include <iostream>
#include <sstream>
#define SERR(x) { std::ostringstream _s; ; _s << x << std::endl; std::cerr << _s.str(); }
int main()
{
std::cout << "Hello World!\n";
bool val = false;
if (val)
SERR("No error");
else
SERR("Error");
}
The error message I get from the compiler in this sample is:
1>c:\users\joe\source\repos\consoleapplication5\consoleapplication5\consoleapplication5.cpp(15):
error C2181: illegal else without matching if
Any ideas what I've got wrong here?
(Note I'm not in a place where I can use a 3rd Party logging solution now so it's got to be something simple like this. I could just leave all this as normal std::cerr/std::cout/std::clog messages, if I had to but I'd prefer something simple to minimize the chances of interleaving of log messages from my multithreaded app.)
Just try to expand it and see what you get:
#include <iostream>
#include <sstream>
int main()
{
std::cout << "Hello World!\n";
bool val = false;
if (val)
{ std::ostringstream _s; ; _s << "No error" << std::endl; std::cerr << _s.str(); };
else
{ std::ostringstream _s; ; _s << "Error" << std::endl; std::cerr << _s.str(); };
}
Notice how { } block is terminated with ;?
If you need a macro, you should always write it using do { } while (0) like this:
#define SERR(x) \
do { \
std::ostringstream _s; \
_s << (x) << std::endl; \
std::cerr << _s.str(); \
} while (0)
Not only this solves your issue, but also make it mandatory to add ; after the macro. Otherwise people could use it two ways:
SERR("foo") // without ;
...
SERR("bar"); // with ;
Is there easily embeddable C++ test lib with a friendly license? I would like a single header file. No .cpp files, no five petabytes of includes. So CppUnit and Boost.Test are out.
Basically all I want is to drop single file to project tree, include it and be able to write
testEqual(a,b)
and see if it fail. I'd use assert, but it doesn't work in non-debug mode and can't print values of a and b, and before rewriting assert I'd rather search existing library.
I'm tempted to say "write your own", which is what I have done. On the other hand, you might want to reuse what I wrote: test_util.hpp and test_util.cpp. It is straightforward to inline the one definition from the cpp file into the hpp file. MIT lisence. I have also pasted it into this answer, below.
This lets you write a test file like this:
#include "test_util.hpp"
bool test_one() {
bool ok = true;
CHECK_EQUAL(1, 1);
return ok;
}
int main() {
bool ok = true;
ok &= test_one();
// Alternatively, if you want better error reporting:
ok &= EXEC(test_one);
// ...
return ok ? 0 : 1;
}
Browse around in the tests directory for more inspiration.
// By Magnus Hoff, from http://stackoverflow.com/a/9964394
#ifndef TEST_UTIL_HPP
#define TEST_UTIL_HPP
#include <iostream>
// The error messages are formatted like GCC's error messages, to allow an IDE
// to pick them up as error messages.
#define REPORT(msg) \
std::cerr << __FILE__ << ':' << __LINE__ << ": error: " msg << std::endl;
#define CHECK_EQUAL(a, b) \
if ((a) != (b)) { \
REPORT( \
"Failed test: " #a " == " #b " " \
"(" << (a) << " != " << (b) << ')' \
) \
ok = false; \
}
static bool execute(bool(*f)(), const char* f_name) {
bool result = f();
if (!result) {
std::cerr << "Test failed: " << f_name << std::endl;
}
return result;
}
#define EXEC(f) execute(f, #f)
#endif // TEST_UTIL_HPP
Try google-test https://github.com/google/googletest/
it's really light weight, cross platform and simple.
Is there a way to define/undefine debug messages using std::cout whenever inside a program?
I am aware that there are such things such as #define, #ifndef, but I was thinking is there a cleaner way to having a variable say:
# debug ON
That prints all of my debug data (using std::cout). Consequently, we'll have code like this for debug:
#ifndef DEBUG
// do something useful
#endif
I find the above code cumbersome when you write 100s of debug code.
Thanks!
Carlo
#ifdef DEBUG
#define DEBUG_MSG(str) do { std::cout << str << std::endl; } while( false )
#else
#define DEBUG_MSG(str) do { } while ( false )
#endif
int main()
{
DEBUG_MSG("Hello" << ' ' << "World!" << 1 );
return 0;
}
Some logging libraries are pretty heavy weight unless you have complex logging needs. Here's something I just knocked together. Needs a little testing but might meet your requirements:
#include <cstdio>
#include <cstdarg>
class CLog
{
public:
enum { All=0, Debug, Info, Warning, Error, Fatal, None };
static void Write(int nLevel, const char *szFormat, ...);
static void SetLevel(int nLevel);
protected:
static void CheckInit();
static void Init();
private:
CLog();
static bool m_bInitialised;
static int m_nLevel;
};
bool CLog::m_bInitialised;
int CLog::m_nLevel;
void CLog::Write(int nLevel, const char *szFormat, ...)
{
CheckInit();
if (nLevel >= m_nLevel)
{
va_list args;
va_start(args, szFormat);
vprintf(szFormat, args);
va_end(args);
}
}
void CLog::SetLevel(int nLevel)
{
m_nLevel = nLevel;
m_bInitialised = true;
}
void CLog::CheckInit()
{
if (!m_bInitialised)
{
Init();
}
}
void CLog::Init()
{
int nDfltLevel(CLog::All);
// Retrieve your level from an environment variable,
// registry entry or wherecer
SetLevel(nDfltLevel);
}
int main()
{
CLog::Write(CLog::Debug, "testing 1 2 3");
return 0;
}
Probably not. I would recommend using a logging library. I'm not sure what the best option is for C++ anymore, but I've used log4cpp in the past and found it pretty good.
EDIT: I assume on the fly means # runtime. If you just need it to be a compile time flag, then Gianni's answer is probably easiest to implement. Logging libraries give you a lot of flexibility and allow reconfiguration # runtime though.
Another simple solution, involves opening a std::ostream reference to cout in debug mode, and /dev/null in non-debug mode, like so:
In debug.h:
extern std::ostream &dout;
In debug.c
#ifdef DEBUG
std::ostream &dout = cout;
#else
std::ofstream dev_null("/dev/null");
std::ostream &dout = dev_null;
#endif
And then:
dout << "This is a debugging message";
Of course, this would only work on any system where /dev/null points to a null device. Since the dout reference is global here, it would much like cout. In this way, you can point the same stream to multiple output streams, for example to a log file, depending of the value of debug flags, etc.
Although the question is old, and there are some good answers, i want to post also a solution to this. It is like Giannis approach but different. And also, i used std::cerr instead of std::cout, but you can change this really quick.
#include <iostream>
#ifdef DEBUG
# define DEBUG_LOG std::cerr
#else
class log_disabled_output {};
static log_disabled_output log_disabled_output_instance;
template<typename T>
log_disabled_output& operator << (log_disabled_output& any, T const& thing) { return any; }
// std::endl simple, quick and dirty
log_disabled_output& operator << (log_disabled_output& any, std::ostream&(*)(std::ostream&)) { return any; }
# define DEBUG_LOG log_disabled_output_instance
#endif
int main() {
int x=0x12345678;
DEBUG_LOG << "my message " << x << " " << "\n more information" << std::endl;
};
Now you can use it just like a output stream.
(Note: iostream is only included if cerr is used. This will reduce the amount of inclusion if you don't have it already included. -edit: not with std::endl support).
If DEBUG is defined cerr is used to print the error. Otherwise the dummy class log_disabled_output is instantiated statically and operator<< is overloaded to any type. The vantage is; If you disable the logging, a clever compiler will notice that there is nothing to do with the stream and optimize the entire "line" away, so you don't have any overhead if DEBUG is disabled.
I was trying to do the same thing. After some research, I developed the following, and it seems to work. Please comment if you see anything wrong.
ostream DbgMsg(NULL);
enum {
DBGMSG_NONE,
DBGMSG_DEFAULT,
DBGMSG_VERBOSE
} DbgLvl = DBGMSG_DEFAULT;
ostream &DbgMsgDefault(ostream &stream) {
return (DbgLvl>=DBGMSG_DEFAULT) ? cout : stream;
}
ostream &DbgMsgVerbose(ostream &stream) {
return (DbgLvl>=DBGMSG_VERBOSE) ? cout : stream;
}
void main() {
DbgMsg<<DbgMsgDefault<<"default:default"<<endl;
DbgMsg<<DbgMsgVerbose<<"default:verbose"<<endl;
DbgLvl = DBGMSG_NONE;
DbgMsg<<DbgMsgDefault<<"none:default"<<endl;
}
A clean thing to do would be to use cerr.
"cerr" acts essentially as "cout", but always flushes the output (useful for debugging, by the way). If you need to remove all the messages, you can comment out all the cerr messages with a simple find-and-replace (cerr into //cerr).
There are probably even better ways to use cerr and to desactivate it cleanly (which writes into a special stream, the error stream, hence the name).
I hope this helps.
This is what I used (worked with VC++) - here "##" is used for concatennation
#ifdef DEBUG
#define pout cout
#else
#define pout / ## / cout
#endif
For other compilers use this :
#ifdef DEBUG
#define pout cout
#else
#define pout 0 && cout
#endif
Usage :
pout << "hello world" << endl;
I was looking for similar example and sharing my example below:
#include <iostream>
enum debug_option
{
DEBUG_DISABLE,
DEBUG_ENABLE
};
class debug
{
public:
debug_option debug_state;
debug() : debug_state(DEBUG_ENABLE) {} // constr
debug(debug_option state) : debug_state(state) {} // constr
template<typename T>
debug & operator<< (T input)
{
if (this->debug_state == DEBUG_ENABLE)
std::cout << input;
return *this;
}
};
int main()
{
debug log, log_lev2(DEBUG_DISABLE);
log << "print 1..\n" << 55 << " over\n";
log.debug_state = DEBUG_DISABLE;
log << "print 2..\n" << 3 << "over\n";
log_lev2 << "print 3..\n" << 4 << "over\n";
log_lev2.debug_state = DEBUG_ENABLE;
log_lev2 << "print 5..\n";
std::cout << "std::cout << print..\n";
return 0;
}
Better suggestions are always welcome.