10

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.

Dean Chen
  • 3,800
  • 8
  • 45
  • 70
  • 1
    This should be so obvious, but it is not. I've been banging my head over this for quite a while now. Any luck since you posted your question ? If you found out, answer your own question and help a fellow ! – ixe013 Mar 11 '14 at 02:24
  • See also https://stackoverflow.com/questions/22095667/how-to-log-line-number-of-coder-in-boost-log-2-0 – Jason Harrison Nov 26 '18 at 22:04

6 Answers6

12

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

Chris
  • 1,532
  • 10
  • 19
  • I tried name scope, it works but more complex. see my code in my post. http://blog.csdn.net/csfreebird/article/details/20213349#t9 I have the same thought like you, just define my own Macros and use __FILE__, __LINE__ – Dean Chen Apr 01 '14 at 03:15
  • Yes, after posting I went down that route too. Ended up with something a bit different than you as I have the named_scope as part of my formatter. It can then produce nested scope calls. – Chris Apr 01 '14 at 19:34
  • It should be noted that this solution, albeit really simple, decentralizes the log formatting control. Using the `named_scope` as suggested by @DeanChen or the `global_attributes` as suggested below keep the formatting all in one place, under the control of the Boost.Log library. – mattdibi Feb 13 '19 at 10:54
7

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.

Horus
  • 617
  • 7
  • 10
5

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
Community
  • 1
  • 1
Guillermo Ruiz
  • 401
  • 5
  • 8
  • There was program termination problem with above code in the program that utilizes multi threads. The program ended up and break point was in the middle of boost log functions in the Visual Studio. After I stop using above method, the problem was gone. Caution is required in multi threads environment. – Hill Nov 16 '17 at 21:09
  • 1
    @Hil For multi threaded environments, you need `severity_logger_mt`, not `severity_logger`. Every loggers have a multi thread variant (_mt postfix). – Samuel Jan 21 '20 at 10:21
  • there is termination problem when setting line value....it happens when swapping line value with rhs template parameter. i do not know how to solve it – ahmed allam Mar 12 '20 at 14:44
  • there is termination problem when setting line value....it happens when swapping line value with rhs template parameter. i do not know how to solve it...Exception thrown: read access violation. **rhs** was 0x8. – ahmed allam Mar 12 '20 at 14:51
  • 1
    @ahmedallam You need to replace this `(set_get_attrib("Line", __LINE__))` with `(set_get_attrib("Line", (int)__LINE__))` – gyro Nov 29 '20 at 22:28
1

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.

Leon
  • 89
  • 1
  • 2
0

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
std''OrgnlDave
  • 3,912
  • 1
  • 25
  • 34
0

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.

Ivan Baidakou
  • 713
  • 9
  • 14