旅の途中、興味深いオアシスを見つけた。忘れないうちに、この羊皮紙に記しておくとしよう。
前回の旅で、我々は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のように書式指定でログを出力できるが、ここに恐ろしい罠がある。%sはstd::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::coutをPoco::Loggerによる「声」の記録に置き換える。
(ソースコードの掲載は、元の記事のままで完璧なため、ここでは省略する)
羊皮紙を巻く前に
Poco::Loggerは、古文書の不足から導入にこそ苦労したが、一度仕組みを理解してしまえば、これほど強力で柔軟なロギング機能はない。 今回のソースコードと設計図をテンプレートとして使えば、あんたの創り出すゴーレムにも、簡単かつ手軽に本格的な魂を吹き込めるはずだ。
POCOライブラリという魔法体系を採用するなら、特別な理由がない限り、このPoco::Loggerを使わない手はない。 これで、我らがお手軽ゴーレムも、一歩、真の相棒へと近づいた。
おっと、どうやら相棒が腹を空かせたようだ。今日はこのへんで筆を置くとしよう。
砂漠で見つけた魔法のランプ
- POCO 公式ドキュメント
- 我々を幾度となく助け、そして突き放した、あの難解なる古文書。
- POCO C++ Libraries - Documentation
- Logging - PDF
- POCO GitHub リポジトリ
- 最終的に、最も信頼できる答えが眠っていた場所。迷ったら、源流を辿れ。
- GitHub - pocoproject/poco: The POCO C++ Libraries are powerful cross-platform C++ libraries for building network- and internet-based applications that run on desktop, server, mobile, IoT, and embedded systems.
- Poco::Util::Application Logging Configuration · pocoproject/poco Wiki · GitHub
- 関連する羊皮紙
ラクダの独り言
最近、ご主人が「ろぐがー」とかいう、目に見えない魂をゴーレムに吹き込むのに夢中だ。その情熱の半分でもいいから、俺の餌の量に向けてくれないもんかね。ゴーレムは腹が減らないからいいよな。まったく、やれやれだぜ。