BOOST_LOG_TRIVIAL and boost::posix_time::ptime output formatting - c++

Need to set custom facet for posix_time::ptime which are printed with BOOST_LOG_TRIVIAL. Say, change default facet to "%Y--%m--%d %H:%M:%S".
I've tried to imbue log sink with new locale but had no success.
The same imbueing trick works perfectly well when applied to std::cout.
What could be the problem and is there a way to deal with it?
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <locale>
namespace logging = boost::log;
namespace pt = boost::posix_time;
void main(void)
{
// console sink
auto sink = logging::add_console_log(std::cout);
// standart output
boost::posix_time::ptime t1(boost::posix_time::time_from_string("2014-11-23 23:59:59.117"));
BOOST_LOG_TRIVIAL(info) << "before " << t1;
// adding custom facet...
pt::time_facet *facet = new boost::posix_time::time_facet("%Y--%m--%d %H:%M:%S");
sink->imbue(std::locale(sink->getloc(), facet));
// ... but output doesn't change
BOOST_LOG_TRIVIAL(info) << "after " << t1;
}

Use sink->set_formatter like this
sink->set_formatter
(
expr::stream << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y--%m--%d %H:%M:%S")
);
Also, make sure you call logging::add_common_attributes();
Working example: Coliru
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/expressions/formatters/date_time.hpp>
namespace logging = boost::log;
namespace pt = boost::posix_time;
namespace expr = boost::log::expressions;
int main(void)
{
// console sink
auto sink = logging::add_console_log(std::cout);
// standart output
boost::posix_time::ptime t1(boost::posix_time::time_from_string("2014-11-23 23:59:59.117"));
BOOST_LOG_TRIVIAL(info) << "before " << t1;
sink->set_formatter
(
expr::stream << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y--%m--%d %H:%M:%S")
);
sink->imbue(sink->getloc());
logging::add_common_attributes();
// ... but output doesn't change
BOOST_LOG_TRIVIAL(info) << "after " << t1;
return 0;
}
Output:
before 2014-Nov-23 23:59:59.117000
2015--09--25 10:37:42

The problem is that the locale in the sink is not used for log message string formatting. It can't be used because there can be multiple sinks and the choice is ambiguous. That locale is used for log record formatting, which is performed for every sink individually.
The log message string is composed with a stream that is initialized for every log message, so you can't imbue it with a custom locale in a way that keeps the locale used for multiple records.
What you can do is set your custom locale as the global one somewhere in the initialization code of your application, before you use Boost.Log.
void main(void)
{
pt::time_facet *facet = new boost::posix_time::time_facet("%Y--%m--%d %H:%M:%S");
std::locale::global(std::locale(std::locale(), facet));
// console sink
auto sink = logging::add_console_log(std::cout);
// ...
BOOST_LOG_TRIVIAL(info) << "after " << t1;
}
Note that this will affect all formatting in your application, for logging purposes and not.
If that is not acceptable you can try different approaches. First, you can write a stream manipulator that will imbue the stream with a locale before formatting date/time objects. To automate the use of this manipulator you can define your own logging macro.
struct locale_manip {};
std::ostream& operator<< (std::ostream& strm, locale_manip)
{
pt::time_facet *facet = new boost::posix_time::time_facet("%Y--%m--%d %H:%M:%S");
strm.imbue(std::locale(std::locale(), facet));
return strm;
}
#define MY_LOG(sev) BOOST_LOG_TRIVIAL(sev) << locale_manip()
void main(void)
{
// ...
MY_LOG(info) << "after " << t1;
}
Another solution is to transform your logging statements so that the date/time objects are added to log records as attributes. Then you can use the formatters and the sink-specific locale to customize the output.

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

BOOST_LOG_TRIVIAL vs logrotate (reopen log)

