Question

Can I use LineID attribute for this? I hope I could use sink::set_formatter to do this instead of using

__LINE__

and

__FILE__

in each log statement.

Was it helpful?

Solution

I struggled with this, until I found this snippet

#define LFC1_LOG_TRACE(logger) \
BOOST_LOG_SEV(logger, trivial::trace) << "(" << __FILE__ << ", " << __LINE__ << ") "

Works like a charm

OTHER TIPS

The LineID attribute is a sequential number that is incremented for each logging message. So you can't use that.

You can use attributes to log the line numbers etc. This allows you flexible formatting using the format string, whereas using Chris' answer your format is fixed.

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) do {                           \
    LOG_LOCATION;                                                       \
    BOOST_LOG_SEV(_log, boost::log::trivial::severity_level::info) << message; \
  } while (false)

#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.

The do {... } while(false) is to make the macro semantically neutral.

The solution shown by Chris works, but if you want to customize the format or choose which information appears in each sink, you need to use mutable constant attributes:

   logging::core::get()->add_global_attribute("File", attrs::mutable_constant<std::string>(""));
   logging::core::get()->add_global_attribute("Line", attrs::mutable_constant<int>(0));

Then, you make a custom macro that includes these new attributes:

// 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_global_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 next complete source code create two sinks. The first uses File and Line attributes, the second not.

#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_global_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_global_attribute("File", attrs::mutable_constant<std::string>(""));
   logging::core::get()->add_global_attribute("Line", attrs::mutable_constant<int>(0));

   // A file log with time, severity, filename, line and message
   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
    )
   );
   // A console log with only time and message
   logging::add_console_log (
    std::clog,
    keywords::format = (
     expr::stream
      << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
      << " | " << 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;
}

The statement CUSTOM_LOG(lg, debug) << "A regular message"; generates two outputs, writing a log file with this format...

2015-10-15_15:25:12.743153: <debug> [main.cpp:61] A regular message

...and outputs to the console this:

2015-10-15 16:58:35 | A regular message

Another possibility is to add line and file attributes to each log record after they are created. This is possible since in newer releases. Attributes added later do not participate in filtering.

Assuming severity_logger identified with variable logger:

boost::log::record rec = logger.open_record(boost::log::keywords::severity = <some severity value>);
if (rec)
{
    rec.attribute_values().insert(boost::log::attribute_name("Line"),
        boost::log::attributes::constant<unsigned int>(__LINE__).get_value());
    ... other stuff appended to record ...
}

The above would, of course, get wrapped into convenient macro.

Later you can show this attribute using custom formatter for the sink:

sink->set_formatter( ...other stuff... << expr::attr<unsigned int>("Line") << ...other stuff... );

Unlike previous answer, this approach requires more custom code and can't use off-the-shelf boost logging macros.

For posterity's sake - I made this set of macros for very simple logging needs, which has served me well - for simple logging needs. But they illustrate how to do this in general, and the concept easily works with Boost. They are meant to be local to one file (which is running in multiple processes, sometimes in multiple threads in multiple processes). They are made for relative simplicity, not speed. They are safe to put in if statements etc. to not steal the else. At the beginning of a function in which one wants to log, one calls

GLogFunc("function name");

Then one can do this to log a complete line:

GLogL("this is a log entry with a string: " << some_string);

They are as so -

#define GLogFunc(x)     std::stringstream logstr; \
                        std::string logfunc; \
                        logfunc = x

#define GLog(x)         do { logstr << x; } while(0)

#define GLogComplete    do { \
                            _log << "[PID:" << _my_process << " L:" << __LINE__ << "] ((" << logfunc << ")) " << logstr.str() << endl; \
                             logstr.str(""); \
                             _log.flush(); \ 
                        } while(0)

#define GLogLine(x)     do { GLog(x); GLogComplete; } while(0)
#define GLogL(x)        GLogLine(x)
#define GLC             GLogComplete

One can also build up a log over a few lines...

GLog("I did this.");
// later
GLog("One result was " << some_number << " and then " << something_else);
// finally
GLog("And now I'm done!");
GLogComplete;

Whatever stream _log is (I open it as a file in the class constructor, which is guaranteed to be safe in this instance) gets ouput like this:

[PID:4848 L:348] ((SetTextBC)) ERROR: bad argument row:0 col:-64

And they can be conditionally turned off and all performance penalty negated by a symbol at compilation time like so:

#ifdef LOGGING_ENABLED
... do the stuff above ...
#else

#define GLogFunc(x)
#define GLog(x)
#define GLogComplete
#define GLogLine(x) 
#define GLogL(x)

#endif

Here is my solution.

Setup code

auto formatter =
    expr::format("[ %3% %1%:%2% :: %4%]")
    % expr::attr< std::string >("File")
    % expr::attr< uint32_t >("Line")
    % expr::attr< boost::posix_time::ptime >("TimeStamp")
    % expr::smessage
    ;

/* stdout sink*/
boost::shared_ptr< sinks::text_ostream_backend > backend =
    boost::make_shared< sinks::text_ostream_backend >();
backend->add_stream(
    boost::shared_ptr< std::ostream >(&std::clog, NullDeleter()));

// 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.
typedef sinks::synchronous_sink< sinks::text_ostream_backend > sink2_t;
boost::shared_ptr< sink2_t > sink_text(new sink2_t(backend));

logging::add_common_attributes();

sink_text->set_formatter(formatter);

The log usage code (short version):

rec.attribute_values().insert("File", attrs::make_attribute_value(std::string(__FILE__))); \

full version :

#define LOG(s, message) { \
  src::severity_logger< severity_level > slg; \
  logging::record rec = slg.open_record(keywords::severity = s); \
  if (rec) \
  { \
    rec.attribute_values().insert("File", attrs::make_attribute_value(boost::filesystem::path(__FILE__).filename().string())); \
    rec.attribute_values().insert("Line", attrs::make_attribute_value(uint32_t(__LINE__))); \
    logging::record_ostream strm(rec); \
    strm << message; \
    strm.flush(); \
    slg.push_record(boost::move(rec)); \
  } \

}\

If I define global attribute (like people adviced before), i.e.

logging::core::get()->add_global_attribute("File", attrs::mutable_constant<std::string>(""));

then I get empty files/stiring.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top