kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

Boost.Logでファイルに出力する

Log出力するならやっぱりファイルに出力したいので、ファイル出力の設定をします。Log用のライブラリを使うなら、ログローテーションを活用しない手は無いですね。

出力用のbackendはtext_file_backendになるので、既定どおりbackend -> sink -> loggerに設定の流れで組んでもよいですが、TRIVIALで取り回す場合、次のメソッドを使うのが簡単です。

  • boost::log::add_file_log

backendやsinkの準備の必要が無く、ファイル名や出力フォーマット、そしてログローテーションの設定を一気に行うことができます。

boost::log::add_file_log

サポート範囲内でなるたけ好みの出力が得られるように設定します。

  • ファイル名は日付
  • 日付が変わった段階で別ファイルに
  • 一定の容量になったら、古いものから消していく
logging::add_file_log(
  keywords::auto_flush = true,
  keywords::open_mode = std::ios::app,
  keywords::target = "log",
  keywords::file_name = "log/app_%Y%m%d.log",
  keywords::time_based_rotation
    = sinks::file::rotation_at_time_point(0, 0, 0),
  keywords::max_size = 10 * 1024 * 1024,
  keywords::format = expr::format("%1%\t%2%\t%3%")
    % expr::format_date_time<posix_time::ptime>(
      "TimeStamp", "%Y-%m-%d %H:%M:%S")
    % logging::trivial::severity
    % expr::message
    );

...

logging::core::get()->remove_all_sinks();

ちっと、出力フォルダ、パス、サイズが適当ですが。

最後のremove_all_sinksは呼び出しておかないと、main関数を抜けた後にプログラムが落ちる場合があります。

backendの破棄時にローテーションが行われるのですが、logging::coreは(たぶん)グローバル空間に存在するため、main関数を抜けた後に処理されます。ところが、このタイミングだとローテーションに必要なインスタンスがすでに開放されているらしく、main関数を抜けて、ローテーションが実行されるタイミングでプログラムが不正終了する場合がありました。

そのため、明示的にbackendの破棄処理が実行されるように細工します。ただ、sinkやbackendをいちいち保持して...というのも手軽さが薄れるので、main関数を抜ける間際に、remove_all_sinksを呼び出し、ローテーションの実行と、sinkの登録解除を行います。

その他こまごまと

ローテーションの動作など、こまごま調べて気になったものがいくつかあるので、メモしておきます。

  • ファイル数でのローテーションは無い
  • file_nameで指定された場所に書き出され、ローテーションのタイミングでtargetに集められる
  • %Nで、連番をつけた場合、起動毎にインクリメントされる

特に2つめの動きは同名ファイルへの追記処理や、ローテーションの動作などに影響するので、動作を把握しておいたほうがよいです。

これまでの処理と組み合わせる

これまで組んだ、コンソールへの出力、Visual Studioへの出力と組み合わせます。ここまでくると、とりあえず使うのに不便はないかなーといった感じです。

#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>

#include <boost/log/expressions.hpp>
#include <boost/log/sinks.hpp>
#include <boost/log/sinks/debug_output_backend.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/utility/empty_deleter.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/trivial.hpp>

int main() {
  using namespace boost;
  namespace logging = boost::log;
  namespace expr = boost::log::expressions;
  namespace sinks = boost::log::sinks;
  namespace keywords = boost::log::keywords;

  { // for Visual Studio
    shared_ptr<sinks::debug_output_backend> backend =
      make_shared<sinks::debug_output_backend>();
    
    typedef sinks::synchronous_sink<sinks::debug_output_backend> sink_t;
    shared_ptr<sink_t> sink(new sink_t(backend));

    sink->set_formatter(
    expr::format("%1%\t%2%\t%3%\n")
      % expr::format_date_time<posix_time::ptime>(
        "TimeStamp", "%H:%M:%S")
      % logging::trivial::severity
      % expr::message
    );
	
    logging::core::get()->add_sink(sink);
  }

  { // for std::clog
    shared_ptr<sinks::text_ostream_backend> backend =
      make_shared<sinks::text_ostream_backend>();
    backend->add_stream(shared_ptr<std::ostream>(
		&std::clog, logging::empty_deleter()));
    backend->auto_flush(true);

    typedef sinks::synchronous_sink<sinks::text_ostream_backend> sink_t;
    shared_ptr<sink_t> sink(new sink_t(backend));

    sink->set_formatter(
      expr::format("%1% [%2%] %3%")
      % expr::format_date_time<posix_time::ptime>(
	    "TimeStamp", "%Y-%m-%d %H:%M:%S")
      % logging::trivial::severity
      % expr::message
      );

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

  { // for file
    logging::add_file_log(
    keywords::auto_flush = true,
    keywords::open_mode = std::ios::app,
    keywords::target = "log",
    keywords::file_name = "log/app_%Y%m%d.log",
    keywords::time_based_rotation
      = sinks::file::rotation_at_time_point(0, 0, 0),
    keywords::max_size = 10 * 1024 * 1024,
    keywords::format = expr::format("%1%\t%2%\t%3%")
      % expr::format_date_time<posix_time::ptime>(
        "TimeStamp", "%Y-%m-%d %H:%M:%S")
      % logging::trivial::severity
      % expr::message
      );
  }

  logging::add_common_attributes();

#ifdef _DEBUG
  // do nothing
#else
  logging::core::get()->set_filter(
    logging::trivial::severity >= logging::trivial::info
    );
#endif

  BOOST_LOG_TRIVIAL(trace) << "trace message";
  BOOST_LOG_TRIVIAL(debug) << "debug";
  BOOST_LOG_TRIVIAL(info) << "info message";
  BOOST_LOG_TRIVIAL(warning) << "warning message";
  BOOST_LOG_TRIVIAL(error) << "error message";
  BOOST_LOG_TRIVIAL(fatal) << "fatal message";

  logging::core::get()->remove_all_sinks();
}