さてさて前回、Fluent Bitでログを転送する仕組みを学びましたが、そのつづきです。
Fluent Bit を使ってできる事をもうすこし深掘りしてみます。
概要
前回はすでに設定済みのFluent Bitを動かしたので、Fluent Bitがなにをやってくれてるのか、ちょっとわかりにくかったかもしれません。なので今回は、設定するまえのデフォルト状態でFluent Bitを動かしてログを出力してみます。
そのあとに最終的に、設定済みのFluent Bit を内容を理解したいと思います。
準備
今回用にソースをアップしてあるので、それでやってみます。
$ git clone --branch fluentbit02 https://github.com/masatomix/spring-boot-sample-tomcat.git
$ cd spring-boot-sample-tomcat/
spring-boot-sample-tomcat $ mvn package
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.745 s
[INFO] Finished at: 2025-08-24T13:47:53Z
[INFO] ------------------------------------------------------------------------
spring-boot-sample-tomcat $
spring-boot-sample-tomcat$ docker compose up --build
[+] Building 0.0s (0/0)
[+] Running 1/0
✔ Container spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 Created
Attaching to spring-boot-sample-tomcat-spring-boot-sample-tomcat-1
...
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | . ____ _ __ _ _
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | ' |____| .__|_| |_|_| |_\__, | / / / /
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | =========|_|==============|___/=/_/_/_/
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | :: Spring Boot :: (v3.1.2)
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 |
...
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | {"@timestamp":"2025-08-24T13:54:59.814799039Z","@version":"1","message":"Tomcat started on port(s): 8080 (http) with context path ''","logger_name":"org.springframework.boot.web.embedded.tomcat.TomcatWebServer","thread_name":"main","level":"INFO","level_value":20000}
spring-boot-sample-tomcat-spring-boot-sample-tomcat-1 | {"@timestamp":"2025-08-24T13:54:59.82463053Z","@version":"1","message":"Started SampleTomcatApplication in 3.178 seconds (process running for 3.648)","logger_name":"nu.mine.kino.SampleTomcatApplication","thread_name":"main","level":"INFO","level_value":20000}
起動しました1。
つづいてFluent Bitも起動しますが、Fluent Bitをデフォルト状態で動かすために、
spring-boot-sample-tomcat$ cd fluentbit/test/
に移動して fluent-bit-test.conf
の下記の記載あたりをコメントアウトします。
# [FILTER]
# Name parser
# Match *-firelens-*
# Key_Name log
# Parser json
# # Preserve_Key ON
# Reserve_Data ON
#
# [FILTER]
# Name rewrite_tag
# Match *-firelens-*
# # Rule $level (ERROR|WARN) error-$container_id true
# Rule $level (ERROR) error-$container_id true
また前回同様、インプットファイルのパスも変更します。
参考: AWS CDK で Infrastructure as Code する: Fluent Bit編1。ローカルのDockerで疎通確認
では起動。
spring-boot-sample-tomcat/fluentbit/test$ docker compose up
[+] Building 0.0s (0/0)
[+] Running 1/0
✔ Container test-fluent-bit-1 Created 0.0s
Attaching to test-fluent-bit-1
test-fluent-bit-1 | Fluent Bit v1.9.10
test-fluent-bit-1 | * Copyright (C) 2015-2022 The Fluent Bit Authors
test-fluent-bit-1 | * Fluent Bit is a CNCF sub-project under the umbrella of Fluentd
test-fluent-bit-1 | * https://fluentbit.io
test-fluent-bit-1 |
...
test-fluent-bit-1 | [2025/08/31 05:29:01] [ info] [output:stdout:stdout.1] worker #0 started
test-fluent-bit-1 | [2025/08/31 05:29:01] [ info] [output:file:file.0] worker #0 started
起動したので、他のコンソールから curlで疎通します。
% curl 'http://localhost:8080/echoLogger' -i
HTTP/1.1 200
vary: accept-encoding
Content-Type: text/plain;charset=UTF-8
Content-Length: 16
Date: Sun, 31 Aug 2025 05:30:13 GMT
Default Message.
%
Fluent Bitのコンソールには、下記のようなログが出力されたと思います。
test-fluent-bit-1 | [0] -firelens-: [1756618213.532375132, {"log"=>"{"@timestamp":"2025-08-31T05:30:13.532127529Z","@version":"1","message":"Initializing Spring DispatcherServlet
...割愛
test-fluent-bit-1 | [4] -firelens-: [1756618213.560922780, {"log"=>"{"@timestamp":"2025-08-31T05:30:13.560482786Z","@version":"1","message":"Default Message.","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-1","level":"INFO","level_value":20000}
test-fluent-bit-1 | ", "stream"=>"stdout", "time"=>"2025-08-31T05:30:13.56092278Z"}]
なんだかごちゃっとしていますが、最終行のログだけちょっと整形してみると
test-fluent-bit-1 | [4] -firelens-: [1756618213.560922780,
{
"log"=>
"{"@timestamp":"2025-08-31T05:30:13.560482786Z","@version":"1","message":"Default Message.","logger_name":"nu.mine.kino.web.EchoController","thread_name":"http-nio-8080-exec-1","level":"INFO","level_value":20000}",
"stream"=>"stdout",
"time"=>"2025-08-31T05:30:13.56092278Z"
}
]
ログはJSONデータになっていて 「 log
プロパティ」があるようです。したがってコメントアウトした設定が有効な場合は、上記のようなデータを受け取り、設定に従ってFluent Bit内で処理がうごく、ってことになりそうです。
さてコメントアウトした設定たちが有効だった場合はどうなるか、順番に見てみます。
parser の処理
ひとつめのFILTER設定、parserの記述は以下の通りでした。
[FILTER]
Name parser
Match *-firelens-*
Key_Name log
Parser json
# Preserve_Key ON
Reserve_Data ON
この処理の内容はだいたい以下の通りです。
-
Fluent Bit に入ってきたログデータのうち、タグが
*-firelens-*
にマッチするものにこのフィルタが適用される。-
*-firelens-*
のタグはECSで動いている場合は、AWS上(Firelens上)で勝手に設定されます。とりあえずは全部のログと思ってもらってOKです。
-
-
Key_Name log
で、そのログデータの log プロパティの値をJSONとしてパース 。-
log
プロパティの値は先ほど見たとおり、{ "@timestamp": "2025-08-31T05:30:13.560482786Z", "@version": "1", "message": "Default Message.", "logger_name": "nu.mine.kino.web.EchoController", "thread_name": "http-nio-8080-exec-1", "level": "INFO", "level_value": 20000 }
こんなJSONデータでした。
-
-
JSON の中身が展開され、上記の各キーがそれぞれ新しいプロパティとして追加される。
-
log
プロパティと同レベルに、stream
、time
などのプロパティもありましたが、そこにlog
プロパティの中身のデータが同列に並ぶ感じです。
-
-
Reserve_Data ON
となっているので、log
以外の元々のプロパティ(stream
、time
、ローカル環境だと存在しないがcontainer_id
など)は削除しないで残す。 -
(今回はコメントアウトされているけど )、
Preserve_Key ON
を指定するとlog
プロパティ自体も削除しないで残す(今回は削除しちゃう)。
したがって、前回の記事のログに戻るのですが、これらの設定を有効にしてログを出力した場合は、
test-fluent-bit-1 | [1] -firelens-: [1756635383.401169885,
{
"@timestamp"=>"2025-08-31T10:16:23.401058661Z",
"@version"=>"1",
"message"=>"Default Message.",
"logger_name"=>"nu.mine.kino.web.EchoController",
"thread_name"=>"http-nio-8080-exec-7",
"level"=>"INFO",
"level_value"=>20000,
"stream"=>"stdout",
"time"=>"2025-08-31T10:16:23.401169885Z"
}
]
こんな感じになることが分かります。
デフォルトのログに level
プロパティなどが追加されたので、その内容によって後続処理を変えたりできるようになりました!
参考: https://docs.fluentbit.io/manual/pipeline/filters/parser
rewrite_tag の処理
ふたつめのFILTER設定、rewrite_tagの記述はこうでした。
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level (ERROR) error-$container_id true
この処理の内容はだいたい以下の通りです。
- parser されたデータに対して、
*-firelens-*
のタグのデータを処理する- 前処理で別のタグを付けたりしていないので、引き続き全データが対象
-
level
プロパティが「ERROR」のログデータに対して、rewrite_tagする(別のタグをつける)。- 別の新しいタグ名は
error-$container_id
(今回、ローカルなのでerror-
ってなる) - 最後の
true
で、元のレコードも残して新しいタグのデータをコピーする2
- 別の新しいタグ名は
参考: https://docs.fluentbit.io/manual/data-pipeline/filters/rewrite-tag
あとで実際にやりますが http://localhost:8080/serverException にアクセスすると、Server上のJavaプログラムが、
log.error(exception.getMessage(), exception);
とやって、結果 level
プロパティが「ERROR」のログが出力されるように作ってあります。
そのERRORなログだけは別のタグを付けるということですね。今回、Fluent Bitの設定ファイルは出力先を
[OUTPUT]
Name file
Match error*
Path /fluent-bit/etc/output_dir
[OUTPUT]
Name stdout
Match *
としてあるので、error-
というタグをつけたログは
- 上の設定により、上記のフォルダ上のあるファイル
- 下の設定により、Fluent Bitのコンソール
に出力されるはずです。
エラーになるリクエストを投げてみる
さてさて、コメントアウトした内容が理解できたところで、再度コメントアウトを戻して起動します。
- Fluent BitのほうのDockerを停止
-
fluent-bit-test.conf
のコメントアウトを戻す - 再度、Dockerを起動
でOKです。起動ができたら、正常終了するリクエストや、さきほどのエラーが発生するAPIにリクエストを発行してみます。
$ curl http://localhost:8080/serverException -i
HTTP/1.1 503
vary: accept-encoding
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 10 Sep 2025 07:54:34 GMT
Connection: close
{"code":"SERVICE_UNAVAILABLE","message":"サーバ起因の例外が発生しました"}
$
エラーが発生しました。
まずFluent Bitのコンソール上は下記のログが出力されたとおもいます。
fluent-bit-1 | [0] -firelens-: [1757490929.038618827,
{
"@timestamp"=>"2025-09-10T07:55:29.03697765Z",
"@version"=>"1",
"message"=>"サーバ起因の例外が発生しました",
"logger_name"=>"nu.mine.kino.advice.GlobalExceptionHandler",
"thread_name"=>"http-nio-8080-exec-2",
"level"=>"ERROR",
"level_value"=>40000,
"stack_trace"=>"nu.mine.kino.exceptions.ServerException: サーバ起因の例外が発生しました
fluent-bit-1 | at nu.mine.kino.web.EchoController.serverExcetpion(EchoController.java:150)
fluent-bit-1 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
... 割愛
fluent-bit-1 | ",
"stream"=>"stdout",
"time"=>"2025-09-10T07:55:29.038618827Z"
}]
fluent-bit-1 | [0] error-: [1757490929.038618827,
{"@timestamp"=>"2025-09-10T07:55:29.03697765Z",
"@version"=>"1",
"message"=>"サーバ起因の例外が発生しました",
"logger_name"=>"nu.mine.kino.advice.GlobalExceptionHandler",
"thread_name"=>"http-nio-8080-exec-2",
"level"=>"ERROR",
"level_value"=>40000,
"stack_trace"=>"nu.mine.kino.exceptions.ServerException: サーバ起因の例外が発生しました
fluent-bit-1 | at nu.mine.kino.web.EchoController.serverExcetpion(EchoController.java:150)
fluent-bit-1 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
... 割愛
fluent-bit-1 | ",
"stream"=>"stdout",
"time"=>"2025-09-10T07:55:29.038618827Z"
}]
同じログが2行出ていますが、元々あった-firelens-
タグのログと、error-
という新たなタグのログがどちらも出力されているからですね。先ほどの trueの設定が効いていそうです。
つづいて、 level
プロパティが「ERROR」のログだけが出力されるはずの場所も見てみます。
spring-boot-sample-tomcat/fluentbit/test$
spring-boot-sample-tomcat/fluentbit/test$ ls -lrt ./output_dir/
-rw-r--r-- 1 root root 17307 Sep 10 16:55 error-
spring-boot-sample-tomcat/fluentbit/test$ cat ./output_dir/error-
error-: [1757490929.038618827, {"@timestamp":"2025-09-10T07:55:29.03697765Z","@version":"1","message":"サーバ起因...
省略
spring-boot-sample-tomcat/fluentbit/test$
error-
というタグがついたログだけが出力されていました!よさそうですね。
おつかれさまでした。
ここまでのまとめ
まとめます。
-
parser 処理によってJSONデータを取り出して、ログに新たなプロパティを追加・出力できることが分かりました。
-
また rewrite_tag 処理によって、ログのプロパティの値によってタグを付け直したりできることが分かりました。
-
これらをうまく活用することで
- アプリや共通のフレームワークが構造的なデータを出力
- その情報に基づいて、タグを付け直す
- タグごとに、ログ出力先を変える
などができそうですね!
上記の活用例をTIPSとしてまとめました。
TIPS1: 業務アプリの出力ログと、共通機能(FW)の出力ログを分ける
業務アプリと共通機能(FW)があったとして、どちらも、いわゆる JavaでおなじみのLoggerの機構を使うと仮定します。それらログをそれぞれ別の場所に保存したいとします。
やりかたは
- FW だけ「FWのログだよ」というプロパティを追加してログ出力3
- parserでログを解析
- Fluent Bitがそのプロパティを見て、該当ログだけ別のタグを付ける
- タグごとに出力先を分ける
とかでできそうですね。
TIPS2: ERROR ログだけ出力先を分ける
ERRORログだけ出力先を分ける、たとえばAWSだと ERRORログだけCloudWatch Logsに保存、その他のログはS3に保存、などができそうです。やり方は上記のハンズオンでやったとおりです。
TIPS3: 特定のログだけメールする
ある特定のケースでのみ内容はメールで通知したいぞ、なんてときがあります。
この場合は、TIPS1で「FWのログを判別」したときと同様に
- アプリがログ出力時に「メールしてほしいな」というプロパティを付けてログ出力
- 宛先、内容、など可変の内容も、個別のプロパティとして出力しておく
- そのログだけ、(メール送信ログ用の)S3へ保存。 可変の内容もJSONデータとして保存されます。
- S3のトリガーにLambdaを設定しておくことで、Lambdaが起動。
- Lambdaの処理では、S3へ保存されたJSONデータを取りだし、SES経由などでメール送信します。
などとやれば実現できますね。
さて、ここまでやって「アプリ達が、(Fluent Bitが処理を判定するための)構造的なデータをログに出力する」のが重要ってことが分かりました。今回はJava/SpringBootでアプリを作っていますが、Javaのログ出力時にプロパティを追加して構造的なデータを出力する方法について、次回はやってみたいと思います。
以上、おつかれさまでした。