0

I am using this code snippet to create a Boost logger:

#include <boost/log/common.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/utility/empty_deleter.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::log;

int main()
{
  typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
  boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);

  core::get()->add_sink(sink);

  sources::logger lg;

  BOOST_LOG(lg) << "note";
  sink->flush();
}

I want to log it to a file. I found a solution for this explained here on SO. The name for the log file shall be read from the command line (the class is handling the command line is based on Boost program_options and works). I am also using a try/catch block to capture errors. My main() now looks like this:

int main(int argc, char* argv[])
{
  try
  {
    CConfig cConfig(argc, argv[]);

    // Get the rdbuf of clog.
    // We need it to reset the value before exiting.
    auto old_rdbuf = std::clog.rdbuf();

    if(cConfig.hasLogfile())
    {
      std::ofstream out(cConfig.getLogfile());

      // Set the rdbuf of clog.
      std::clog.rdbuf(out.rdbuf());
    }

    typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
    boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

    boost::shared_ptr<std::ostream> stream{&std::clog,
    boost::empty_deleter{}};
    sink->locked_backend()->add_stream(stream);

    core::get()->add_sink(sink);

    sources::logger lg;

    BOOST_LOG(lg) << "note";
    sink->flush();

    if(cConfig.hasLogfile())
    {
      // Reset the rdbuf of clog.
      std::clog.rdbuf(old_rdbuf);
    }
  }
  catch (std::exception& cException)
  {
    std::cerr << "Error: " << cException.what() << std::endl;
    return 71;
  }
  catch (...)
  {
    std::cerr << "A fatal exception error has occurred." << std::endl;
    return 71;
  }
return 0;
}

Now, for good object-oriented style, I want to extract the code into its own class, so that only the following should remain on my main.cpp. The logger should also log the errors:

int main(int argc, char* argv[])
{
  CLogger cLogger();
  try
  {
    CConfig cConfig(argc, argv);
    cLogger.init(cConfig);
    cLogger.log("note");
  }
  catch (std::exception& cException)
  {
    std::cerr << "Error: " << cException.what() << std::endl;
    cLogger.log(cException.what());
    return 71;
  }
  catch (...)
  {
    std::cerr << "A fatal exception error has occurred." << std::endl;
    cLogger.log("A fatal exception error has occurred.");
    return 71;
  }
cLogger.finish();
return 0;
}

My problem is how to pass the variables sink and old_rdbuf from init() to finish()? In Java I would just pass them as class member as follows:

// logger.h

#include …
using namespace boost::log;
typedef sinks::asynchronous_sink<sinks::text_ostream_backend> text_sink;
class CLogger { … }

// logger.cpp

// Declare (private) class member variables:

boost::shared_ptr<text_sink> sink;
auto old_rdbuf;

void CLogger::init(CConfig &cConfig)
{
  if (cConfig.hasLogfile())
  {
    std::ofstream out(cConfig.getLogfile());

    // Get the rdbuf of clog.
    // We need it to reset the value before exiting.
    old_rdbuf = std::clog.rdbuf();

    // Set the rdbuf of clog.
    std::clog.rdbuf(out.rdbuf());
  }

  sink = boost::make_shared<text_sink>();

  boost::shared_ptr<std::ostream> stream{&std::clog,
  boost::empty_deleter{}};
  sink->locked_backend()->add_stream(stream);

  core::get()->add_sink(sink);
}

void CLogger::log(char* text)
{
  sources::logger lg;
  BOOST_LOG(lg) << text;
}

void CLogger::finish()
{
  sink->flush();
  if(old_rdbuf != null)
  {
    std::clog.rdbuf(old_rdbuf);
  }
}

As far as I understand it, this will not work, because sink is an object which will be destroyed when the body block of init() is closed, and the member variable sink then will then point somewhere into memory where the object isn’t anymore (an invalid pointer, but not null). Am I right? Second, the compiler does not accept my member declaration of old_rdbuf. How do I pass the two correctly to finish()?

Side question, is there a more direct way to pass the file name (std::string CConfig.getLogfile()) to the logger (as an ostream)? I tried

boost::shared_ptr<std::ostream> stream{cConfig.getLogfile(), boost::empty_deleter{}};

and

boost::shared_ptr<std::ostream> stream{&cConfig.getLogfile(), boost::empty_deleter{}};

but that does not work at all. One more question: Why are the constructors for stream and empty_deleter written with braces, I would have expected parentheses?

Community
  • 1
  • 1
Matthias Ronge
  • 9,403
  • 7
  • 47
  • 63
  • When `sink` and `old_rdbuf` are members of `CLogger`, they are accessible by all member functions. When you want them to be private members, create getters/setters for them. Where is the problem? – Torbjörn Aug 15 '16 at 08:38
  • 1.) As far as I understand it, the sink object will be destroyed when the body block of init() is closed. So `sink->flush()` will be called on uninitialized memory. Am I wrong about that? 2.) The declaration of `old_rdbuf` does not compile with the error *a symbol whose type contains 'auto' must have an initializer*. – Matthias Ronge Aug 15 '16 at 08:58
  • If the `CLogger` object holds the sink as a member, the `sink` shared pointer will be valid (at least) as long as the logger object lives. Read up on shared pointers. – Torbjörn Aug 15 '16 at 09:02
  • 2
    Instead of an `init` function, use a **constructor**. Instead of a `finish` function, use a **destructor**. The Boost logger thing looks like an exercise in über-silly complexity, I'd stay away from it. – Cheers and hth. - Alf Aug 15 '16 at 09:14
  • I agree to not using the logging framework for a first success. I cannot use the constructor because the object must be constructed outside the try/catch block to be available in the catch cases. It can however not be initialised there, because parsing the command line is done inside the try block, and the command line argument is used to set the file name. It would be clear how to create the needed class from the constructor. This is exactly my question how to create a class in a function, and store it in a class member variable. – Matthias Ronge Aug 15 '16 at 11:46

0 Answers0