I am using boost log in my application, and while it has been tricky to configure it is generally working well.
Now however, I would like to add some more advanced filtering logic to my application, and I can't figure it out.
What I would like is to have two "levels" of filtering:
I am already using a "severity logger" with different levels like debug, warn, note etc. This is setup and working.
I would like to add an additional way to filter records by looking at the "named scope" that the record emanates from.
So I would like for instance to be able to see only the records with severity >= note, AND within a NAMED_SCOPE of monthly.
I have successfully been able to use the BOOST_LOG_NAMED_SCOPE() macro and can see the scope stack in the log messages.
I have tried implementing a custom filter with boost::phoenix, but I can't get it to work.
The code I have pasted here compiles in my application (I am working on stripping this down so I can include a complete working minimal example here), but nothing appears to happen in the my_filter(..) function. I think that I am not correctly passing the scope stack into the bound function, because if I put a std::cout statement within the loop over the scope stack, I do not see anything printed.
Minimal example here:
// Excerpt from MyLogger.cpp class
bool my_filter(attrs::named_scope_list const& scopeList) {
for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
if ( (*iter).scope_name == "monthly") {
return true;
}
}
return false;
}
void setup_logging(std::string lvl) {
logging::core::get()->add_global_attribute(
"Scope", attrs::named_scope()
);
logging::add_console_log(
std::clog,
keywords::format = (
expr::stream
<< "[" << severity << "] "
<< "[" << named_scope << "] "
<< expr::smessage
)
);
try {
// set the severity level...
EnumParser<severity_level> parser;
logging::core::get()->set_filter(
(
severity >= parser.parseEnum(lvl) &&
( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, attrs::named_scope::get_scopes() ) ) )
)
);
} catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
<< "[debug, info, note, warn, err, fatal]\n";
exit(-1);
}
}
EDIT Updated with minimal working example:
TEMLogger.h
#ifndef _TEMLOGGER_H_
#define _TEMLOGGER_H_
#include <string>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <exception>
#include <map>
#include <iomanip>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/sources/global_logger_storage.hpp>
#include <boost/log/sources/severity_feature.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/attributes/current_process_id.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
namespace logging = boost::log;
namespace src = boost::log::sources;
namespace attrs = boost::log::attributes;
namespace keywords = boost::log::keywords;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
/** Define the "severity levels" for Boost::Log's severity logger. */
enum severity_level {
debug, info, note, warn, err, fatal
};
/** Convert from string to enum integer value.
*
* Inspired by: http://stackoverflow.com/questions/726664/string-to-enum-in-c
*/
template <typename T>
class EnumParser {
std::map <std::string, T> enumMap;
public:
EnumParser(){};
T parseEnum(const std::string &value) {
typename std::map<std::string, T>::const_iterator iValue = enumMap.find(value);
if (iValue == enumMap.end())
throw std::runtime_error("Value not found in enum!");
return iValue->second;
}
};
BOOST_LOG_GLOBAL_LOGGER(my_logger, src::severity_logger< severity_level >);
/** Send string representing an enum value to stream
*/
std::ostream& operator<< (std::ostream& strm, severity_level lvl);
void setup_logging(std::string lvl);
#endif /* _TEMLOGGER_H_ */
TEMLogger.cpp
#include <boost/log/expressions/formatters/named_scope.hpp>
#include <boost/log/expressions.hpp>
#include <boost/phoenix.hpp>
#include "TEMLogger.h"
// Create the global logger object
src::severity_logger< severity_level > glg;
// Add a bunch of attributes to it
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", severity_level)
BOOST_LOG_ATTRIBUTE_KEYWORD(named_scope, "Scope", attrs::named_scope::value_type)
/** Initialize the enum parser map from strings to the enum levels.*/
template<>
EnumParser< severity_level >::EnumParser() {
enumMap["debug"] = debug;
enumMap["info"] = info;
enumMap["note"] = note;
enumMap["warn"] = warn;
enumMap["err"] = err;
enumMap["fatal"] = fatal;
}
std::ostream& operator<< (std::ostream& strm, severity_level level) {
static const char* strings[] = {
"debug", "info", "note", "warn", "err", "fatal"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
bool my_filter(boost::log::value_ref< attrs::named_scope > const& theNamedScope) {
// I think I want something like this:
// for (attrs::named_scope_list::const_iterator iter = scopeList.begin(); iter != scopeList.end(); ++iter) {
// if ( (*iter).scope_name == "monthly"){
// return true;
// }
// }
return true;
}
void setup_logging(std::string lvl) {
logging::core::get()->add_global_attribute(
"Scope", attrs::named_scope()
);
logging::add_console_log(
std::clog,
keywords::format = (
expr::stream
<< "[" << severity << "] "
<< "[" << named_scope << "] "
<< expr::smessage
)
);
try {
// set the severity level...
EnumParser<severity_level> parser;
logging::core::get()->set_filter(
(
severity >= parser.parseEnum(lvl) &&
( expr::has_attr("Scope") && ( boost::phoenix::bind(&my_filter, expr::attr< attrs::named_scope >("Scope").or_none()) ) )
)
);
} catch (std::runtime_error& e) {
std::cout << e.what() << std::endl;
std::cout << "'" << lvl << "' is an invalid --log-level! Must be one of "
<< "[debug, info, note, warn, err, fatal]\n";
exit(-1);
}
}
Main Program
#include "TEMLogger.h"
extern src::severity_logger< severity_level > glg;
void func1() {
BOOST_LOG_NAMED_SCOPE("monthly");
for (int i=0; i<5; ++i) {
BOOST_LOG_SEV(glg, note) << "doing iteration " << i << "within monthly scope!";
}
}
int main(int argc, char* argv[]) {
std::cout << "Setting up logging...\n";
setup_logging("debug");
BOOST_LOG_SEV(glg, note) << "Some message in the main scope";
func1();
BOOST_LOG_SEV(glg, note) << "Another message in the main scope";
return 0;
}
Compile
(I am on a Mac, and due to the way I installed Boost I have to specify the compiler, and the method of linking the Boost libs. YMMV)
g++-4.8 -o TEMLogger.o -c -g -DBOOST_ALL_DYN_LINK TEMLogger.cpp
g++-4.8 -o log-filter-example.o -c -g -DBOOST_ALL_DYN_LINK log-filter-example.cpp
g++-4.8 -o a.out log-filter-example.o TEMLogger.o -L/usr/local/lib -lboost_system-mt -lboost_filesystem-mt -lboost_thread-mt -lboost_log-mt
Run
$ ./a.out
Setting up logging...
[note] [] Some message in the main scope
[note] [monthly] doing iteration 0within monthly scope!
[note] [monthly] doing iteration 1within monthly scope!
[note] [monthly] doing iteration 2within monthly scope!
[note] [monthly] doing iteration 3within monthly scope!
[note] [monthly] doing iteration 4within monthly scope!
[note] [] Another message in the main scope
Analysis
Your problems lie in how you use phoenix::bind to create the filtering expression.
boost::phoenix::bind(&my_filter
, attrs::named_scope::get_scopes())
The way it's written, it will bind to the value returned by get_scopes() at the time of binding. Instead, we want lazy evaluation, which will happen just before the my_filter function is invoked for each message. This is accomplished by boost::log::expressions::attribute_actor, which we can create using boost::log::expressions::attr(...)
boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope>("Scope").or_none())
The next issue lies in boost::log::attributes::named_scope. As the documentation says
The basic_named_scope attribute is essentially a hook to the thread-specific instance of scope list.
Instead of this dummy attribute, we're actually interested in extracting the current scope stack for the given message. According to the value_type, this is an instance of boost::log::attributes::named_scope_list. Hence, we should change the code to
boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope_list>("Scope").or_none())
and also adjust the signature of my_filter(...) to match:
bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes)
Now, since we're using .or_none() to create the attribute_actor, we can drop from our filter expression the check for existence of attribute "Scope"
expr::has_attr("Scope") // This goes away
and do this test in our filter function instead
if (!scopes.empty()) { // ...
} else { return false; }
Finally, we should probably have a way to configure the scope we want to filter. So, let's add a parameter to the filter function:
bool my_filter(boost::log::value_ref<attrs::named_scope_list> const& scopes
, std::string const& target_scope)
{ // ...
}
And bind it to the desired value
std::string target_scope("scope_2"); // Or read from config
// .....
(boost::phoenix::bind(&my_filter
, expr::attr<attrs::named_scope_list>("Scope").or_none()
, target_scope))
Sample Code
Includes, type definitions:
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
// NB: The following two are just convenience
// Reduce them to just the headers needed to reduce compile time
#include <boost/log/attributes.hpp>
#include <boost/log/expressions.hpp>
#include <boost/phoenix.hpp>
// ============================================================================
namespace bl = boost::log;
// ----------------------------------------------------------------------------
typedef bl::sources::severity_logger <bl::trivial::severity_level> logger_t;
typedef bl::trivial::severity_level log_level;
// ----------------------------------------------------------------------------
BOOST_LOG_ATTRIBUTE_KEYWORD(my_named_scope, "Scope", bl::attributes::named_scope::value_type)
// ============================================================================
The filter function:
// ============================================================================
bool my_filter(bl::value_ref<bl::attributes::named_scope_list> const& scopes
, std::string const& target_scope)
{
bool matched(false);
if (!scopes.empty()) {
for (auto& scope : scopes.get()) {
if (scope.scope_name == target_scope) {
matched = matched || true; // Any scope name matches...
}
}
}
return matched;
}
// ============================================================================
Logger initialization:
// ============================================================================
void init_logging()
{
bl::core::get()->add_global_attribute(
"Scope", bl::attributes::named_scope()
);
bl::add_console_log(std::clog
, bl::keywords::format = (
bl::expressions::stream
<< "[" << bl::trivial::severity << "] "
<< "[" << my_named_scope << "] "
// Alternative way to format this:
// << bl::expressions::format_named_scope("Scope", bl::keywords::format = "[%n] ")
<< bl::expressions::smessage
));
// Hard-coded, determine this as appropriate from config
log_level target_severity(log_level::info);
std::string target_scope("scope_2");
bl::core::get()->set_filter(
(bl::trivial::severity >= target_severity)
&& (boost::phoenix::bind(&my_filter
, bl::expressions::attr<bl::attributes::named_scope_list>("Scope").or_none()
, target_scope))
);
}
// ============================================================================
Finally testing it:
// ============================================================================
void log_it(logger_t& log, int n)
{
BOOST_LOG_SEV(log, log_level::debug) << "A" << n;
BOOST_LOG_SEV(log, log_level::trace) << "B" << n;
BOOST_LOG_SEV(log, log_level::info) << "C" << n;
BOOST_LOG_SEV(log, log_level::warning) << "D" << n;
BOOST_LOG_SEV(log, log_level::error) << "E" << n;
BOOST_LOG_SEV(log, log_level::fatal) << "F" << n;
}
// ============================================================================
int main()
{
init_logging();
logger_t log;
log_it(log, 1);
{
BOOST_LOG_NAMED_SCOPE("scope_1");
log_it(log, 2);
}
{
BOOST_LOG_NAMED_SCOPE("scope_2");
log_it(log, 3);
{
BOOST_LOG_NAMED_SCOPE("scope_3");
log_it(log, 4);
}
log_it(log, 5);
}
return 0;
}
// ============================================================================
Test Run
Output without filtering:
[debug] [] A1
[trace] [] B1
[info] [] C1
[warning] [] D1
[error] [] E1
[fatal] [] F1
[debug] [scope_1] A2
[trace] [scope_1] B2
[info] [scope_1] C2
[warning] [scope_1] D2
[error] [scope_1] E2
[fatal] [scope_1] F2
[debug] [scope_2] A3
[trace] [scope_2] B3
[info] [scope_2] C3
[warning] [scope_2] D3
[error] [scope_2] E3
[fatal] [scope_2] F3
[debug] [scope_2->scope_3] A4
[trace] [scope_2->scope_3] B4
[info] [scope_2->scope_3] C4
[warning] [scope_2->scope_3] D4
[error] [scope_2->scope_3] E4
[fatal] [scope_2->scope_3] F4
[debug] [scope_2] A5
[trace] [scope_2] B5
[info] [scope_2] C5
[warning] [scope_2] D5
[error] [scope_2] E5
[fatal] [scope_2] F5
Output with filtering (as in sample code, only info level and higher, and only those with some scope named "scope_2"):
[info] [scope_2] C3
[warning] [scope_2] D3
[error] [scope_2] E3
[fatal] [scope_2] F3
[info] [scope_2->scope_3] C4
[warning] [scope_2->scope_3] D4
[error] [scope_2->scope_3] E4
[fatal] [scope_2->scope_3] F4
[info] [scope_2] C5
[warning] [scope_2] D5
[error] [scope_2] E5
[fatal] [scope_2] F5
Related
I'm using boost::process library to run different sub-processes from my main process. Recently we have updated to boost 1.77 version from previous 1.71 version. After the update my tool is crashing when io_context run() function is called. The call is made like below.
namespace bp = boost::process;
namespace ba = boost::asio;
ba::io_context _ios;
_child = new bp::child(execPath,
_args,
bp::on_exit=[this](int exit_code, const std::error_code& ec){onExit(exit_code, ec);},
bp::std_out > _outHandler,
bp::std_err > _errHandler,
bp::std_in.close(),
_ios);
_ios.run();
Going deep into the trace it shows that it crashes while calling free() in boost::asio::aligned_delete funtion. Also the crash occurs before the exit handler is called. Any pointers would be very helpful.
Because the code is not enough to diagnose the problem off, here's what I imagined the surrounding code to be like, so you can compare notes.
This code has no problems:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
namespace bp = boost::process;
namespace ba = boost::asio;
using boost::system::error_code;
struct X {
boost::filesystem::path execPath{"/bin/bash"};
std::vector<std::string> _args{
"-c",
"sort -R /etc/dictionaries-common/words | head -30 | nl"};
std::future<std::string> _outHandler, _errHandler;
void foo() {
ba::io_context _ios;
bp::child child(
execPath,
_args,
bp::on_exit =
[this](int exit_code, error_code ec)
{
onExit(exit_code, ec);
},
bp::std_out > _outHandler,
bp::std_err > _errHandler,
bp::std_in.close(),
_ios);
std::cout << "Before run" << std::endl;
_ios.run();
std::cout << "After run" << std::endl;
}
void onExit(int exit_code, error_code ec)
{
std::cout << "Exited with: " << exit_code << " ("
<< ec.message() << ")" << std::endl;
}
};
int main()
{
X x;
x.foo();
std::cout << "err: " << std::quoted(x._errHandler.get()) << std::endl;
std::cout << "out: " << std::quoted(x._outHandler.get()) << std::endl;
}
Prints e.g.
Before run
Exited with: 0 (Success)
After run
err: ""
out: " 1 mainframe
2 congenital
3 entrusted
4 employing
5 sheepishly
6 denuding
7 abjuration
8 descant
9 brawn's
10 happier
11 activism
12 Cheops
13 Taiwan's
14 sublimated
15 cardigans
16 Triton's
17 atlas's
18 penning
19 stink
20 forefeet
21 fusses
22 spectrum's
23 reoccupy
24 replicate
25 rectors
26 preventives
27 catacomb's
28 untainted
29 eightieth
30 testing
"
Like I said, there's not much use extending the lifetime of child but you can. If you replace that line with just
new bp::child(
// ... the same
everything works the same (though obviously the bp::child instance is leaked)
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"
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.
I recently tried to write a scraper capable of downloading 3.5 million
torrent files based on their magnet URL. I decided to start by hacking
an example from libtorrent's tutorial webpage, but while it works well
with just one torrent file, it fails segfaults in create_torrent() when
I try to download more than one file. Here's my code:
#include <thread>
#include <chrono>
#include <fstream>
#include <sstream>
#include <string>
#include <libtorrent/session.hpp>
#include <libtorrent/add_torrent_params.hpp>
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/torrent_status.hpp>
#include <libtorrent/torrent_info.hpp>
namespace lt = libtorrent;
using clk = std::chrono::steady_clock;
int torrents_left = 0;
int save_file(std::string const& filename, std::vector<char>& v)
{
FILE* f = std::fopen(filename.c_str(), "wb");
if (f == nullptr)
return -1;
int w = int(std::fwrite(&v[0], 1, v.size(), f));
std::fclose(f);
if (w < 0) return -1;
if (w != int(v.size())) return -3;
return 0;
}
void add_torrent_url(std::string url, lt::session& ses) {
// std::cerr << "DEBUG: Will download '" << url << "'" << std::endl;
lt::add_torrent_params atp;
atp.url = url;
atp.save_path = "."; // save in current dir
ses.async_add_torrent(atp);
torrents_left++;
}
void add_torrents_from_stdin(lt::session& ses) {
std::cerr << "DEBUG: reading stdin." << std::endl;
std::string url;
while(std::getline(std::cin, url)) {
add_torrent_url(url, ses);
}
std::cerr << "DEBUG: done reading stdin." << std::endl;
}
int main(int argc, char const* argv[])
{
lt::settings_pack pack;
pack.set_int(lt::settings_pack::alert_mask
, lt::alert::error_notification
| lt::alert::storage_notification
| lt::alert::status_notification);
lt::session ses(pack);
lt::add_torrent_params atp;
//add_torrent_url(argv[1]);
add_torrent_url("magnet:?xt=urn:btih:3E37CFE29B1049E03F858758A73EFD85BA170BE8", ses);
add_torrent_url("magnet:?xt=urn:btih:8FCDE178E3F9A24EA40856826C4E8A625A931B73", ses);
//add_torrents_from_stdin(ses);
// this is the handle we'll set once we get the notification of it being
// added
lt::torrent_handle h;
for (;;) {
std::vector<lt::alert*> alerts;
ses.pop_alerts(&alerts);
for (lt::alert const* a : alerts) {
if (auto at = lt::alert_cast<lt::add_torrent_alert>(a)) {
h = at->handle;
}
// if we receive the finished alert or an error, we're done
if (lt::alert_cast<lt::torrent_finished_alert>(a)) {
std::cout << "torrent finished or error." << std::endl;
goto done;
}
if (lt::alert_cast<lt::torrent_error_alert>(a)) {
std::cout << a->message() << std::endl;
goto done;
}
if (auto st = lt::alert_cast<lt::state_update_alert>(a)) {
if (st->status.empty()) continue;
// we only have a single torrent, so we know which one
// the status is for
lt::torrent_status const& s = st->status[0];
if (s.state == lt::torrent_status::downloading)
{
std::cout << "Hi!" << std::endl;
std::shared_ptr<const lt::torrent_info> ti = h.torrent_file();
if (ti == 0) {
std::cerr << "ERROR: ti == NULL" << std::endl;
goto done;
}
ses.remove_torrent(h, lt::session::delete_files);
lt::create_torrent new_torrent(*ti);
std::vector<char> out;
lt::bencode(std::back_inserter(out), new_torrent.generate());
std::stringstream ss;
ss << "downloaded/" << (*ti).info_hash() << ".torrent";
save_file(ss.str(), out);
h.pause();
torrents_left--;
std::cerr << "DEBUG: Done (" << torrents_left << " left): " << (*ti).info_hash() << std::endl;
if (torrents_left == 0)
goto done;
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// ask the session to post a state_update_alert, to update our
// state output for the torrent
ses.post_torrent_updates();
}
done:
{}
}
I suspect it's related to this part:
// we only have a single torrent, so we know which one
// the status is for
lt::torrent_status const& s = st->status[0];
But according to my debugger, when torrent_file() gives NULL, st->status[] only contains one element anyway.
What's happening here? How do I fix it?
It looks like I made wrong assumptions about what "h" points to in the example. Here's a diff that fixes the code in question:
--- scrape_rasterbar.cpp 2017-01-07 21:00:39.565636805 +0100
+++ scrape_rasterbar_old.cpp 2017-01-07 21:05:53.339718098 +0100
## -1,4 +1,3 ##
-#include <iostream>
#include <thread>
#include <chrono>
#include <fstream>
## -94,17 +93,18 ##
if (auto st = lt::alert_cast<lt::state_update_alert>(a)) {
if (st->status.empty()) continue;
- for (auto &s : st->status) {
// we only have a single torrent, so we know which one
// the status is for
+ lt::torrent_status const& s = st->status[0];
if (s.state == lt::torrent_status::downloading)
{
- std::shared_ptr<const lt::torrent_info> ti = s.handle.torrent_file();
+ std::cout << "Hi!" << std::endl;
+ std::shared_ptr<const lt::torrent_info> ti = h.torrent_file();
if (ti == 0) {
std::cerr << "ERROR: ti == NULL" << std::endl;
goto done;
}
- ses.remove_torrent(s.handle, lt::session::delete_files);
+ ses.remove_torrent(h, lt::session::delete_files);
lt::create_torrent new_torrent(*ti);
std::vector<char> out;
lt::bencode(std::back_inserter(out), new_torrent.generate());
## -112,7 +112,7 ##
std::stringstream ss;
ss << "downloaded/" << (*ti).info_hash() << ".torrent";
save_file(ss.str(), out);
- s.handle.pause();
+ h.pause();
torrents_left--;
std::cerr << "DEBUG: Done (" << torrents_left << " left): " << (*ti).info_hash() << std::endl;
if (torrents_left == 0)
## -120,7 +120,6 ##
}
}
}
- }
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// ask the session to post a state_update_alert, to update our
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have written an application in c++ with OpenCV and boost and it is running well and correct, but before exiting it crashes (if I debug step by step, it crashes when return 0; at the end of main) saying:
in kdevelop:
*** Error in `/home/xxx/git_repos/my_proj/build/my_proj': free(): invalid pointer: 0x00000000008939c0 ***
*** Crashed with return code: 0 ***
in command line:
*** Error in `./my_proj': free(): invalid pointer: 0x00000000008939c0 ***
Aborted (core dumped)
if I use gdb:
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
*** Error in `/home/xxx/git_repos/my_proj/build/plate_info_extractor': free(): invalid pointer: 0x00000000008939c0 ***
Program received signal SIGABRT, Aborted.
0x00007ffff5b83cc9 in __GI_raise (sig=sig#entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
Any help please? I cannot figure out what is wrong.
I have commented some code parts and it seems that the problem is in the code that I use to log the errors/info... So:
#include "CLogger.hpp"
static void help(const std::string& appNameIn)
{
std::cerr << "Usage: " << appNameIn << " <option(s)> SOURCES" << std::endl
<< "Options:" << std::endl
<< " -h,--help Show this help message" << std::endl
<< " -c,--config CONFIG FILE Specify the name and path to the config file" << std::endl
<< std::endl
<< std::endl;
}
enum RetVals
{
NO_ERRORS,
NOT_ENOUGH_PARAMETERS,
UNKNOWN_OPTION,
SHOW_USAGE,
UNKNOWN_EXCEPTION,
OTHER_EXCEPTION,
};
int main(int argc, char **argv)
{
Logger::initFilesList();
Logger::initLogging();
BoostLogger lg = setClassNameAttribute("main");
if (argc == 3)
{
std::string opt = argv[1];
if (opt == "-c" || opt == "--config")
{
try
{
std::cout << "running" << std::endl;
}
catch (std::exception& e)
{
BOOST_LOG_SEV(lg, Logger::SeverityLevels::error) << "Other exception: " << e.what();
return OTHER_EXCEPTION;
}
catch (...)
{
BOOST_LOG_SEV(lg, Logger::SeverityLevels::error) << "Unkown exception ...";
return UNKNOWN_EXCEPTION;
}
}
else
{
help(argv[0]);
BOOST_LOG_SEV(lg, Logger::SeverityLevels::debug) << "The options are -c or --config, -h or --help";
return UNKNOWN_OPTION;
}
}
else if (argc == 2 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help"))
{
help(argv[0]);
BOOST_LOG_SEV(lg, Logger::SeverityLevels::debug) << "Call help function";
return SHOW_USAGE;
}
else
{
help(argv[0]);
BOOST_LOG_SEV(lg, Logger::SeverityLevels::debug) << "The number of input parameters is wrong";
return NOT_ENOUGH_PARAMETERS;
}
return NO_ERRORS;
}
and the CLogger.hpp:
#pragma once
#include <list>
#include <string>
#include <boost/log/common.hpp>
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/concept_check.hpp>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
class Logger
{
private:
static std::list< std::string > sm_logFilesList;
static const std::string sm_folderOfLogFiles;
/** the function adds the input log file to the list
*
* #param logFilePathIn : input log file path
*/
static void addNewLogFileToList(const std::string& logFilePathIn, bool sorting = false);
/** The function adds new logs to new log file and remove the oltest ones to have always 2 log files */
static void addNewRemoveOldLogs(bool sorting = false);
/** #returns : if the input path is valid or not */
static bool checkPath(const boost::filesystem::path& pathIn);
/** #returns if the file with the first input path was more recent accesed than the file with the second input path
*/
static bool compareAccessTime(const std::string& path1In, const std::string& path2In);
/** The function remove the old log files and keeps just the most recent two */
static void removeOldLogFiles();
/** The function is calles at the end of each file and removes the old files keeping just the last two */
static void openingHandler(boost::log::sinks::text_file_backend::stream_type& file);
public:
enum SeverityLevels
{
debug,
info,
warning,
error
};
/** The function sets the sm_logFilesList to contain the files if there are log files from earlier runs */
static void initFilesList();
/** The fuction is initializing the sinks and the formatter of the logger */
static void initLogging();
};
typedef boost::log::sources::severity_logger< Logger::SeverityLevels > BoostLogger;
/** the overloaded << operator of the BoostLogger */
std::ostream& operator<< (std::ostream& strm, Logger::SeverityLevels level);
/** The function sets the logger attribute ClassName to the specified string and returns the initialized logger
*
* #param classNameIn : input string name of the class
*
* #returns : initialized BoostLogger
*/
BoostLogger setClassNameAttribute(const std::string& classNameIn);
and the implementation:
#include "CLogger.hpp"
#include <boost/log/attributes.hpp>
#include <boost/log/attributes/current_process_id.hpp>
#include <boost/log/attributes/current_process_name.hpp>
#include <boost/log/core/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_file_backend.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/utility/setup/file.hpp>
typedef boost::log::sinks::synchronous_sink< boost::log::sinks::text_file_backend > SinkTextFileBakend;
BOOST_LOG_ATTRIBUTE_KEYWORD(correlid, "CorrelationID", std::string)
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", Logger::SeverityLevels)
BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(threadid, "ThreadID", boost::log::attributes::current_thread_id)
BOOST_LOG_ATTRIBUTE_KEYWORD(classname, "ClassName", std::string)
std::list< std::string > Logger::sm_logFilesList;
const std::string Logger::sm_folderOfLogFiles = "../logs/";
std::ostream& operator<<(std::ostream& strm, Logger::SeverityLevels level)
{
static const char* strings[] =
{
"DEBUG",
"INFO",
"WARN",
"ERROR"
};
if (static_cast< std::size_t >(level) < sizeof(strings) / sizeof(*strings))
strm << strings[level];
else
strm << static_cast< int >(level);
return strm;
}
BoostLogger setClassNameAttribute(const std::string& classNameIn)
{
BoostLogger lg;
lg.add_attribute("ClassName", boost::log::attributes::constant<std::string>(classNameIn));
return lg;
}
void Logger::removeOldLogFiles()
{
while (sm_logFilesList.size() > 2)
{
std::string fileToDelete = sm_logFilesList.front();
if (fs::exists(fs::path(fileToDelete)))
{
fs::remove(fs::path(fileToDelete));
sm_logFilesList.pop_front();
}
}
}
bool Logger::compareAccessTime(const std::string& path1In, const std::string& path2In)
{
return (fs::last_write_time(fs::path(path1In)) < fs::last_write_time(fs::path(path2In)));
}
void Logger::addNewLogFileToList(const std::string& logFilePathIn, bool sorting)
{
if (std::find(sm_logFilesList.begin(), sm_logFilesList.end(), logFilePathIn) == sm_logFilesList.end())
{
sm_logFilesList.push_back(logFilePathIn);
if (sorting)
{
sm_logFilesList.sort(compareAccessTime);
}
removeOldLogFiles();
}
}
void Logger::addNewRemoveOldLogs(bool sorting)
{
fs::path path(sm_folderOfLogFiles);
fs::directory_iterator endDir;
if (checkPath(path))
{
for (fs::directory_iterator it(path); it != endDir; it++)
{
if (fs::is_regular_file(it->status()))
{
if (fs::extension(*it) == ".log")
{
std::string fileToPush = it->path().string();
addNewLogFileToList(fileToPush, sorting);
}
}
}
}
}
bool Logger::checkPath(const boost::filesystem::path& pathIn)
{
if (!fs::exists(pathIn))
{
return false;
}
if (!fs::is_directory(pathIn))
{
return false;
}
return true;
}
void Logger::openingHandler(boost::log::sinks::text_file_backend::stream_type& file)
{
addNewRemoveOldLogs();
}
void Logger::initFilesList()
{
addNewRemoveOldLogs(true);
}
void Logger::initLogging()
{
// Create a backend
boost::shared_ptr< SinkTextFileBakend > sink = boost::log::add_file_log(
sm_folderOfLogFiles + "plate_info_extractor_%Y-%m-%d_%H-%M-%S.%N.log",
boost::log::keywords::format = boost::log::expressions::stream
<< boost::log::expressions::attr< boost::log::attributes::current_process_name::value_type >("Executable") << "("
<< boost::log::expressions::attr< boost::log::attributes::current_process_id::value_type >("ExeUID") << ") CID("
<< correlid << ") " << severity << "["
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f")
<< "] [" << boost::log::expressions::attr< boost::log::attributes::current_thread_id::value_type >("ThreadID")
<< "] " << classname << " - " << boost::log::expressions::smessage,
boost::log::keywords::open_mode = (std::ios::out | std::ios::app),
boost::log::keywords::rotation_size = 2 * 1024 * 1024,
boost::log::keywords::auto_flush = true
);
sink->locked_backend()->set_open_handler(&openingHandler);
boost::log::core::get()->set_filter(severity >= SeverityLevels::info);
boost::log::core::get()->add_sink(sink);
#ifdef DEBUGGING
boost::shared_ptr< boost::log::sinks::text_ostream_backend > backend =
boost::make_shared< boost::log::sinks::text_ostream_backend >();
backend->add_stream(
boost::shared_ptr< std::ostream >(&std::cout));
// Enable auto-flushing after each log record written
backend->auto_flush(true);
// Wrap it into the frontend and register in the core.
// The backend requires synchronization in the frontend.
// for testing and printing log in sysout
boost::shared_ptr< boost::log::sinks::synchronous_sink< boost::log::sinks::text_ostream_backend > > backend_sink(
new boost::log::sinks::synchronous_sink< boost::log::sinks::text_ostream_backend >(backend));
backend_sink->set_formatter(
boost::log::expressions::stream
// line id will be written in hex, 8-digits, zero-filled
<< boost::log::expressions::attr< boost::log::attributes::current_process_name::value_type >("Executable")
<< "(" << boost::log::expressions::attr< boost::log::attributes::current_process_name::value_type >("ExeUID")
<< ") CID(" << correlid << ") " << severity << " ["
<< boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f")
<< "] [" << boost::log::expressions::attr< boost::log::attributes::current_thread_id::value_type >("ThreadID")
<< "] " << classname << " - " << boost::log::expressions::smessage);
boost::log::core::get()->add_sink(backend_sink); // for testing and printing log in sysout
#endif
}
I hope that now there is enough information :)
This line:
backend->add_stream(boost::shared_ptr< std::ostream >(&std::cout));
looks pretty disastrous me. Your program will attempt to delete std::cout when exiting. I'm not familiar with Boost Log, so I don't know how to properly set up a sink for std::cout.
As Arpegius pointed out you should use the null_deleter to avoid deletion. Something along the lines of this:
backend->add_stream(boost::shared_ptr< std::ostream>(&std::cout, boost::null_deleter()));