Boost logging in real time - c++

I use boost log to output my logs in two separate files, the problem is that i need these outputs to be written instantly, but boost waits 200+ lines to output it in the files. I don't want to have an ultra fast output but two times in a second or each second can be great.
Is there a way to manage this time or the number of lines between each write ?
My h (BoostLogging.h) :
#ifndef AUDIO_RECO_MODULES_COMMON_BOOSTLOGGING_H_
#define AUDIO_RECO_MODULES_COMMON_BOOSTLOGGING_H_
#include <cstddef>
#include <string>
#include <ostream>
#include <fstream>
#include <iomanip>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/phoenix/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/attributes.hpp>
#include <boost/log/sources/basic_logger.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/severity_channel_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/utility/value_ref.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
enum severity_level {
TRACE,
DEBUG,
INFO,
WARNING,
ERROR,
FATAL,
NODE
};
BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(tag_attr, "Tag", std::string)
std::ostream& operator<<(std::ostream& strm, severity_level level);
void InitBoostLog(severity_level logging_level, std::string module_name);
#endif //AUDIO_RECO_MODULES_COMMON_BOOSTLOGGING_H_
The source (BoostLogging.cc) :
#include <BoostLogging.h>
std::ostream& operator<<(std::ostream& strm, severity_level level) {
static const char* strings[] = {
"trace",
"debug",
"info",
"warning",
"error",
"fatal",
"node"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
void InitBoostLog(severity_level logging_level, std::string module_name) {
// Setup the common formatter for all sinks
logging::formatter fmt = expr::stream
<< std::setw(6) << std::setfill('0') << line_id << std::setfill(' ')
<< ": <" << severity << ">\t"
<< expr::if_(expr::has_attr(tag_attr))
[
expr::stream << "[" << tag_attr << "] "
]
<< expr::smessage;
// Initialize sinks
boost::shared_ptr<logging::core> core = logging::core::get();
typedef sinks::synchronous_sink <sinks::text_ostream_backend> text_sink;
boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();
sink->locked_backend()->add_stream(
boost::make_shared<std::ofstream>("arm-" + module_name + ".log"));
sink->set_formatter(fmt);
sink->set_filter(severity != NODE && severity >= logging_level);
core->add_sink(sink);
sink = boost::make_shared<text_sink>();
sink->locked_backend()->add_stream(
boost::make_shared<std::ofstream>("arm-" + module_name + "-node.log"));
sink->set_filter(severity == NODE);
core->add_sink(sink);
// Add attributes
logging::add_common_attributes();
}

you can enable auto-flushing for your backend as described here: http://boost-log.sourceforge.net/libs/log/doc/html/log/detailed/sink_backends.html

Enabling auto flushing as #Andy T suggested is the good way to do it, before adding the sink to the core we need to enable the auto-flushing.
sink->locked_backend()->add_stream(
boost::make_shared<std::ofstream>("arm-" + module_name + ".log"));
sink->set_formatter(fmt);
sink->set_filter(severity != NODE && severity >= logging_level);
sink->locked_backend()->auto_flush(true); // HERE
core->add_sink(sink);
sink = boost::make_shared<text_sink>();
sink->locked_backend()->add_stream(
boost::make_shared<std::ofstream>("arm-" + module_name + "-node.log"));
sink->set_filter(severity == NODE);
sink->locked_backend()->auto_flush(true); // AND HERE
core->add_sink(sink);

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

Passing boost log message as an argument to function call

I have written C++ code for capturing the various severity levels of messages. I have used https://github.com/gklingler/simpleLogger for this.
File simpleLogger.cpp
#include "simpleLogger.h"
#include <boost/log/core/core.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/expressions.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/core/null_deleter.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>
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(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)
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("LineID", attrs::counter<unsigned int>(1)); // lines are sequentially numbered
logger.add_attribute("TimeStamp", attrs::local_clock()); // each log line gets a timestamp
// add a text sink
typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink;
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
<< std::setw(7) << std::setfill('0') << line_id << std::setfill(' ') << " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - " << 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 simpleLogger.h
#ifndef simpleLogger_h__
#define simpleLogger_h__
#define BOOST_LOG_DYN_LINK // necessary when linking the boost_log library dynamically
#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.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 app.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;
}
So I would send my message as
LOG_INFO << "This is info message"
But this log message I need to send it to some other function as an argument. In that function I will be doing some other changes on the log message i.e "This is info message".
How to send the boost log message as an argument to function?
I didn't find relevant source for this.
Thanks in advance
You can define a "lazy actor" that you can put into a wrapped formatter expression. This is pretty much the rocket science of Boost Log, but perhaps this simple example will help you:
auto reverse_expr = [](auto fmt) {
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);
std::reverse(text.begin(), text.end());
strm << text;
});
};
// specify the format of the log message
logging::formatter formatter = expr::stream
<< reverse_expr(expr::stream << std::setw(7) << std::setfill('0') << line_id)
<< " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - "
<< reverse_expr(expr::stream << expr::smessage);
sink->set_formatter(formatter);
Results in:
3000000 | 2020-04-28, 16:15:15.779204 [warning] - egassem gninraw a si siht
4000000 | 2020-04-28, 16:15:15.779308 [error] - egassem rorre na si siht
5000000 | 2020-04-28, 16:15:15.779324 [fatal] - egassem rorre lataf a si siht
Notes: it will not be overly efficient because it involves "double buffering" with a temporary stream, but it is highly flexible, as you can see.
UPDATE Improved method below (see section BONUS)
A slightly more generic implementation might look like:
template <typename F> struct Xfrm {
Xfrm(F f) : _f(f) {}
F _f;
template <typename E> auto operator[](E fmt) const {
return expr::wrap_formatter(
[f=_f,fmt](logging::record_view const& rec, logging::formatting_ostream& strm) {
logging::formatting_ostream tmp;
std::string text;
tmp.attach(text);
fmt(rec, tmp);
strm << f(text);
});
}
};
So you can actually use it for other functions:
std::string reversed(std::string v) {
std::reverse(v.begin(), v.end());
return v;
}
std::string to_uppercase(std::string v) {
for (auto& ch : v) ch = std::toupper(ch);
return v;
}
Which you could then use like:
logging::formatter formatter = expr::stream
<< Xfrm(reversed)[expr::stream << std::setw(7) << std::setfill('0') << line_id]
<< " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - "
<< Xfrm(to_uppercase)[ expr::stream << expr::smessage ];
Or put the manipulators in your header file:
static inline constexpr Xfrm UC{to_uppercase};
static inline constexpr Xfrm REV{reversed};
So you can use it pre-fab:
logging::formatter formatter = expr::stream
<< REV[ expr::stream << std::setw(7) << std::setfill('0') << line_id ]
<< " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - "
<< UC[ expr::stream << expr::smessage ];
Output
3000000 | 2020-04-28, 16:36:46.184958 [warning] - THIS IS A WARNING MESSAGE
4000000 | 2020-04-28, 16:36:46.185034 [error] - THIS IS AN ERROR MESSAGE
5000000 | 2020-04-28, 16:36:46.185045 [fatal] - THIS IS A FATAL 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 <boost/make_shared.hpp>
#include <boost/phoenix.hpp>
#include <boost/phoenix/function.hpp>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <ostream>
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(line_id, "LineID", unsigned int)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity",
logging::trivial::severity_level)
namespace /*extend locally*/ {
template <typename F> struct Xfrm {
constexpr Xfrm(F f) : _f(f) {}
F _f;
template <typename E> auto operator[](E fmt) const {
return expr::wrap_formatter(
[f = _f, fmt](logging::record_view const& rec,
logging::formatting_ostream& strm) {
logging::formatting_ostream tmp;
std::string text;
tmp.attach(text);
fmt(rec, tmp);
strm << f(text);
});
}
};
std::string reversed(std::string v) {
std::reverse(v.begin(), v.end());
return v;
}
std::string to_uppercase(std::string v) {
for (auto& ch : v) {
ch = std::toupper(ch);
}
return v;
}
inline constexpr Xfrm UC{ to_uppercase };
inline constexpr Xfrm REV{ reversed };
} // 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("LineID", attrs::counter<unsigned int>(
1)); // lines are sequentially numbered
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
<< REV[expr::stream << std::setw(7) << std::setfill('0') << line_id]
<< " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - " << UC[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;
}
BONUS: More Efficient
To avoid double-buffering, we can avoid using the original formatter. This works best for known attributes:
struct OddEvenId {
void operator()(logging::record_view const& rec, logging::formatting_ostream& strm) const {
auto vr = line_id.or_throw()(rec);
if (!vr.empty()) {
strm << std::setw(4) << (vr.get<uint32_t>()%2? "Odd":"Even");
}
}
};
struct QuotedMessage {
void operator()(logging::record_view const& rec, logging::formatting_ostream& strm) const {
auto vr = expr::smessage.or_throw()(rec);
if (!vr.empty())
strm << std::quoted(vr.get<std::string>());
}
};
static inline auto oddeven_id = expr::wrap_formatter(OddEvenId{});
static inline auto qmessage = expr::wrap_formatter(QuotedMessage{});
Now we can simply say qmessage instead of expr::smessage to get the quoted message value:
Live On Wandbox
logging::formatter formatter = expr::stream
<< oddeven_id
<< " | "
<< expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
<< "[" << logging::trivial::severity << "]"
<< " - " << qmessage;
Prints
Odd | 2020-04-28, 17:21:12.619565 [warning] - "this is a warning message"
Even | 2020-04-28, 17:21:12.619666 [error] - "this is an error message"
Odd | 2020-04-28, 17:21:12.619684 [fatal] - "this is a fatal error message"

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.

