Simple Logger in C++ with log level - c++

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;
}

Related

How to pass boost::log::expressions::smessage to nlohmann::json constructor?

I have a code similar to this:
const auto jsonFormatter = boost::log::expressions::stream << boost::log::expressions::smessage;
I want to escape the message using nlohmann::json, something like:
nlohmann::json json{boost::log::expressions::smessage};
I can do following to convert boost::log::expressions::smessage to std::string:
std::stringstream ss;
ss << boost::log::expressions::smessage;
std::string message = ss.str();
nlohmann::json json{message};
, but I need to put it inside the formatter, because the
const auto jsonFormatter = boost::log::expressions::stream << nlohmann::json{boost::log::expressions::smessage};
can't convert the boost::log::expressions::smessage argument to any nlohmann::json constructor.
Any suggestions how to make it work?
Log formatters look like normal C++, but they are expression templates that compose deferred calleable that do the corresponding action.
Here's how you can make a wrapper expression that knows how to do this:
namespace {
struct as_json_t {
template <typename E> auto operator[](E fmt) const {
return expr::wrap_formatter(
[fmt](logging::record_view const& rec,
logging::formatting_ostream& strm) {
logging::formatting_ostream tmp;
std::string text;
tmp.attach(text);
fmt(rec, tmp);
strm << nlohmann::json{text};
});
}
};
inline constexpr as_json_t as_json;
} // namespace
Now you can make your formatter e.g.
logging::formatter formatter = expr::stream
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< logging::trivial::severity
<< " - " << as_json[expr::stream << expr::smessage]
;
ANd the result is e.g.
2021-01-15, 23:34:08.489173 error - ["this is an error message"]
Live Demo
Live On Wandbox
File simpleLogger.h
#ifndef _HOME_SEHE_PROJECTS_STACKOVERFLOW_SIMPLELOGGER_H
#define _HOME_SEHE_PROJECTS_STACKOVERFLOW_SIMPLELOGGER_H
#pragma once
#define BOOST_LOG_DYN_LINK \
1 // necessary when linking the boost_log library dynamically
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/trivial.hpp>
// the logs are also written to LOGFILE
#define LOGFILE "logfile.log"
// just log messages with severity >= SEVERITY_THRESHOLD are written
#define SEVERITY_THRESHOLD logging::trivial::warning
// register a global logger
BOOST_LOG_GLOBAL_LOGGER(logger, boost::log::sources::severity_logger_mt<
boost::log::trivial::severity_level>)
// just a helper macro used by the macros below - don't use it in your code
#define LOG(severity) \
BOOST_LOG_SEV(logger::get(), boost::log::trivial::severity)
// ===== log macros =====
#define LOG_TRACE LOG(trace)
#define LOG_DEBUG LOG(debug)
#define LOG_INFO LOG(info)
#define LOG_WARNING LOG(warning)
#define LOG_ERROR LOG(error)
#define LOG_FATAL LOG(fatal)
#endif
File simpleLogger.cpp
#include "simpleLogger.h"
#include <boost/core/null_deleter.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/expressions/formatters/char_decorator.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <nlohmann/json.hpp>
#include <fstream>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)
namespace {
struct as_json_t {
template <typename E> auto operator[](E fmt) const {
return expr::wrap_formatter(
[fmt](logging::record_view const& rec,
logging::formatting_ostream& strm) {
logging::formatting_ostream tmp;
std::string text;
tmp.attach(text);
fmt(rec, tmp);
strm << nlohmann::json{text};
});
}
};
inline constexpr as_json_t as_json;
} // namespace
BOOST_LOG_GLOBAL_LOGGER_INIT(logger, src::severity_logger_mt) {
src::severity_logger_mt<boost::log::trivial::severity_level> logger;
// add attributes
logger.add_attribute("TimeStamp", attrs::local_clock()); // each log line gets a timestamp
// add a text sink
using text_sink = sinks::synchronous_sink<sinks::text_ostream_backend>;
boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();
// add a logfile stream to our sink
sink->locked_backend()->add_stream(
boost::make_shared<std::ofstream>(LOGFILE));
// add "console" output stream to our sink
sink->locked_backend()->add_stream(
boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter()));
// specify the format of the log message
logging::formatter formatter = expr::stream
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< logging::trivial::severity
<< " - " << as_json[expr::stream << expr::smessage]
;
sink->set_formatter(formatter);
// only messages with severity >= SEVERITY_THRESHOLD are written
sink->set_filter(severity >= SEVERITY_THRESHOLD);
// "register" our sink
logging::core::get()->add_sink(sink);
return logger;
}
File test.cpp
#include "simpleLogger.h"
int main() {
LOG_TRACE << "this is a trace message";
LOG_DEBUG << "this is a debug message";
LOG_WARNING << "this is a warning message";
LOG_ERROR << "this is an error message";
LOG_FATAL << "this is a fatal error message";
return 0;
}
Prints
2021-01-15, 23:50:03.130250 warning - ["this is a warning message"]
2021-01-15, 23:50:03.130327 error - ["this is an error message"]
2021-01-15, 23:50:03.130354 fatal - ["this is a fatal error message"]
In addition to sehe's answer, you could achieve the JSON-like format using Boost.Log components. The essential part is the c_decor character decorator, which ensures that its output can be used as a C-style string literal.
namespace expr = boost::log::expressions;
const auto jsonFormatter =
expr::stream << "[\""
<< expr::c_decor[ expr::stream << expr::smessage ]
<< "\"]";
First, c_decor will escape any control characters in the messages to C-style escape sequences, like \n, \t. It will also escape double quote characters. Then, the surrounding brackets and double quotes are added to make the output compatible with JSON format.
If you have non-ASCII characters in your log messages and you want the formatted log records to be strictly ASCII, you can use c_ascii_decor instead of c_decor. In addition to what c_decor does, it will also escape any bytes greater than 127 to their hex escape sequences, e.g. \x8c.
I really appreciate help and the other answers, which may be better in other cases, but long story short, I tried a lot of solutions and went with this one in the end (I can update it if it can be improved):
#include <boost/phoenix/bind/bind_function.hpp>
..
nlohmann::json EscapeMessage(
boost::log::value_ref<std::string, boost::log::expressions::tag::smessage> const& message)
{
return message ? nlohmann::json(message.get()) : nlohmann::json();
}
..
const auto jsonFormatter = boost::log::expressions::stream << boost::phoenix::bind(&EscapeMessage, boost::log::expressions::smessage.or_none())
boost::log::add_console_log(std::cout, boost::log::keywords::format = jsonFormatter);

