たびとの旅路 ~電脳砂漠の冒険譚~

フロッピー頼りに歩き、クラウドの地平を見つめる今日まで。見つけたオアシス、迷い込んだ砂の迷宮、全てこの羊皮紙に。

羊皮紙に、魂を刻む ~POCOフレームワークとロギング機能の戦いの記録~

旅の途中、興味深いオアシスを見つけた。忘れないうちに、この羊皮紙に記しておくとしよう。

前回の旅で、我々はC++とPOCOフレームワークを使い、お手軽なRedfishシミュレータという名のゴーレムを創り出した。しかし、ただ動くだけのゴーレムは、無口で何を考えているかわからない。実用的な相棒とするには、その「声」を聴く術が必要だ。そう、「ログ出力」という名の魂が。

今回は、このゴーレムにPOCOが提供する強力なロギング機能「Poco::Logger」を組み込み、真に頼れる相棒へと進化させる。正直に告白すると、この作業は想像を絶する苦難の道だった。日本語の古文書はほぼ存在せず、公式の暗号めいたサンプルコードと格闘する日々…。しかし、その砂漠の果てに、私はついに誰でもコピペで使える「正解」という名のオアシスに辿り着いたのだ。

この羊皮紙のあらまし

この羊皮紙が導く者

  • C++という広大な砂漠で、信頼できるロギング・ライブラリという名の水場を探している探求者
  • POCOフレームワークという強力な魔法を、さらに使いこなしたいと願う魔法使い
  • 自ら創り出したゴーレム(ツール)に、魂を吹き込みたいと願う全ての創造主

魂を宿す儀式の基本

まず、Poco::Loggerがどのように動作するのか、その仕組みを理解しよう。

魔法の起点:Applicationクラス

POCOの世界では、Poco::Util::Applicationクラスがすべての土台となる。このクラスのinitialize()メソッド内でloadConfiguration()という呪文を唱えることで、外部の羊皮紙(設定ファイル)を読み込み、ログの出力先やフォーマットを意のままに操れるようになるのだ。

声を聴く作法

Applicationを継承したクラス内では、logger()メソッドで簡単にゴーレムの声を聴ける。

// ゴーレムの中心核から声を聴く
logger().information("ゴーレム、起動しました。");

それ以外の部品からは、Poco::Logger::get()で特定の声(ロガー)を取得する。

// ゴーレムの腕から声を聴く
Poco::Logger& logger = Poco::Logger::get("golem_arm");
logger.information("リクエストを処理しました。");

作成場所の記録も残したいなら、poco_informationのようなマクロが便利だ。

// 羊皮紙のどこで声がしたかを自動で記録する
poco_information(logger(), "ゴーレム、起動しました。");

【重要】printf形式という名の流砂 printfのように書式指定でログを出力できるが、ここに恐ろしい罠がある。%sstd::string型を期待するため、文字列リテラル("...")を直接渡すと、フォーマットエラーという名の流砂に飲み込まれる。

// NG: 文字列リテラルは char* と解釈され、流砂にハマる
poco_information_f(logger(), "リクエストURI: %s", request.getURI());

// OK: std::stringに変換するか、string型の変数を渡す
poco_information_f(logger(), "リクエストURI: %s", std::string(request.getURI()));

特にtry-catchブロック内でこのミスを犯すと、ゴーレムの断末魔さえ記録できなくなる。細心の注意が必要だ。

魂の設計図:ログ設定ファイルの書き方

Poco::Loggerの真価は、この設計図によって発揮される。 実行ファイルと同じ場所に<実行モジュール名>.propertiesという名の羊皮紙を置こう。

リリース用設計図(本番稼働のゴーレム)

本番環境を想定し、重要な声(information以上)だけを日次で記録。7日分を保持し、古い記録は圧縮して保管する。

# ログの記録先をチャネルc1に設定
logging.loggers.root.channel = c1
# 記録する声のレベルをinformation以上に設定
logging.loggers.root.level = information
# フォーマッタf1(記録形式)の定義
logging.formatters.f1.class = PatternFormatter
logging.formatters.f1.pattern = %Y-%m-%d %H:%M:%S.%i %p [%I] (%U:%u) %t
logging.formatters.f1.times = local
# チャネルc1をFileChannel(ファイル記録)として定義
logging.channels.c1.class = FileChannel
logging.channels.c1.path = ${application.dir}logger_release.log
logging.channels.c1.formatter = f1
logging.channels.c1.rotation = daily
logging.channels.c1.archive = number
logging.channels.c1.purgeAge = 7 days
logging.channels.c1.compress = true

デバッグ用設計図(調整中のゴーレム)

開発中は、詠唱者の目の前(コンソール)と羊皮紙の両方で声を確認したい。SplitterChannelを使えば、声を二手に分けられる。

# ログの記録先をSplitterChannelであるc1に設定
logging.loggers.root.channel = c1
# 記録する声のレベルをdebug以上に設定
logging.loggers.root.level = debug
# c1を、コンソール(s1)とファイル(s2)に分岐させる
logging.channels.c1.class = SplitterChannel
logging.channels.c1.channels = s1,s2

# コンソール(s1)の設定 ...(以下略)
# ファイル(s2)の設定 ...(以下略)

魂を吹き込む呪文の詠唱(ソースコード修正)

前回のゴーレムに、initialize()の儀式を追加し、std::coutPoco::Loggerによる「声」の記録に置き換える。 (ソースコードの掲載は、元の記事のままで完璧なため、ここでは省略する)

羊皮紙を巻く前に

Poco::Loggerは、古文書の不足から導入にこそ苦労したが、一度仕組みを理解してしまえば、これほど強力で柔軟なロギング機能はない。 今回のソースコードと設計図をテンプレートとして使えば、あんたの創り出すゴーレムにも、簡単かつ手軽に本格的な魂を吹き込めるはずだ。

POCOライブラリという魔法体系を採用するなら、特別な理由がない限り、このPoco::Loggerを使わない手はない。 これで、我らがお手軽ゴーレムも、一歩、真の相棒へと近づいた。

おっと、どうやら相棒が腹を空かせたようだ。今日はこのへんで筆を置くとしよう。

砂漠で見つけた魔法のランプ

POCO 公式ドキュメント

POCO GitHub リポジトリ

関連する羊皮紙

ラクダの独り言

最近、ご主人が「ろぐがー」とかいう、目に見えない魂をゴーレムに吹き込むのに夢中だ。その情熱の半分でもいいから、俺の餌の量に向けてくれないもんかね。ゴーレムは腹が減らないからいいよな。まったく、やれやれだぜ。