I have a program that is running as a service for a long time. I use BOOST_LOG_TRIVIAL(info) << "Message" for logging various events. I would like to cooperate with logrotate and reopen my log file after rotation. In theory the screenplay works as follows:
My program starts, some logging happens
Log is renamed from something.log to something.log.1 by an other program like logrotate or by me manually.
My program continues to log to the something.log.1 file.
I send SIGHUP (or something) to my program so I can reopen the log file. But I don't know how to.
My prepared example so far (may not be necessary):
#include <iostream>
#include <stdexcept>
#include <csignal>
#include <boost/thread.hpp>
#include <boost/chrono.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/support/date_time.hpp>
inline
void setupLogging(std::string const &logFileName)
{
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace keywords = boost::log::keywords;
logging::add_common_attributes();
auto format = expr::stream
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f")
<< " progname " << logging::trivial::severity
<< ": " << expr::smessage;
auto fileOutput = logging::add_file_log(
keywords::file_name = logFileName, keywords::format = format
, keywords::auto_flush = true, keywords::open_mode = std::ios::app
);
auto consoleOutput = logging::add_console_log(
std::cerr, keywords::format = format, keywords::auto_flush = true
);
BOOST_LOG_TRIVIAL(debug) << "CHECKPOINT # setupLogging() after log initialization.";
}
void my_signal_handler(int signal)
{
BOOST_LOG_TRIVIAL(info) << "my_signal_handler BEGIN";
/* REOPEN LOG HERE */
BOOST_LOG_TRIVIAL(info) << "my_signal_handler END";
}
int main()
{
setupLogging("logrotate.test.log");
if(signal(SIGHUP, my_signal_handler) == SIG_ERR)
{
BOOST_LOG_TRIVIAL(error) << "Failed to register signal handler";
return 1;
} else
BOOST_LOG_TRIVIAL(info) << "Signal handler registered.";
BOOST_LOG_TRIVIAL(info) << "CHECKPOINT 0";
for(size_t i=1; i<100; ++i)
{
boost::this_thread::sleep_for( boost::chrono::seconds(1) );
BOOST_LOG_TRIVIAL(info) << "CHECKPOINT " << i;
}
return 0;
}
Compiling:
LINK="-lboost_system -lboost_date_time -lboost_log -lboost_log_setup -lboost_thread -lboost_chrono -lpthread"
g++ -std=c++11 -Wextra -DBOOST_LOG_DYN_LINK -pedantic -O3 logrotate_test.cpp -o logrotate_test $LINK
Source code representation of Adam's answer
namespace detail666777888
{
using namespace boost;
using namespace boost::log;
typedef shared_ptr< sinks::synchronous_sink< sinks::text_file_backend > > T;
}
typedef detail666777888::T SPFileSink;
SPFileSink logFileSink;
void setupLogging(...){
... logFileSink = logging::add_file_log ...
}
void my_sighup_handler(int /*signal*/)
{
BOOST_LOG_TRIVIAL(info) << "my_sighup_handler START";
auto oldLFS = logFileSink;
setupLogging("logrotate.test.log");
boost::log::core::get()->remove_sink(oldLFS);
BOOST_LOG_TRIVIAL(info) << "my_sighup_handler FINISH";
}
I was facing the same problem and found a quite straightforward solution: Just initialize the text_file_backend with a single filename (no pattern whatsoever) and call text_file_backend::rotate_file(). This will close the current file and open a new one with the same name.
// create sink
auto backend = boost::make_shared< boost::log::sinks::text_file_backend >(
boost::log::keywords::file_name = "my.log",
boost::log::keywords::open_mode = std::ios_base::out | std::ios_base::app
);
auto sink = boost::make_shared<sinks::synchronous_sink<boost::log::sinks::text_file_backend>>(backend);
boost::log::core::get()->add_sink(sink);
// ... do some logging
// move log file and...
// reopen log file
sink->locked_backend()->rotate_file();
// ... do more logging
I open the log file with std::ios_base::app to prevent the logfile from being truncated if the file exists.
This solution has the advantage that no files need to be copied and log records will neither be lost nor duplicated.
Just use copytruncate logrotate option.
From manual:
Truncate the original log file in place after creating a copy, instead
of moving the old log file and optionally creating a new one. It can
be used when some program cannot be told to close its logfile and thus
might continue writing (appending) to the previous log file forever.
Note that there is a very small time slice between copying the file
and truncating it, so some logging data might be lost. When this
option is used, the create option will have no effect, as the old log
file stays in place.
http://linux.die.net/man/8/logrotate
EDIT:
Try this. Take a look at add_file_log[1] function source code. Then:
remember the object you have added with add_file_log (it returns sink)
when you receive the signal remove_sink[2] and add new one sink with add_file_log[1] -- (logs entries may leak when they are generated by other thread)
[1] http://www.boost.org/doc/libs/1_55_0/boost/log/utility/setup/file.hpp
[2] http://www.boost.org/doc/libs/1_55_0/boost/log/core/core.hpp

How can you get boost::program_options working with boost::posix_time::ptime?

