エラーは一つもないのに 5xx の件数が実際の三分の一だった
目標
Pod内の本物のLokiで4種類のLogQLパーサーを自分で付けてみて、パースエラーが出た行と、エラーなしで静かに値を取り出せなかった行をそれぞれ数え、本当の5xx件数を求めます。
なぜ重要なのか
Lokiは本文をインデックス化しません。そのため、本文の中の値で絞り込むには、クエリのたびにパーサーで取り出す必要があり、パーサーの選択を誤ると、数字が静かに減ります。jsonは失敗を__error__ラベルで知らせますが、logfmtは知らせません。形式が異なる行に出会うと、何のラベルも作らずに通り過ぎ、ラベルがなければ、後に続くラベルフィルターはその行を静かに捨てます。ダッシュボードにはエラーも警告も出ません。そのため、新しいストリームをクエリする前に、常に2つの数字を先に測る必要があります。パースが失敗した行数と、失敗もしていないのに値が出なかった行数です。
ステップ
/root/lk-parsersでLokiを起動し、date +%sを/root/lk-parsers/anchor.txtに書いたあと、python3 /opt/lab/d5/gen.py parsers "$(cat anchor.txt)"でデータを入れてください。そして、基準時刻から過去1時間の間に、{app="mixed"}と{app="orders"}がそれぞれ何行あるかを数えて、/root/lk-parsers/01-boot.txtにmixed=<정수>とorders=<정수>の2行で書いてください(プレースホルダーは整数です)。ordersストリームでstatusが500の行が何行あるかを、logfmtパーサーで数えてください。クエリは/root/lk-parsers/02-logfmt.logql、答えは/root/lk-parsers/02-logfmt.txtにlines=<정수>で書きます(プレースホルダーは整数です)。mixedストリームにjsonパーサーを付けて、パースに成功した行と、__error__ラベルが付いた行をそれぞれ数えてください。答えは/root/lk-parsers/03-json.txtにok=<정수>とerr=<정수>の2行で書き、__error__の実際の値を1つ、/root/lk-parsers/03-json-name.txtに1行で書いてください(プレースホルダーは整数です)。mixedストリームにlogfmtパーサーを付けたとき、エラーは出なかったのにstatusラベルが作られなかった行が何行あるかを数えてください。クエリは/root/lk-parsers/04-silent.logql、答えは/root/lk-parsers/04-silent.txtにlines=<정수>で書きます(プレースホルダーは整数です)。- 本文に
legacyが入っている行だけを選んで、patternパーサーでステータスコードを取り出し、その値が500の行を数えてください。クエリは/root/lk-parsers/05-pattern.logql、答えは/root/lk-parsers/05-pattern.txtにlines=<정수>で書きます(プレースホルダーは整数です)。クエリにはpatternパーサーが必ず入っている必要があります。 - 同じレガシーの行から、
regexpパーサーで所要時間(ミリ秒)を取り出し、1000より大きい行を数えてください。クエリは/root/lk-parsers/06-regexp.logql、答えは/root/lk-parsers/06-regexp.txtにlines=<정수>で書きます(プレースホルダーは整数です)。クエリにはregexpパーサーが必ず入っている必要があります。 mixedストリームの行を3つの形式に分けて数え、/root/lk-parsers/tally.tsvを作成してください。ヘッダーなしで3行で、各行はタブで区切った2つの欄<형식><탭><줄수>(プレースホルダーは形式、タブ、行数です)です。形式名は順にjson、legacy、logfmtで、3行の合計は、ステップ1で数えたmixedの全体の行数と同じである必要があります。mixedストリームで、ステータスコードが500の行の本当の合計件数を求めて、/root/lk-parsers/08-total.txtにtotal=<정수>で書き(プレースホルダーは整数です)、その次の行に、reason=で始まる1文(空白を除いて40文字以上)を書いてください。なぜパーサー1つではこの数字を数えられないのかを、自分の言葉で書きます。
参考
- 作業ディレクトリは
/root/lk-parsersです。Lokiはステップ1で自分で起動します。 - データ生成器は
/opt/lab/d5/gen.pyで、parsersデータを書き込みます。採点ツールはこのファイルを読みません。 - クエリはバッククォートで囲むほうが安全です。二重引用符の中では、バックスラッシュがエスケープとして解釈されます。
- よくある間違い:
since=1hで測ってしまいます。時間が経つと答えが変わり、再採点で落ちます。anchor.txtの基準時刻でstart・endを指定してください。 - よくある間違い:
| __error__=""を習慣的に付けてしまいます。壊れた行を数える前に消すと、形式が混ざっているという事実そのものが見えなくなります。 - ログクエリとパーサー・LogQL概要・ラベル・HTTP API
形式が混ざったストリームを手元に用意する
/root/lk-parsersでLokiを起動し、date +%sを/root/lk-parsers/anchor.txtに書いたあと、python3 /opt/lab/d5/gen.py parsers "$(cat anchor.txt)"でデータを入れてください。そして、基準時刻から過去1時間の間に、{app="mixed"}と{app="orders"}がそれぞれ何行あるかを数えて、/root/lk-parsers/01-boot.txtにmixed=<정수>とorders=<정수>の2行で書いてください(プレースホルダーは整数です)。
/readyがreadyを返すまで20秒ほどかかります。区間はanchor.txtの値でstart・endをナノ秒で指定してください。パーサーなしで、セレクターだけで数えれば済みます。
形式がきれいなときは、logfmt1行で済む
ordersストリームでstatusが500の行が何行あるかを、logfmtパーサーで数えてください。クエリは/root/lk-parsers/02-logfmt.logql、答えは/root/lk-parsers/02-logfmt.txtにlines=<정수>で書きます(プレースホルダーは整数です)。
パーサーはセレクターの後ろにパイプで付けます。パーサーが作ったラベルは、その後ろでラベルフィルターとして使えます。行フィルターとして|= "status=500"のように文字列を探すのとは違います。今回は、パーサーで取り出した値を使ってください。
jsonパーサーは失敗を知らせる
mixedストリームにjsonパーサーを付けて、パースに成功した行と、__error__ラベルが付いた行をそれぞれ数えてください。答えは/root/lk-parsers/03-json.txtにok=<정수>とerr=<정수>の2行で書き、__error__の実際の値を1つ、/root/lk-parsers/03-json-name.txtに1行で書いてください(プレースホルダーは整数です)。
パーサーが失敗すると、__error__ラベルが付きます。そのラベルが空の行と、空でない行を、それぞれ選べば済みます。ラベルの値が何かは、結果のstreamをそのまま見ると見えます。__error_details__も一緒に付きます。
エラーもなく何も取り出せなかった行を数える
mixedストリームにlogfmtパーサーを付けたとき、エラーは出なかったのにstatusラベルが作られなかった行が何行あるかを数えてください。クエリは/root/lk-parsers/04-silent.logql、答えは/root/lk-parsers/04-silent.txtにlines=<정수>で書きます(プレースホルダーは整数です)。
LogQLでは、「存在しないラベル」は、空文字列と同じように比較されます。その性質を使えば、「パーサーが値を作れなかった行」を選べます。この数字が、このラボで最も恐ろしい数字です。ダッシュボードのどこにも出ないのに、合計を削っていくからです。
位置が固定された行にはpatternが合う
本文にlegacyが入っている行だけを選んで、patternパーサーでステータスコードを取り出し、その値が500の行を数えてください。クエリは/root/lk-parsers/05-pattern.logql、答えは/root/lk-parsers/05-pattern.txtにlines=<정수>で書きます(プレースホルダーは整数です)。クエリにはpatternパーサーが必ず入っている必要があります。
レガシーの行は、legacy handler finished code 500 in 123msの形です。patternは、取り出す位置に山括弧で名前を書き、残りは文字どおりに書きます。パーサーの前に行フィルターを置いて、レガシーの行だけを渡すことが重要です。ほかの形式の行には、この形がありません。
正規表現パーサーで数値を取り出して比較する
同じレガシーの行から、regexpパーサーで所要時間(ミリ秒)を取り出し、1000より大きい行を数えてください。クエリは/root/lk-parsers/06-regexp.logql、答えは/root/lk-parsers/06-regexp.txtにlines=<정수>で書きます(プレースホルダーは整数です)。クエリにはregexpパーサーが必ず入っている必要があります。
regexpは名前付きキャプチャグループだけをラベルにします。名前のない括弧は捨てられます。取り出した値は文字列ですが、不等号で比較すると、LogQLが数値に変換してくれます。同じ答えをpatternでも出せます。どちらが読みやすいか、比べてみてください。
応用①: 形式別に何行かを表にする
mixedストリームの行を3つの形式に分けて数え、/root/lk-parsers/tally.tsvを作成してください。ヘッダーなしで3行で、各行はタブで区切った2つの欄<형식><탭><줄수>(プレースホルダーは形式、タブ、行数です)です。形式名は順にjson、legacy、logfmtで、3行の合計は、ステップ1で数えたmixedの全体の行数と同じである必要があります。
JSONの行は、jsonパーサーがエラーを出さない行です。レガシーの行は、本文にlegacyがあります。残りがlogfmtの行で、これらの行は、logfmtでstatusが作られます。3つの数字の合計が全体と合っているかを、必ず確認してください。合っていなければ、どこかを2回数えたか、数え漏らしています。
応用②: 本当の5xx件数と、その事実を残す
mixedストリームで、ステータスコードが500の行の本当の合計件数を求めて、/root/lk-parsers/08-total.txtにtotal=<정수>で書き(プレースホルダーは整数です)、その次の行に、reason=で始まる1文(空白を除いて40文字以上)を書いてください。なぜパーサー1つではこの数字を数えられないのかを、自分の言葉で書きます。
1つのクエリにパーサーを2つつなげても、2つの形式を一緒に読めるわけではありません。形式ごとに別々に数えて足してください。ステップ4で数えた「静かに抜けた行」がどこへ行ったかを考えれば、何方向に分ける必要があるかが見えます。答えを書くときは、3方向の数字がそれぞれいくつだったかも確認しておいてください。