boost log to print source code file name and line number

I'm using Boost(1.55.0) Logging in my C++ application.
I have been able to generate log of this format
[2014-Jul-15 10:47:26.137959]: <debug> A regular message
I want to be able to add source file name and line number where
the log is generated.
[2014-Jul-15 10:47:26.137959]: <debug> [filename:line_no] A regular message
example:
[2014-Jul-15 10:47:26.137959]: <debug> [helloworld.cpp : 12] A regular message
Source Code:
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/attributes/attribute.hpp>
#include <boost/log/attributes/attribute_cast.hpp>
#include <boost/log/attributes/attribute_value.hpp>
#include <boost/make_shared.hpp>
#include <boost/property_tree/ptree.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace sinks = boost::log::sinks;
namespace keywords = boost::log::keywords;
void init()
{
logging::add_file_log
(
keywords::file_name = "sample_%N.log", /*< file name pattern >*/
keywords::rotation_size = 10*1024*1204, /*< rotate files every 10 MiB... >*/
keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0), /*< ...or at midnight >*/
keywords::format =
(
boost::log::expressions::stream
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d_%H:%M:%S.%f")
<< ": <" << boost::log::trivial::severity << "> "
<< boost::log::expressions::smessage
)
);
}
int main(int, char*[])
{
init();
logging::add_common_attributes();
using namespace logging::trivial;
src::severity_logger< severity_level > lg;
BOOST_LOG_SEV(lg, debug) << "A regular message";
return 0;
}
As Horus pointed out, you can use attributes to log file and line numbers. However it is best to avoid using multi-statements macros to avoid problems with expressions like this:
if (something)
LOG_FILE_LINE(debug) << "It's true"; // Only the first statement is conditional!
You can do better creating a macro that leverages the underlying behavior of the Boost Log library. For example, BOOST_LOG_SEV is:
#define BOOST_LOG_SEV(logger, lvl) BOOST_LOG_STREAM_SEV(logger, lvl)
#define BOOST_LOG_STREAM_SEV(logger, lvl)\
BOOST_LOG_STREAM_WITH_PARAMS((logger), (::boost::log::keywords::severity = (lvl)))
Using BOOST_LOG_STREAM_WITH_PARAMS you can set and get more attributes, like this:
// New macro that includes severity, filename and line number
#define CUSTOM_LOG(logger, sev) \
BOOST_LOG_STREAM_WITH_PARAMS( \
(logger), \
(set_get_attrib("File", path_to_filename(__FILE__))) \
(set_get_attrib("Line", __LINE__)) \
(::boost::log::keywords::severity = (boost::log::trivial::sev)) \
)
// Set attribute and return the new value
template<typename ValueType>
ValueType set_get_attrib(const char* name, ValueType value) {
auto attr = logging::attribute_cast<attrs::mutable_constant<ValueType>>(logging::core::get()->get_thread_attributes()[name]);
attr.set(value);
return attr.get();
}
// Convert file path to only the filename
std::string path_to_filename(std::string path) {
return path.substr(path.find_last_of("/\\")+1);
}
The complete source code is:
#include <boost/log/trivial.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
namespace logging = boost::log;
namespace attrs = boost::log::attributes;
namespace expr = boost::log::expressions;
namespace src = boost::log::sources;
namespace keywords = boost::log::keywords;
// New macro that includes severity, filename and line number
#define CUSTOM_LOG(logger, sev) \
BOOST_LOG_STREAM_WITH_PARAMS( \
(logger), \
(set_get_attrib("File", path_to_filename(__FILE__))) \
(set_get_attrib("Line", __LINE__)) \
(::boost::log::keywords::severity = (boost::log::trivial::sev)) \
)
// Set attribute and return the new value
template<typename ValueType>
ValueType set_get_attrib(const char* name, ValueType value) {
auto attr = logging::attribute_cast<attrs::mutable_constant<ValueType>>(logging::core::get()->get_thread_attributes()[name]);
attr.set(value);
return attr.get();
}
// Convert file path to only the filename
std::string path_to_filename(std::string path) {
return path.substr(path.find_last_of("/\\")+1);
}
void init() {
// New attributes that hold filename and line number
logging::core::get()->add_thread_attribute("File", attrs::mutable_constant<std::string>(""));
logging::core::get()->add_thread_attribute("Line", attrs::mutable_constant<int>(0));
logging::add_file_log (
keywords::file_name = "sample.log",
keywords::format = (
expr::stream
<< expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y-%m-%d_%H:%M:%S.%f")
<< ": <" << boost::log::trivial::severity << "> "
<< '[' << expr::attr<std::string>("File")
<< ':' << expr::attr<int>("Line") << "] "
<< expr::smessage
)
);
logging::add_common_attributes();
}
int main(int argc, char* argv[]) {
init();
src::severity_logger<logging::trivial::severity_level> lg;
CUSTOM_LOG(lg, debug) << "A regular message";
return 0;
}
This generate a log like this:
2015-10-15_15:25:12.743153: <debug> [main.cpp:61] A regular message
As user2943014 pointed out, using scopes prints the line number where you opened that scope, not the line number where you emitted a log message using BOOST_LOG_SEV.
You can use attributes to log the line numbers etc at the place you actually logged.
Register global attributes in your logging initialization function:
using namespace boost::log;
core::get()->add_global_attribute("Line", attributes::mutable_constant<int>(5));
core::get()->add_global_attribute("File", attributes::mutable_constant<std::string>(""));
core::get()->add_global_attribute("Function", attributes::mutable_constant<std::string>(""));
Setting these attributes in your logging macro:
#define logInfo(methodname, message) \
LOG_LOCATION; \
BOOST_LOG_SEV(_log, boost::log::trivial::severity_level::trace) << message
#define LOG_LOCATION \
boost::log::attribute_cast<boost::log::attributes::mutable_constant<int>>(boost::log::core::get()->get_global_attributes()["Line"]).set(__LINE__); \
boost::log::attribute_cast<boost::log::attributes::mutable_constant<std::string>>(boost::log::core::get()->get_global_attributes()["File"]).set(__FILE__); \
boost::log::attribute_cast<boost::log::attributes::mutable_constant<std::string>>(boost::log::core::get()->get_global_attributes()["Function"]).set(__func__);
Not exactly beautiful, but it works and it was a long way for me. It's a pity boost doesn't offer this feature out of the box.
I would suggest to use the boost::log::add_value() function.
Define:
#define LOG_LOCATION(LEVEL, MSG) \
BOOST_LOG_SEV(logger::get(), LEVEL) \
<< boost::log::add_value("Line", __LINE__) \
<< boost::log::add_value("File", __FILE__) \
<< boost::log::add_value("Function", __FUNCTION__) << MSG
And then you can format it as follows:
boost::log::add_common_attributes();
boost::log::register_simple_filter_factory<boost::log::trivial::severity_level, char>("Severity");
boost::log::register_simple_formatter_factory<boost::log::trivial::severity_level, char>("Severity");
auto syslog_format(
boost::log::expressions::stream <<
"[" << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") <<
"] [" << boost::log::expressions::attr<boost::log::attributes::current_thread_id::value_type>("ThreadID") <<
"] [" << std::left << std::setw(7) << std::setfill(' ') << boost::log::trivial::severity <<
"] " << boost::log::expressions::smessage <<
" (" << boost::log::expressions::attr<std::string>("Filename") <<
":" << boost::log::expressions::attr<int>("Line") <<
":" << boost::log::expressions::attr<std::string>("Function") <<
")"
);
boost::log::add_file_log(
boost::log::keywords::file_name = "sys_%d_%m_%Y.%N.log",
boost::log::keywords::format = syslog_format
);
No need to add global attributes, and you can format it easily as seen above. I find this is a good compromise between other's solutions and the raw __FILE__ __LINE__ approach.
Full example here.
Define
namespace attrs = boost::logging::attributes;
namespace expr = boost::logging::expressions;
Add
<< expr::format_named_scope("Scope", keywords::format = "[%f:%l]")
to your keywords::format = (...) in init.
Then add
logging::core::get()->add_global_attribute("Scope", attrs::named_scope());
after add_common_attributes() in main.
Then just before the BOOST_LOG_SEV line add BOOST_LOG_NAMED_SCOPE("whatever").
BOOST_LOG_NAMED_SCOPE("whatever") creates a "scope" named "whatever". The scope is implemented by a unused variable that contains the scope name and the file and the line where the scope was defined.
The format_named_scope line specifies how a scope should be formatted in the log line. %f is the file, %l is the line and %n is the scope name.
Note that the file line that appears in the log record is the line where the macro BOOST_LOG_NAMED_SCOPE appears and not the line of the BOOST_LOG_SEV macro.
I am not aware of simple method to record the file and line without using the BOOST_LOG_NAMED_SCOPE.