I've made several attemps at getting this working to no avail. The program compiles but every timestamp format I can think of for supplying to the program is invalid.
#include <boost/program_options.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <string>
using namespace boost::program_options;
using namespace std;
int main(int argc, const char** argv) {
boost::posix_time::ptime forceDate;
boost::optional<boost::posix_time::ptime> forceDateOptional;
options_description descr("Options");
descr.add_options()
("force-date,a", value(&forceDate)->value_name("<date>"), "...");
try {
variables_map args;
store(command_line_parser(argc, argv).options(descr).run(), args);
notify(args);
cout << "Done." << endl;
return 0;
} catch (std::exception& e) {
cerr << e.what() << endl;
return 1;
}
}
Compiled with g++ -I /usr/local/include -L /usr/local/lib -lboost_program_options -lboost_system x.cpp and run with ./a.out --force-date 2012-01-03.
Here is the error from the exception thrown:
the argument ('2012-01-03') for option '--force-date' is invalid
Let's first try to simplify your example by removing program_options and simply trying to parse posix_time::ptime from a stream.
boost::posix_time::ptime forceDate;
std::istringstream ss("2012-01-03");
if(ss >> forceDate) {
std::cout << forceDate << "\n";
}
The above example prints nothing, so clearly something's going wrong with the parsing. If you look up the documentation for operator>> it becomes evident that the expected format for the month is an abbreviated name instead of a numeric value.
If you change the above code to
boost::posix_time::ptime forceDate;
std::istringstream ss("2012-Jan-03");
if(ss >> forceDate) {
std::cout << forceDate << "\n";
}
it produces the desired output.
More digging through the documentation shows the way to control the input format is using boost::posix_time::time_input_facet. The set_iso_extended_format sets the format to the numeric format you want to use.
boost::posix_time::ptime forceDate;
std::istringstream ss("2012-01-03");
auto facet = new boost::posix_time::time_input_facet();
facet->set_iso_extended_format();
ss.imbue(std::locale(ss.getloc(), facet));
if(ss >> forceDate) {
std::cout << forceDate << "\n";
}
Live demo
Back to program_options now. It doesn't look like the library offers locale support directly, so the only way I can get the above solution to work is by messing with the global locale. If you add the following lines to your example, it behaves as you want it to.
auto facet = new boost::posix_time::time_input_facet();
facet->set_iso_extended_format();
std::locale::global(std::locale(std::locale(), facet));
Live demo
I was able to replicate your code and compile it on Ubuntu 14.04-- I got the same results you did. So I passed the date into a std::string and then attempted to construct a boost::posix_time::ptime from that:
#include <boost/program_options.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <string>
using namespace boost::program_options;
using namespace std;
int main (int argc, const char ** argv) {
auto dateStr = string {};
auto descr = options_description { "Options" };
descr.add_options ()
("help,h", "produce help message")
("date,a", value (&dateStr)->value_name ("<date>"), "...");
try {
auto args = variables_map {};
store (command_line_parser (argc, argv).options (descr).run (), args);
notify (args);
auto forceDate = boost::posix_time::ptime { dateStr };
cout << "Done." << endl;
return 0;
} catch (std::exception & e) {
cout << e.what () << endl;
return 1;
}
}
I had to dig around a little before finding out that according to this SO answer you must link boost_date_time. I used the following script to compile and re-run at the same time:
rerun.sh
#!/bin/bash
g++ -o main.x64 -std=c++11 -I . -L/usr/lib/x86_64-linux-gnu main.cpp -lboost_program_options -lboost_system -lboost_date_time
./main.x64 --date "2012-01-01"
and this is the final code I used to get it to run:
main.cpp
#include <boost/program_options.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <string>
using namespace boost::program_options;
using namespace boost::posix_time;
using namespace std;
int main (int argc, const char ** argv) {
auto dateStr = string {};
auto descr = options_description { "Options" };
descr.add_options ()
("date,a", value (&dateStr)->value_name ("<date>"), "...");
try {
auto args = variables_map {};
store (command_line_parser (argc, argv).options (descr).run (), args);
notify (args);
// this is the important part.
auto d = boost::gregorian::date {
boost::gregorian::from_simple_string (dateStr)
};
auto forceDate = boost::posix_time::ptime { d };
cout << "Done." << endl;
return 0;
} catch (std::exception & e) {
cout << e.what () << endl;
return 1;
}
}

Boost.Log: Support file name and line number