std::ofstream not creating file when called from constructor from global object

To begin - the code as written works in Linux, but when building and running on Windows the log file is not created.
I've created a simple Logging library and that should create a file local to the executable.
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
namespace Logger
{
enum class Level
{
DEBUG,
INFO,
WARNING,
ERROR
};
std::string GetTime();
class Log
{
private:
std::string filename;
std::stringstream inStr;
std::ofstream logFile;
std::recursive_mutex inStrMutex;
std::mutex fileMutex;
void ClearStrStream();
std::string LevelToString(Level lvl);
public:
Log(std::string file);
~Log();
template<typename T>
void Write(Level lvl, const T& arg)
{
std::lock_guard<std::recursive_mutex> inStrLock(inStrMutex);
std::lock_guard<std::mutex> fileLock(fileMutex);
inStr << std::noskipws << arg << '\n';
logFile << std::noskipws << "[" << GetTime() << "]"
<< "[" << LevelToString(lvl) << "] "
<< inStr.str();
ClearStrStream();
return;
}
template<typename T, typename... Args>
void Write(Level lvl, const T& firstArg, Args... args)
{
std::lock_guard<std::recursive_mutex> lock(inStrMutex);
inStr << std::noskipws << firstArg;
Write(lvl, args...);
return;
}
};
}
#endif
The constructor defined in Logger.cpp
Logger::Log::Log(std::string file) : filename{ file }
{
std::cout << "Ctor called\n";
auto now = Logger::GetTime();
auto fullName = now + "_" + filename;
logFile = std::ofstream(fullName);
inStr = std::stringstream("", std::ios_base::ate | std::ios_base::out | std::ios_base::in);
}
This gets built into a static library and then linked in LoggerUnitTest.cpp
#include <chrono>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <functional>
#include "Logger.h"
#include "LoggerConfig.h"
#include "date.h"
Logger::Log GLogObject("LoggerTest.log");
void WriteToLogger()
{
GLogObject.Write(Logger::Level::WARNING,
"The quick brown ",
"fox jumps ",
"over the ",
"lazy ",
"dog.");
GLogObject.Write(Logger::Level::ERROR,
"Because nighttime ",
"is the best time ",
"to fight crime.");
return;
}
void TestMultipleTypes()
{
GLogObject.Write(Logger::Level::DEBUG,
"String ",
123,
" and integer.");
return;
}
int main()
{
std::cout << "Testing Logger Version: " << LOGGER_VERSION_MAJOR << "."
<< LOGGER_VERSION_MINOR << std::endl;
std::thread t1(TestMultipleTypes);
std::thread t2(WriteToLogger);
WriteToLogger();
std::thread t3(WriteToLogger);
WriteToLogger();
std::thread t4(WriteToLogger);
std::thread t5(WriteToLogger);
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
return 0;
}
When LoggerUnitTest is built and run in Linux, things work as expected. A log file is created and everything is written into the log file as expected.
When LoggerUnitTest is build and run in Windows, LoggerUnitTest.exe runs, but no log file is created. What's different about Windows that results in no file being written?
I've also attempted to explicitly add std::flush in Logger's Write function which doesn't seem to make a difference.
The issue was with the way GetTime() returned a date/time formatted string. The formate was YYYY/mm/DD_HH:MM:SS. According to Microsoft Docs, colons : and forward slashes / are prohibited characters in file names. After adjusting the format of the date/time, everything works as expected!

