0

I have a tracer, that should log going in and out of a function:

class Tracer
{
public:  
  Tracer(Logger logger, std::string function, std::string file)
    :
    _logger(logger),
    _function(function),
    _file(file)
  {}

  ~Tracer()
  {
    using namespace boost::log;
    // attribute_cast<attributes::mutable_constant<int>>(core::get()->get_global_attributes()["Line"]).set(1);
    attribute_cast<attributes::mutable_constant<std::string>>(core::get()->get_global_attributes()["File"]).set(_file);
    attribute_cast<attributes::mutable_constant<std::string>>(core::get()->get_global_attributes()["Function"]).set(_function);
    BOOST_LOG_SEV(_logger, trivial::severity_level::trace) << "Leaving " << _function;
  }

private:
  Logger _logger;
  std::string _function;
  std::string _file;
};

This works fine, but as soon as I uncomment the line that sets the Line attribut I don't get any Leaving messages anymore.

The attributes are added beforehand like that:

using namespace boost::log;
add_common_attributes();
core::get()->add_global_attribute("Scope", attributes::named_scope());
core::get()->add_global_attribute("Rank", attributes::constant<int>(5));
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 all other attributes, similiar to Line works fine. I'm puzzled because Line does not seem to be a reserved name, it works somewhere else and the boost::log sources reveal no relevant usages of that name.

What could be the problem here? I'm happy to send anyone the complate code (approx. 150 lines in total)

Horus
  • 617
  • 7
  • 10

1 Answers1

0

The main problem with your code is that get_global_attributes() returns a copy of the global attribute set. Whatever changes you do to the copy does not affect the attributes you see attached to log records. And if your code is multi-threaded then using global attributes is wrong in the first place because the execution context is thread-specific. Also, assuming that attribute_cast succeeds (i.e. does not return an empty attribute) presumes that the attribute is never changed anywhere in the program. You should at least check the result of attribute_cast.

A better approach to achieve what you want is to add the Line and Function attributes to the thread-specific attribute set in the Tracer constructor instead of your logging initialization code, and restore their previous state in destructor. The restoring part is important is you plan to nest calls marked with Tracer.

using namespace boost::log;

class Tracer
{
  enum restore_action
  {
    noop,
    restore_prev,
    remove
  };

public:  
  Tracer(
    Logger const& logger,
    std::string const& function,
    std::string const& file,
    int line)
    :
    _logger(logger),
    _function_name(function),
    _prev_line_number(0),
    _function(set_attribute("Function", function, _prev_function_name)),
    _file(set_attribute("File", file, _prev_file_name)),
    _line(set_attribute("Line", line, _prev_line_number))
  {
    BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
      << "Entering " << _function_name;
  }

  ~Tracer()
  {
    BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
      << "Leaving " << _function_name;

    restore_attribute(_function, _prev_function_name);
    restore_attribute(_file, _prev_file_name);
    restore_attribute(_line, _prev_line_number);
  }

private:
  template< typename T >
  static std::pair< attribute_set::iterator, restore_action >
    set_attribute(attribute_name name, T const& value, T& prev_value)
  {
    typedef attributes::mutable_constant< T > mutable_constant_t;
    core_ptr p = core::get();

    std::pair< attribute_set::iterator, bool > res =
      p->add_thread_attribute(name, mutable_constant_t(value));
    if (res.second)
      return std::make_pair(res.first, remove);

    mutable_constant_t prev =
      attribute_cast< mutable_constant_t >(res.first->second);
    if (!prev)
      return std::make_pair(res.first, noop);

    prev_value = prev.get();
    prev.set(value);
    return std::make_pair(res.first, restore_prev);
  }

  template< typename T >
  static void restore_attribute(
    std::pair< attribute_set::iterator, restore_action > const& state,
    T const& prev_value)
  {
    typedef attributes::mutable_constant< T > mutable_constant_t;

    switch (state.second)
    {
    case restore_prev:
      {
        mutable_constant_t attr =
          attribute_cast< mutable_constant_t >(state.first->second);
        if (attr)
          attr.set(prev_value);
      }
      break;

    case remove:
      core::get()->remove_thread_attribute(state.first);
      break;

    case noop:
    default:
      break;
    }
  }

private:
  Logger _logger;
  std::string _function_name;
  std::string _prev_function_name, _prev_file_name;
  int _prev_line_number;
  std::pair< attribute_set::iterator, restore_action > _function;
  std::pair< attribute_set::iterator, restore_action > _file;
  std::pair< attribute_set::iterator, restore_action > _line;
};

Note that this code will mark all records made by this thread with Function, File and Line attributes as they are set by the Tracer. If that is not what you actually want and you only need to mark the messages about entering and leaving the function then you should add Function, File and Line to the logger attributes instead of thread-specific ones. This will significantly simplify code as well since you won't have to bother with restoring the previous state of the attributes.

I shall also add that there are better tools for maintaining call stack and in particular marking the current execution context. Named scopes allow you to mark the current scope or function in a much cheaper way than what is written above. The markup does not produce log records, but that is simple to add.

class Tracer
{
public:  
  Tracer(
    Logger const& logger,
    string_literal const& function,
    string_literal const& file,
    unsigned int line)
    :
    _logger(logger),
    _function_name(function),
    _scope_sentry(function, file, line)
  {
    BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
      << "Entering " << _function_name;
  }

  ~Tracer()
  {
    BOOST_LOG_SEV(_logger, trivial::severity_level::trace)
      << "Leaving " << _function_name;
  }

private:
  Logger _logger;
  string_literal _function_name;
  attributes::named_scope::sentry _scope_sentry;
};

Compared to the original code this version has the additional limitation that the function and file names must be string literals and not just arbitrary strings. You will also have to set up the formatter differently.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • Thanks for you reply which I - sorry - do not fully grok. My complete code is at https://gist.github.com/floli/9f2c5de3fafebcda0d74 My orginal intention was to add line numbers and files to log messages. Scopes just give you the line number of were I opened the scope, not the one of the actual BOOST_LOG_SEV call. I think that's something should be standard inside a logging library and be hacked into it from the user. To achieve that I used the macro around line 70. Next I want to have this tracer, which can't use the __LINE__ and __FILE__ so I wanted to set it manually. – Horus Oct 16 '15 at 13:37
  • In that case I did not understand your question. If you want to mark the location of each logging statement then the best way to do it is to use the `add_value` manipulator (http://www.boost.org/doc/libs/1_59_0/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.manipulators.add_value). To automate its use you can define your own logging macro that will wrap `BOOST_LOG_SEV` and `add_value` and use it throughout your code. It's not possible to implement this as a special attribute or logger. – Andrey Semashev Oct 16 '15 at 15:33
  • This place, since it's has very limited message length does not seem to be the right place to discuss that. Can you be reached somewhere else? – Horus Oct 21 '15 at 09:19