promtailのmultilineの設定がよくわからなかったので調べてたら、pipeline_stagesからちゃんと理解する必要がありました
ので、一応最後のmultilineがメインです
ドキュメント
処理順序
設定した順番で処理されます
stageの種類で処理の優先度、とかはないです
ログエントリのstructure
pipeline処理時に内部で保持している構造物
なんとなく頭に入ってると色々理解しやすいです
-
Line
- 流れてきた生ログ
- 最終的なアウトプットになるもの
- string
-
Timestamp
- Lokiで読み取る際に使用するtimestamp(のはず)
- デフォルトはログエントリが作られた時刻が入る?(未確認)
- time.Time
-
Labels
- アウトプットするログに付与するlabel
- keyとvalueのmap
-
Extracted
- 各stageでparseしたものを保持するところ
- keyとvalueのmap
stages
(個人的に)よく使うものを掘っていきます
json
文字列をjsonにparseして、任意のフィールドをExtracted
に格納する
parseしたjsonのkeyとExtracted
に格納するkeyを同じにする場合は省略できる
これは他のstageでも共通
- json:
expressions:
extracted_key: json_key
common_key:
parseする文字列はデフォルトでLine
で、source
を指定すればExtractedの内容をparseできるので、入れ子のjsonをparseできる
例えば、以下のjson文字列を
{"one": {"two": {"three":"goal"}}}
pipeline_stages:
- json:
expressions:
one:
- json:
expressions:
two:
source: one
- json:
expressions
three:
source: two
- output:
source: three
こんな流れで完全にparseできる
regex
parseしてExtractedに格納する流れはjson stageと同じで、parseにjsonでなく正規表現を使用する
処理対象はデフォルトでLine
、sourceで指定可能
expression: '(?P<keyname>.*)'
という形で、Extractedにkeyにkeyname、valueに正規表現でキャプチャした値(この場合は全文)が入る
labels
Extracted
の内容をlabelで使用する
指定したkeyがExtracted
に存在しない場合は何も起きない(エラーにはならない)
timestamp
ログエントリのTimestamp
を設定する
設定する値は、sourceでExtracted
内のフィールドを指定するので、事前に何かしらのparseをしておく必要がある
フォーマットはプリセットから選ぶ必要があって、自由記述できない
output
Extracted
の中から、ログとして出力するフィールドを決める
内部ではLine
を置換している
sourceで指定したものがExtracted
に存在しない場合は何もしない
なので、outputステージを設定しなかったり、指定してもそれがExtracted
に存在しなかったら、生ログをそのまま出力する
docker
以下のstagesのwrapperになっている
- json:
output: log
stream: stream
timestamp: time
- labels:
stream:
- timestamp:
source: timestamp
format: RFC3339Nano
- output:
source: output
cri
以下のstagesのwrapperになっている
- regex:
expression: "^(?s)(?P<time>\\S+?) (?P<stream>stdout|stderr) (?P<flags>\\S+?) (?P<content>.*)$",
- labels:
stream:
- timestamp:
source: time
format: RFC3339Nano
- output:
source: content
multiline
ちゃんと理解するためにだいぶ長くなる
複数行のログの集合体block
を作り、最後に繋げてひとつのログにする
設定項目
- firstline
- 最初の行であるという判定を行う正規表現
- max_wait_times
- 後続のログを待つ時間
- あまり長く設定すると、最新のログが長時間ここで待ちになるのでその間の閲覧ができない
- 最初の行が出てからではなく、前の行が出てからの間隔
- デフォルトは3秒
- max_lines
- ひとつのblockの最大行数
- デフォルトは128
内部ステータス
multiline専用の内部ステータスがある
- buffer
- 現在のblockで保持してるログのバッファ
- startLineEntry
- firstlineに該当したログのログエントリstructure
- currentLines
- 現在のblockで保持しているログの行数
処理の流れ
- ログが来る
- firstlineに該当するか判定
- 判定の対象になるのは
Line
- よって、output stageの前か後かで挙動が変わる
- 該当する場合、flush処理(後述)をして現在のログエントリをまるっとstartLineEntryに入れる
- 判定の対象になるのは
- bufferに Line を追加して、currentLinesを+1する
- ログは\n(改行)で繋ぐ
- firstlineに該当したエントリもこの処理は行う
- currentLinesがmax_linesに達している場合、flush処理
- 後続のログを待ち、max_wait_timesを超えるとflush処理
- 先頭へ
flush処理とは
- bufferが空の場合は何もしない
- ログエントリを作成
-
Timestamp
Labels
Extracted
はstartLineEntryのものを使用 - Lineはbufferを文字列にしたもの
-
- 作成したログエントリを後続のstageに渡す
- bufferとcurrentLinesをリセットする
-
startLineEntryはリセットしない
- おそらくmax_linesやmax_wait_timesでflushされた際に次のblockでまだ使うため
ユニーク性
上記の処理は、ログエントリのlabelごとに独立して行われる
ポイント
-
Line
に対して先頭行の判定を行ったり繋げたり -
Line
以外の情報は先頭行のものを使用 - k8sの場合、dockerやcriで1回parseしてからoutputでLineを置き換えてからmultilineで処理する必要がある
- 複数のログを改行で繋いでる
- これをなくしたいならこの後replaceする必要があり、元のログに改行がある場合は一緒に消えてしまう
おまけ
そもそも今回multilneでやりたかったのは、dockerやcontainerdのログが16kbで分割されるやつの再結合だったんですが
これは現状のmultiline stageでは不可能なようです
dockerの場合
ログ分割が起きてるかどうかを見分ける方法は、本文の終端が\n
で終わっているかどうか(だったはず)
終端が\n
で終わっていない場合は、ログが分割されていてまだ続きがある(3分割以上の可能性もあるので、先頭とは限らない)
終わっている場合は、分割されたログの最後
-> multilineで判定できるのは先頭行のみなので不可能(最終行で判定できればよかった)
containerdの場合
containerdのログ分割が起きている場合はタグに入るP/Fで判定
-> ログ本文以外での判定はできないので不可能