I am trying to make my team go away from log4cxx and try to use Boost.Log v2 instead. Our current log4cxx pattern is rather simple:
log4cxx::helpers::Properties prop;
prop.setProperty("log4j.rootLogger","DEBUG, A1");
prop.setProperty("log4j.appender.A1","org.apache.log4j.ConsoleAppender");
prop.setProperty("log4j.appender.A1.layout","org.apache.log4j.PatternLayout");
prop.setProperty("log4j.appender.A1.layout.ConversionPattern","%d{ABSOLUTE} %-5p [%c] %m%n");
log4cxx::PropertyConfigurator::configure(prop);
However I failed to find a solution to support filename and line number printing. I've found so far an old post, but there is no clear solution (no accepted solution). I've looked into using those BOOST_LOG_NAMED_SCOPE but they feel very ugly as it make it impossible to print the proper line number when multiple of those are used within the same function.
I've also found a simpler direct solution, here. But that also feel ugly, since it will print the fullpath, instead of just the basename (it is not configurable). So the fullpath and line number are always printed on a fixed location (before expr::smessage).
I've found also this post, which looks like an old solution.
And finally the most promising solution, I found was here. However it does not even compile for me.
So my question is simply: how can I use Boost.Log v2 to print filename (not fullpath) and line number with minimal formatter flexibility (no solution with MACRO and __FILE__ / __LINE__ accepted, please). I'd appreciate a solution which does not involve BOOST_LOG_NAMED_SCOPE, as described here:
If instead you want to see line numbers of particular log records then
the best way is to define a custom macro which you will use to write
logs. In that macro, you can add the file name and line number as
attributes to the record (you can use manipulators for that). Note
that in this case you won't be able to use these attributes in
filters, but you probably don't need that.
I finally found a simple solution based on add_value. Here is the full source code:
#include <ostream>
#include <fstream>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/manipulators/add_value.hpp>
#include <boost/filesystem.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
void my_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) << ": ";
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[logging::trivial::severity] << "> ";
// Finally, put the record message to the stream
strm << rec[expr::smessage];
}
void init()
{
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 >("sample.log"));
sink->set_formatter(&my_formatter);
logging::core::get()->add_sink(sink);
}
#define MY_GLOBAL_LOGGER(log_,sv) BOOST_LOG_SEV( log_, sv) \
<< boost::log::add_value("Line", __LINE__) \
<< boost::log::add_value("File", __FILE__) \
<< boost::log::add_value("Function", BOOST_CURRENT_FUNCTION)
int main(int, char*[])
{
init();
logging::add_common_attributes();
using namespace logging::trivial;
src::severity_logger< severity_level > lg;
MY_GLOBAL_LOGGER(lg,debug) << "Keep";
MY_GLOBAL_LOGGER(lg,info) << "It";
MY_GLOBAL_LOGGER(lg,warning) << "Simple";
MY_GLOBAL_LOGGER(lg,error) << "Stupid";
return 0;
}
On my Linux box, I compiled it using:
$ c++ -otutorial_fmt_custom -DBOOST_LOG_DYN_LINK tutorial_fmt_custom.cpp -lboost_log -lboost_log_setup -lboost_thread -lpthread -lboost_filesystem
If you run and check the log file generated here is what you get:
$ ./tutorial_fmt_custom && cat sample.log
1: 61: tutorial_fmt_custom.cpp: <debug> Keep
2: 62: tutorial_fmt_custom.cpp: <info> It
3: 63: tutorial_fmt_custom.cpp: <warning> Simple
4: 64: tutorial_fmt_custom.cpp: <error> Stupid
My solution is based on two inputs (thanks!):
http://www.boost.org/doc/libs/1_58_0/libs/log/example/doc/tutorial_fmt_custom.cpp
http://d.hatena.ne.jp/yamada28go/20140215/1392470561
In order to output filename and line you will need to register Scopes:
logging::core::get()->add_global_attribute("Scopes", attributes::named_scope());
and add custom formatter:
void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
const auto cont = logging::extract< attributes::named_scope::value_type >("Scopes", rec);
if(cont.empty())
return;
auto it = cont->begin();
boost::filesystem::path path(it->file_name.c_str());
strm << path.filename().string() << ":" << it->line << "\t" << rec[expr::smessage];
}
Note: In order for this to work the scopes container needs to be cleared before each logging:
if(!attributes::named_scope::get_scopes().empty()) attributes::named_scope::pop_scope();

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.