エラーはゼロだと報告したが、その時間に三十件あった
目標
パーサーが取り出した名前がストリームラベルとぶつかって押しのけられる現象を自分で再現し、label_format・line_format・keep・dropで、結果を目的の形に整理します。
なぜ重要なのか
Lokiでは、ラベルは2つの場所から来ます。収集時に付けてインデックス化されたストリームラベルと、クエリ時にパーサーが本文から取り出したラベルです。名前が重なると、あとから取り出したほうに_extractedが付いて押しのけられますが、警告もエラーもありません。そのため、level="error"が0件を返し、人はそれを「エラーがない」と読みます。クエリ時点の名前の整理は、結果にだけ作用してインデックスには手を触れないので、思う存分使ってもコストは増えません。カーディナリティが心配な値を本文に置いて、クエリのときに取り出して使う設計が成り立つ理由です。
ステップ
/root/lk-formatでLokiを起動し、date +%sを/root/lk-format/anchor.txtに書いたあと、python3 /opt/lab/d5/gen.py format "$(cat anchor.txt)"でデータを入れてください。そして、{app="payment"}の1時間の行数と、そのストリームに付いているストリームラベルの名前を、/root/lk-format/01-boot.txtに2行で書いてください。lines=<정수>とlabels=<이름들을 쉼표로, 사전순>です(プレースホルダーは順に、整数と、名前をカンマでつないだ辞書順の並びです)。{app="payment"} | logfmt | level="error"を1時間の区間で投げて、結果が何行かを見て、名前が押しのけられたラベルでもう一度問い合わせ、何行かを数えてください。答えは/root/lk-format/02-collision.txtに3行で書きます。naive=<정수>、extracted_name=<밀려난 라벨 이름>、correct=<정수>です(プレースホルダーは順に、整数、押しのけられたラベルの名前、整数です)。label_formatを使って、本文のレベル値がlevelという名前で来るようにしたうえで、level="error"でふるい分けてください。クエリは/root/lk-format/03-relabel.logql、答えは/root/lk-format/03-relabel.txtにlines=<정수>で書きます(プレースホルダーは整数です)。結果の行数は、ステップ2のcorrectと同じである必要があります。{app="payment"}のすべての行を、<svc>/<본문의 수준>/<dur_ms>の形(プレースホルダーは本文のレベルです)に書き直すクエリを/root/lk-format/04-line.logqlに書いてください。スラッシュ2つだけで区切り、ほかの文字は入れません。そして、結果のうち最も早い行の本文を、/root/lk-format/04-line.txtに1行で書いてください。- 本文のレベル別に行数を数えるメトリクスクエリを
/root/lk-format/05-bylevel.logqlに書いてください。結果はちょうど2つの系列である必要があり、各系列のラベルはlvlだけである必要があります。値は/root/lk-format/05-bylevel.txtにinfo=<정수>とerror=<정수>の2行で書きます(プレースホルダーは整数です)。 - ステップ5のクエリで、
keepの代わりにdropを使って同じ結果を出すクエリを/root/lk-format/06-drop.logqlに書いてください。そして/root/lk-format/06-drop.txtに2行を書きます。series=<계열 수>とdropped=<버린 라벨 이름들을 쉼표로, 사전순>です(プレースホルダーは順に、系列数と、捨てたラベル名をカンマでつないだ辞書順の並びです)。 - ステップ3で
label_formatでラベルを作り、ステップ5では新しい名前で系列をまとめました。そのあと、インデックスのストリーム数が変わったかを確認して、/root/lk-format/07-index.txtに2行で書いてください。streams=<정수>とchanged=<yes|no>です(プレースホルダーは整数です)。ストリーム数は、series APIで数えます。 /root/lk-format/08-panel.logqlにクエリを1つ書いてください。条件は3つです。(1)本文のレベルがerrorの行だけを返してください、(2)返す本文を<dur_ms>ms <svc> <region>の形にしてください(空白2つだけで区切り、ほかの文字はなし)、(3)行フィルターを、本文の書き直しの前に置いてください。そして、結果の最も早い行を/root/lk-format/08-panel.txtに1行で書いてください。
参考
- 作業ディレクトリは
/root/lk-formatです。Lokiはステップ1で自分で起動します。 - データ生成器は
/opt/lab/d5/gen.pyで、formatデータを書き込みます。採点ツールはこのファイルを読みません。 - テンプレートはGoのtext/templateです。フィールドの前にドットを付け、波括弧を2重にして囲みます。クエリ文はバッククォートで囲むほうが安全です。
- 結果のラベル一覧は、
jq '.data.result[0].stream'で一度展開して見る習慣を付けてください。名前が押しのけられたことは、そうして初めて見えます。 - よくある間違い:
line_formatを行フィルターの前に置いてしまいます。その後ろのフィルターは、新しい本文を見ることになります。 - よくある間違い:
since=1hで測ってしまいます。anchor.txtの基準時刻でstart・endを指定してください。 - ログクエリとフォーマット・メトリクスクエリ・ラベル・HTTP API
ラベルと本文がずれたデータを入れる
/root/lk-formatでLokiを起動し、date +%sを/root/lk-format/anchor.txtに書いたあと、python3 /opt/lab/d5/gen.py format "$(cat anchor.txt)"でデータを入れてください。そして、{app="payment"}の1時間の行数と、そのストリームに付いているストリームラベルの名前を、/root/lk-format/01-boot.txtに2行で書いてください。lines=<정수>とlabels=<이름들을 쉼표로, 사전순>です(プレースホルダーは順に、整数と、名前をカンマでつないだ辞書順の並びです)。
ストリームラベルは、結果のstreamオブジェクトのキーです。Loki 3.xはservice_nameを自動で付けるので、それも一覧に入ります。辞書順に並べて、カンマでつないでください(空白なしで)。
level="error"が0件を返す
{app="payment"} | logfmt | level="error"を1時間の区間で投げて、結果が何行かを見て、名前が押しのけられたラベルでもう一度問い合わせ、何行かを数えてください。答えは/root/lk-format/02-collision.txtに3行で書きます。naive=<정수>、extracted_name=<밀려난 라벨 이름>、correct=<정수>です(プレースホルダーは順に、整数、押しのけられたラベルの名前、整数です)。
結果の1つをjq '.data.result[0].stream'で展開して、キーの一覧を見てください。パーサーが取り出した名前が、すでにあるストリームラベルとぶつかると、Lokiが接尾辞を付けて押しのけます。その名前でもう一度尋ねれば、数字が出ます。
label_formatで名前を元に戻す
label_formatを使って、本文のレベル値がlevelという名前で来るようにしたうえで、level="error"でふるい分けてください。クエリは/root/lk-format/03-relabel.logql、答えは/root/lk-format/03-relabel.txtにlines=<정수>で書きます(プレースホルダーは整数です)。結果の行数は、ステップ2のcorrectと同じである必要があります。
label_formatは、새이름=기존라벨の形で書くか、テンプレートを渡せます(プレースホルダーは新しい名前と既存のラベルです)。すでにある名前に上書きすることもできます。順序が重要です。名前を変えたあとでふるい分ける必要があります。
line_formatで行を書き直す
{app="payment"}のすべての行を、<svc>/<본문의 수준>/<dur_ms>の形(プレースホルダーは本文のレベルです)に書き直すクエリを/root/lk-format/04-line.logqlに書いてください。スラッシュ2つだけで区切り、ほかの文字は入れません。そして、結果のうち最も早い行の本文を、/root/lk-format/04-line.txtに1行で書いてください。
line_formatはGoテンプレートを受け取ります。フィールド名の前にドットを付けます。レベルは、ステップ2で見つけた、押しのけられた名前を使う必要があります。最も早い行は、direction=forwardで受け取れば、先頭に来ます。
keepで系列をまとめる
本文のレベル別に行数を数えるメトリクスクエリを/root/lk-format/05-bylevel.logqlに書いてください。結果はちょうど2つの系列である必要があり、各系列のラベルはlvlだけである必要があります。値は/root/lk-format/05-bylevel.txtにinfo=<정수>とerror=<정수>の2行で書きます(プレースホルダーは整数です)。
label_formatで使う名前を作り、keepでその名前だけを残してから、外側をsum by (lvl) (count_over_time(... [1h]))で包みます。keepを外すと何が起きるかも、一度見てください。系列数ですぐにわかります。
dropは何を捨て、何を残すか
ステップ5のクエリで、keepの代わりにdropを使って同じ結果を出すクエリを/root/lk-format/06-drop.logqlに書いてください。そして/root/lk-format/06-drop.txtに2行を書きます。series=<계열 수>とdropped=<버린 라벨 이름들을 쉼표로, 사전순>です(プレースホルダーは順に、系列数と、捨てたラベル名をカンマでつないだ辞書順の並びです)。
dropは名前を1つずつ書いて捨てます。ステップ1で書いておいたストリームラベルの一覧と、ステップ2で見たパーサーラベルの一覧を合わせれば、何を捨てるべきかが出ます。残すものが少ないときはkeep、捨てるものが少ないときはdropのほうが短くなります。
応用①: ラベルを変えても、インデックスはそのまま
ステップ3でlabel_formatでラベルを作り、ステップ5では新しい名前で系列をまとめました。そのあと、インデックスのストリーム数が変わったかを確認して、/root/lk-format/07-index.txtに2行で書いてください。streams=<정수>とchanged=<yes|no>です(プレースホルダーは整数です)。ストリーム数は、series APIで数えます。
/loki/api/v1/seriesにmatch[]={app=~".+"}を指定すると、現在インデックスにあるストリームの一覧が出ます。クエリ時点で作ったラベルがそこにあるかを見てください。なければ、それがまさに「クエリ時点のラベルは保存されない」ことの証拠です。
応用②: 人が読めるエラーパネルのクエリ
/root/lk-format/08-panel.logqlにクエリを1つ書いてください。条件は3つです。(1)本文のレベルがerrorの行だけを返してください、(2)返す本文を<dur_ms>ms <svc> <region>の形にしてください(空白2つだけで区切り、ほかの文字はなし)、(3)行フィルターを、本文の書き直しの前に置いてください。そして、結果の最も早い行を/root/lk-format/08-panel.txtに1行で書いてください。
順序が意味を持ちます。line_formatの後ろのフィルターは、新しい本文を見ます。そのため、ふるい分けを先に、書き直しをあとに行う必要があります。レベルでふるい分けるときは、ステップ2で見つけた名前か、ステップ3で作った名前を使えば済みます。