Boost.Log change filter per channel with unknown channel names

in Boost.Log I want to set filters based on channels. Using this example I implemented using text_file_backend. But in my program, channel names are given by user as input argument. So I decided to implement a method that set severity filter for channel.
commons.h
#ifndef COMMONS_H_
#define COMMONS_H_
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/common.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/sinks/syslog_backend.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/thread/thread.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace sinks = boost::log::sinks;
namespace expr = boost::log::expressions;
namespace keywords = boost::log::keywords;
enum severity_levels
{
normal,
notification,
warning,
error,
critical
};
// Define the attribute keywords
BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_levels)
BOOST_LOG_ATTRIBUTE_KEYWORD(channel, "Channel", std::string)
typedef expr::channel_severity_filter_actor< std::string, severity_levels >
min_severity_filter;
typedef src::severity_channel_logger_mt< severity_levels, std::string >
logger_type_mt;
BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(test_lg, logger_type_mt)
typedef sinks::synchronous_sink< sinks::text_file_backend > File_sink;
#define ADD_LOG(severity, channel, msg, args...) add_log_message(__FILE__, __LINE__, severity, channel, boost::this_thread::get_id(), msg, args)
#define MY_GLOBAL_LOGGER(log_, channel, sv, file, line, thread) BOOST_LOG_CHANNEL_SEV( log_, channel, sv) \
<< boost::log::add_value("Line", line) \
<< boost::log::add_value("File", file) \
<< boost::log::add_value("Thread_id", thread)
std::ostream& operator<< (std::ostream& strm, severity_levels level)
{
static const char* strings[] =
{
"normal",
"notification",
"warning",
"error",
"critical"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
#endif
Logger.h
#ifndef LOGGER_H_
#define LOGGER_H_
#define BOOST_LOG_DYN_LINK 1
#include <string.h>
#include <ctime>
#include <chrono>
#include "commons.h"
class Logger
{
public:
static min_severity_filter min_severity;
static void init_logging()
{
logging::add_common_attributes();
// Create a text file sink
boost::shared_ptr< sinks::text_file_backend> backend(new sinks::text_file_backend());
backend->set_file_name_pattern<std::string>("sample_%N.log");
backend->set_rotation_size(2 * 1024 * 1024);
boost::shared_ptr< File_sink > sink(new File_sink(backend));
// Set up where the rotated files will be stored
init_file_collecting <File_sink>(sink);
sink->set_formatter(&file_log_formatter);
sink->locked_backend()->scan_for_files();
logging::core::get()->add_sink(sink);
logging::core::get()->set_filter(min_severity || severity >= normal);
}
template <typename T>
static void init_file_collecting(boost::shared_ptr< T > sink)
{
sink->locked_backend()->set_file_collector(sinks::file::make_collector(
keywords::target = "logs", /*< the target directory >*/
keywords::max_size = 64 * 1024 * 1024, /*< maximum total size of the stored files, in bytes >*/
keywords::min_free_space = 100 * 1024 * 1024 /*< minimum free space on the drive, in bytes >*/
));
}
static void file_log_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
// Get the LineID attribute value and put it into the stream
strm << logging::extract< unsigned int >("LineID", rec) << ": ";
// TimeStamp
strm << "[";
strm << logging::extract<boost::posix_time::ptime>("TimeStamp", rec);
strm << "]";
// thread id
strm << "[" << logging::extract< boost::thread::id >("Thread_id", rec) << "] ";
strm << "[" << rec[channel] << "] ";
strm << "[";
strm << logging::extract< int >("Line", rec) << ", ";
logging::value_ref< std::string > fullpath = logging::extract< std::string >("File", rec);
strm << boost::filesystem::path(fullpath.get()).filename().string() << "] ";
// The same for the severity level.
// The simplified syntax is possible if attribute keywords are used.
strm << "<" << rec[severity] << "> ";
// Finally, put the record message to the stream
strm << rec[expr::smessage];
}
static void set_channel_filter(std::string channel, severity_levels min_level)
{
min_severity[channel] = min_level;
logging::core::get()->set_filter(min_severity);
}
static void add_log_message(const char* file, int line, severity_levels severity,
std::string channel, boost::thread::id thread_id,
const char* message, ...)
{
char buffer[256];
va_list ap;
va_start(ap, message);
vsnprintf(buffer, 256, message, ap);
MY_GLOBAL_LOGGER(test_lg::get(), channel, severity, file, line, thread_id) << buffer;
va_end(ap);
}
};
#endif
min_severity_filter Logger::min_severity = expr::channel_severity_filter(channel, severity);
At the first of program by calling init_logging() filter for all channels is set to normal.
the problem is when I invoke set_channel_filter() with some input (e.g. "CHANNEL_1", warning), I expect setting filter only for "CHANNEL_1", But filtering is set for all possible channels. (e.g. "CHANNEL_2, etc). When I add for example "OTHER_CHANNEL" manually to set_channel_filter() it works for it. I want to have c++ map like data structure saving all severity filter per channel. and anytime user invoke to add a new or existing channel with a filter, it just change filtering for that particular channel, not for all.
main.cpp
int main()
{
Logger::init_logging();
Logger::ADD_LOG(severity_levels::normal, "NETWORK", "a warning message with id %d", 34);
Logger::ADD_LOG(severity_levels::notification, "NETWORK", "a warning message with id %d", 65);
Logger::ADD_LOG(severity_levels::notification, "GENERAL", "a notification message with id %d", 12);
Logger::ADD_LOG(severity_levels::warning, "GENERAL", "a warning message with id %d", 13);
// Logs in GENERAL category must have severity level of warning or higher in-order to record.
Logger::set_channel_filter("GENERAL", severity_levels::warning);
Logger::ADD_LOG(severity_levels::notification, "GENERAL", "a notification message with id %d", 14);
for (int i = 0; i < 2; i++)
{
Logger::ADD_LOG(severity_levels::warning, "GENERAL", "a warning message with id %d", 15);
}
Logger::ADD_LOG(severity_levels::normal, "NETWORK", "a warning message with id %d", 34); // Expected to sent to file. but filtered!!
Logger::ADD_LOG(severity_levels::notification, "NETWORK", "a warning message with id %d", 65); //Also filtered !!
}
The problem is that initially, in init_logging, you set the filter as follows:
logging::core::get()->set_filter(min_severity || severity >= normal);
At this point, min_severity is empty, as you haven't added any channels/severity thresholds. By default, min_severity will return false when a channel is not found in the channel/severity mapping, which means the filter you set here is effectively severity >= normal.
Later, when you call set_channel_filter, you add the first entry to the min_severity mapping, so that it works for the "GENERAL" channel but not for any other. However, you set a different filter this time:
logging::core::get()->set_filter(min_severity);
This time, if min_severity returns false the log record is discarded, which is what happens with the last two records in the "NETWORK" channel. You need to set the same filter expression every time to have a consistent behavior.

Boost log sink - Timestamp attribute and formatter problem

I have implemented a sink which logs records to a std::vector and flushes all logs to console when the flush function is called. I have added four attributes according to:
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::log::attributes::local_clock::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", boost::log::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", std::string)
However, when I use the set_formatter function I get an compiler error (MSVC17) saying:
Error C2679 binary '<<': no operator found which takes a right-hand
operand of type 'const T' (or there is no acceptable
conversion) ControllerSoftware c:\local\boost_1_68_0\boost\log\utility\formatting_ostream.hpp 870
I'll provide full code for sake of clarity:
// cswlogger.h
#pragma once
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/log/core.hpp>
#include <boost/log/sinks/basic_sink_backend.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <vector>
#include <string>
namespace sinks = boost::log::sinks;
BOOST_LOG_GLOBAL_LOGGER(sysLogger,
boost::log::sources::severity_logger_mt<boost::log::trivial::severity_level>);
class CswLogger
{
public:
/// Init with default info level logging
static void init(bool useRamLog = false, boost::log::trivial::severity_level level = boost::log::trivial::info);
/// Disable logging
static void disable();
// Flush log
static void flushLogs();
// Convert file path to only the filename
static std::string path_to_filename(std::string path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
private:
static void registerRamLog();
};
class RamLogSink :
public sinks::basic_formatted_sink_backend<
char,
sinks::combine_requirements<
sinks::synchronized_feeding,
sinks::flushing
>::type
>
{
public:
// The function consumes the log records that come from the frontend
void consume(boost::log::record_view const& rec, string_type const& strType);
// The function flushes the logs in vector
void flush();
private:
std::vector<std::string> logEntries_;
};
// Complete sink type
typedef sinks::synchronous_sink< RamLogSink > sink_t;
#define LOG_LOG_LOCATION(LOGGER, LEVEL, ARG) \
BOOST_LOG_SEV(LOGGER, boost::log::trivial::LEVEL) \
<< boost::log::add_value("Line", std::to_string(__LINE__)) \
<< boost::log::add_value("File", CswLogger::path_to_filename(__FILE__)) << ARG
// System Log macros.
// TRACE < DEBUG < INFO < WARN < ERROR < FATAL
#define LOG_TRACE(ARG) LOG_LOG_LOCATION(sysLogger::get(), trace, ARG);
#define LOG_DEBUG(ARG) LOG_LOG_LOCATION(sysLogger::get(), debug, ARG);
#define LOG_INFO(ARG) LOG_LOG_LOCATION(sysLogger::get(), info, ARG);
#define LOG_WARN(ARG) LOG_LOG_LOCATION(sysLogger::get(), warning, ARG);
#define LOG_ERROR(ARG) LOG_LOG_LOCATION(sysLogger::get(), error, ARG);
#define LOG_FATAL(ARG) LOG_LOG_LOCATION(sysLogger::get(), fatal, ARG);
Source file:
// cswlogger.cpp
#include "cswlogger.h"
#include <boost/log/core.hpp>
#include <boost/log/common.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/settings.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <string>
#include <iostream>
BOOST_LOG_GLOBAL_LOGGER_DEFAULT(sysLogger,
boost::log::sources::severity_channel_logger_mt<boost::log::trivial::severity_level>);
BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::log::attributes::local_clock::value_type)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_severity, "Severity", boost::log::trivial::severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", std::string)
void CswLogger::init(bool useRamLog, boost::log::trivial::severity_level level)
{
boost::log::register_simple_formatter_factory<boost::log::trivial::severity_level, char>("Severity");
if (useRamLog)
{
registerRamLog();
}
else
{
boost::log::add_console_log
(
std::clog,
boost::log::keywords::format = "[%TimeStamp%] [%Severity%] %File%(%Line%): %Message%"
);
}
boost::log::add_common_attributes();
boost::log::core::get()->set_filter
(
boost::log::trivial::severity >= level
);
// Indicate start of logging
LOG_INFO("Log Start");
}
void CswLogger::disable()
{
boost::log::core::get()->set_logging_enabled(false);
}
void CswLogger::flushLogs()
{
boost::log::core::get()->flush();
}
void CswLogger::registerRamLog()
{
boost::shared_ptr< RamLogSink > backend(new RamLogSink());
boost::shared_ptr< sink_t > sink(new sink_t(backend));
sink->set_formatter(boost::log::expressions::stream
<< "[" << a_timestamp << "] " // <--- Compile error here
<< "[" << a_severity << "] "
<< "[" << a_file << "(" << a_line << ")] "
<< boost::log::expressions::message);
boost::log::core::get()->add_sink(sink);
}
// RamlogSink Class declarations
void RamLogSink::consume(boost::log::record_view const& rec, string_type const& strType)
{
logEntries_.emplace_back(strType);
}
void RamLogSink::flush()
{
for (auto const& entry : logEntries_)
{
std::cout << entry << std::endl;
}
logEntries_.clear();
}
main.cpp:
#include "cswlogger.h"
CswLogger::init(true);
LOG_TRACE("This should not be printed");
LOG_ERROR("Very bad error that needs to be printed");
int theInt = 234;
LOG_FATAL("TheInt integer is: " << theInt);
CswLogger::flushLogs();
I also hade a problem when adding the "Line" attribute with:
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
I hade to change to std::string and convert the integer to a string in
#define LOG_LOG_LOCATION(LOGGER, LEVEL, ARG) \
BOOST_LOG_SEV(LOGGER, boost::log::trivial::LEVEL) \
<< boost::log::add_value("Line", std::to_string(__LINE__)) \
<< boost::log::add_value("File", CswLogger::path_to_filename(__FILE__)) << ARG
Otherwise the line number came out as an empty string.

C++ enable/disable debug messages of std::couts on the fly

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.