boost.log breaking when Xcode precompiled header used

I've got a pretty minimal sample project for boost.log running on Xcode 5, which goes like this:
#include <iostream>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/utility/setup/console.hpp>
namespace logging = boost::log;
namespace keywords = boost::log::keywords;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
int main(int argc, const char *argv[])
{
logging::add_file_log
(
keywords::file_name = "Out_%N.log",
keywords::format =
(
expr::stream
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
<< ": [" << logging::trivial::severity
<< "] " << expr::smessage
)
);
logging::core::get()->set_filter(logging::trivial::severity >= logging::trivial::debug);
logging::add_common_attributes();
src::severity_logger< logging::trivial::severity_level > logger;
BOOST_LOG_SEV(logger, logging::trivial::warning) << "a warning message";
return 0;
}
Now everything runs fine with a command line project.
However as soon as I use the snipped in a real-world project with the default Xcode precompiled header file I'm getting compiler errors in boost/type_traits/detail/has_binary_operator.hpp and boost/lexical_cast.hpp.
Precompiled header test_prefix.pch:
//
// Prefix header for all source files in project
//
#include <Carbon/Carbon.h>
Already wasted hours fiddling with compiler settings and project configuration in Xcode so any feedback is appreciated!
You likely need to #define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 prior to including Carbon/Carbon.h in your prefix header. See https://svn.boost.org/trac/boost/ticket/6219 for more information.