新しいアプリを作成しています。構造化ロギングを含めたいのですが。私の理想的なセットアップは、C#コードの場合はSerilog
、JSの場合はBunyan
のようなものです。これらはfluentd
に供給され、その後、いくつものことに出かけることができると私は最初に_elasticsearch + kibana
_を考えていました。私たちはすでにMySQLデータベースを持っているので、短期的には、Serilog + Bunyanのセットアップとそれを使用する開発者にもっと興味があります。fluentdとその他を導入するのに少し時間をかけながら、MySQLにログインできます。
ただし、より経験豊富なコーダーの1人は、_log4net
_を使用してlog.debug("Disk quota {0} exceeded by user {1}", quota, user);
を実行し、次に_SELECT text FROM logs WHERE text LIKE "Disk quota";
_のようにMySQLに対して選択ステートメントを実行することを好みます。
そうは言っても、ロギングシステムのタイプを選択するときに、どちらのアプローチが優れているか、および/またはどのようなことを考慮する必要がありますか?
structuredアプローチには2つの基本的な進歩があり、テキストログを使用して(時には極端なレベルの)追加の努力なしにエミュレートすることはできません。
次のようにlog4netで2つのイベントを書き込む場合:
log.Debug("Disk quota {0} exceeded by user {1}", 100, "DTI-Matt");
log.Debug("Disk quota {0} exceeded by user {1}", 150, "nblumhardt");
これらは同様のテキストを生成します:
Disk quota 100 exceeded by user DTI-Matt
Disk quota 150 exceeded by user nblumhardt
しかし、機械処理に関する限り、それらは異なるテキストの2行にすぎません。
すべての「ディスククォータ超過」イベントを検索することもできますが、イベントを探す単純なケースlike 'Disk quota%'
は、次のような別のイベントが発生するとすぐに落下します。
Disk quota 100 set for user DTI-Matt
テキストロギングは、イベントのソースについて最初に持っていた情報を破棄します。これは、通常、ますます複雑な一致表現でログを読み取るときに再構築する必要があります。
対照的に、次の2つのSerilogイベントを書き込む場合:
log.Debug("Disk quota {Quota} exceeded by user {Username}", 100, "DTI-Matt");
log.Debug("Disk quota {Quota} exceeded by user {Username}", 150, "nblumhardt");
これらはlog4netバージョンと同様のテキスト出力を生成しますが、舞台裏では"Disk quota {Quota} exceeded by user {Username}"
メッセージテンプレート は両方のイベントで伝達されます。
適切なシンクを使用すると、後でクエリを書くことができますwhere MessageTemplate = 'Disk quota {Quota} exceeded by user {Username}'
とディスククォータを超えたイベントをexactly取得します。
すべてのログイベントでメッセージテンプレート全体を保存することが常に便利であるとは限らないため、一部のシンクはメッセージテンプレートをEventType
数値にハッシュ化します(例:0x1234abcd
)、または これを自分で行う にするために、ログパイプラインに強化機能を追加できます。
下記の次の違いよりも微妙ですが、大量のログボリュームを処理する場合は非常に強力です。
ここでも、ディスク領域の使用状況に関する2つのイベントを検討します。テキストログを使用すると、like 'Disk quota' and like 'DTI-Matt'
。
ただし、本番環境の診断は必ずしもそれほど簡単ではありません。ディスククォータが125 MBを下回ったイベントを見つける必要があると想像してください。
Serilogでは、これは次のバリアントを使用するほとんどのシンクで可能です。
Quota < 125
正規表現からこの種のクエリを作成することは可能ですが(= /// =)すぐに疲れてしまい、通常は最後の手段の手段になります。
これにイベントタイプを追加します。
Quota < 125 and EventType = 0x1234abcd
ここで、これらの機能を簡単に組み合わせて、ログを使用した本番環境のデバッグを一流の開発アクティビティのように感じる方法を見ていきます。
もう1つの利点は、おそらく前もって防ぐのは簡単ではないかもしれませんが、プロダクションデバッグが正規表現のハッカーの領域から取り除かれると、開発者はログをより重要視し、それらを記述する際により多くの注意と考慮を払うようになります。ログの改善->品質の高いアプリケーション->幸福度の向上。
structuredロギングを使用して、いくつかのデータベースに解析したり、処理されたログを後で検索したりするために、処理のためにログを収集する場合、より簡単/より効率的な処理。パーサーは、既知の構造(egJSON、XML、ASN.1、なんでも)を利用して、通常とは対照的に、状態マシンを解析に使用できます式(コンパイルと実行に(比較的)計算コストがかかる可能性があります)。同僚から提案されたような自由形式のテキストの解析は、正規表現に依存する傾向がありますandそのテキストに依存します変更されません。これにより、自由形式のテキストの解析がかなり壊れやすくなる可能性があります(つまり解析は、コード内の正確なテキストと密接に結び付いています)。
検索/検索のケースも考慮してください例:
SELECT text FROM logs WHERE text LIKE "Disk quota";
LIKE
条件では、すべてのtext
行値との比較が必要です。繰り返しになりますが、これは比較的計算コストがかかります特にワイルドカードが使用されている場合。
SELECT text FROM logs WHERE text LIKE "Disk %";
構造化ロギングを使用すると、ディスクエラー関連のログメッセージはJSONでは次のようになります。
{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }
この種類の構造のフィールドは、、たとえばSQLテーブルの列名にかなり簡単にマップできます。これは、検索をより具体的/詳細にできることを意味します。
SELECT user, text FROM logs WHERE error_type = "disk";
それらの列の値にLIKE
句を使用しない限り、頻繁に検索/検索することが期待される値の列にインデックスを配置できます。ログメッセージを特定のカテゴリに分類できるほど、検索対象を絞り込めます。たとえば、上の例のerror_type
フィールド/列に加えて、"error_category": "disk", "error_type": "quota"
やそのようなものにすることもできます。
ログメッセージの構造が多いほど、解析/検索システム(fluentd
、elasticsearch
、kibana
など)がその構造を利用して実行できるより高速でCPU /メモリが少ないタスク。
お役に立てれば!
アプリが1日に数百のログメッセージを作成する場合、構造化ロギングのメリットはあまりありません。デプロイされたさまざまなアプリから1秒間に数百のログメッセージが送信される場合は、間違いありません。
関連して、ログメッセージが最終的に ELKスタック になるセットアップは、SQLへのロギングがボトルネックになるスケールにも適しています。
SQL select .. like
と正規表現を使用した「基本的なロギングと検索」の設定が、限界に達し、バラバラになるところを見てきました。誤検出、脱落、維持が難しい既知のバグのある恐ろしいフィルターコードがあり、触りたい、フィルタの仮定に従わない新しいログメッセージ、レポートを壊さないようにコード内のロギングステートメントを触るのをためらうなど。
そのため、この問題をより適切に処理するために、いくつかのソフトウェアパッケージが登場しています。 Serilogがあり、 NLogチームがそれを見ていると聞きました 、私たちは StructuredLogging.Json
と書きましたNlog の場合、新しい ASP.Netコアロギングアブストラクション により、ロギングプロバイダーが実装...構造化ロギング」。
StructuredLoggingの例。次のようにNLogロガーにログインします。
logger.ExtendedError("Order send failed", new { OrderId = 1234, RestaurantId = 4567 } );
この構造化データは木花に送られます。値1234
は、ログエントリのOrderId
フィールドに格納されます。次に、キバナクエリ構文を使用して検索できます。 @LogType:nlog AND Level:Error AND OrderId:1234
のすべてのログエントリ。
Message
とOrderId
は、必要に応じて完全一致または不正確一致を検索したり、カウントを集計したりできる単なるフィールドになりました。これは強力で柔軟です。
StructuredLoggingのベストプラクティス から:
ログに記録されるメッセージは毎回同じでなければなりません。 IDや数量などのデータ値を含むようにフォーマットされた文字列ではなく、定数文字列である必要があります。その後、簡単に検索できます。
ログに記録されるメッセージは明確でなければなりません。つまり、無関係なログステートメントによって生成されるメッセージと同じではありません。次に、それを検索しても、無関係なものとは一致しません。