ウェブアプリケーションのモニタリングやオブザーバビリティを実現していくための要素のひとつに、ログがある。
現在ではログファイルなどを直接見ることは稀で、 Datadog などのサービスを使うことが多い。そういったサービスには様々な機能があり、ただログを眺める以上のことができる。
しかしアプリケーション(今回の場合は Metabase)が出力するログが適切に構造化されていないと、 Datadog などのサービスが上手くログを扱うことができず、せっかくの機能を利用できなくなってしまう。
そして Metabase が出力するログはデフォルトでは構造化されていない。
Datadog のドキュメントから引用する。
一般的な Java ログのスタックトレースは複数の行に分割されているため、元のログイベントに関連付けることが困難です。
この問題を解決するには、ログを JSON 形式で生成するようにログライブラリを構成します。JSON にログすると、次のことができます。
Java ログ収集
この記事では、 Kubernetes クラスタで運用している Metabase のログを JSON 形式で出力させる方法について書いていく。
この記事の内容は以下の環境で動作確認している。
- Kubernetes
1.34
- Metabase
v0.56.13
デフォルトでの挙動
以下の内容のmanifest.yamlを用意して$ kubectl apply -f manifest.yamlすると、 Metabase がデプロイされる。今回は Amazon EKS と Amazon Aurora を使っているが、この記事で扱う内容はそれらの環境に依存したものではないので、環境は何でもよい。
apiVersion: v1 kind: Secret metadata: name: metabase-secret type: Opaque stringData: MB_DB_TYPE: "mysql" MB_DB_DBNAME: "mydb" MB_DB_PORT: "3306" MB_DB_USER: "admin" MB_DB_PASS: "qwerty123" MB_DB_HOST: "***.cluster-***.ap-northeast-1.rds.amazonaws.com" # データベースのホストを記述する --- apiVersion: apps/v1 kind: Deployment metadata: name: metabase spec: replicas: 1 selector: matchLabels: app: metabase template: metadata: labels: app: metabase spec: containers: - name: metabase image: docker.io/metabase/metabase:v0.56.13 ports: - containerPort: 3000 envFrom: - secretRef: name: metabase-secret resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" --- apiVersion: v1 kind: Service metadata: name: metabase spec: type: LoadBalancer selector: app: metabase ports: - protocol: TCP port: 80 targetPort: 3000
$ kubectl logs -f <pod-name>でログを見れるので確認してみると、以下のように表示される。

これを JSON 形式に変えるのが、今回やりたいことである。
Log4j 2 の設定を変える
Metabase は内部でLog4j 2というロギングライブラリを使用しており、その設定ファイルを上書きすることで、出力形式を変えることができる。
デフォルトの設定ファイルは以下で公開されている。
https://github.com/metabase/metabase/blob/master/resources/log4j2.xml
これを書き換えたファイルをコンテナ内に配置し、そのファイルを参照するように Metabase に指示すれば、 JSON 形式で出力されるようになる。
コンテナ内へのファイルの配置
今回は ConfigMap と Volume を使う。
まずmanifest.yamlの末尾に以下を追記し ConfigMap を作成する。
--- apiVersion: v1 kind: ConfigMap metadata: name: log4j2-config data: log4j2.xml: | <?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="STDOUT" target="SYSTEM_OUT" follow="true"> <JsonTemplateLayout eventTemplateUri="classpath:JsonLayout.json"/> </Console> <!-- This file appender is provided as an example --> <!-- <RollingFile name="FILE" fileName="${logfile.path}/metabase.log" filePattern="${logfile.path}/metabase.log.%i"> <Policies> <SizeBasedTriggeringPolicy size="500 MB"/> </Policies> <DefaultRolloverStrategy max="2"/> <PatternLayout pattern="%d [%t] %-5p%c - %m%n"> <replace regex=":basic-auth \\[.*\\]" replacement=":basic-auth [redacted]"/> </PatternLayout> </RollingFile> --> </Appenders> <Loggers> <Logger name="com.mchange" level="ERROR"/> <Logger name="liquibase" level="INFO"/> <Logger name="metabase" level="INFO"/> <Logger name="metabase-enterprise" level="INFO"/> <Logger name="metabase.metabot" level="DEBUG"/> <Logger name="metabase.plugins" level="DEBUG"/> <Logger name="metabase.query-processor.async" level="DEBUG"/> <Logger name="metabase.server.middleware" level="DEBUG"/> <Logger name="org.quartz" level="INFO"/> <Logger name="net.snowflake.client.jdbc.SnowflakeConnectString" level="ERROR"/> <Logger name="net.snowflake.client.core.SessionUtil" level="FATAL"/> <Root level="WARN"> <AppenderRef ref="STDOUT"/> </Root> </Loggers> </Configuration>
これでlog4j2.xmlというファイルを定義できた。
ファイルの中身だが、先程示した「デフォルトの設定ファイル」をコピーした上で、Console要素の子要素を<JsonTemplateLayout eventTemplateUri="classpath:JsonLayout.json"/>に変えている。
ちなみにJsonLayout.jsonは以下で公開されている。
https://github.com/apache/logging-log4j2/blob/rel/2.25.2/log4j-layout-template-json/src/main/resources/JsonLayout.json
次に Deployment を編集する。volumesとvolumeMountsを書き足す。
limits:
memory: "1Gi"
cpu: "500m"
+ volumeMounts:
+ - name: log4j2-config-volume
+ mountPath: /etc/log4j2
+ volumes:
+ - name: log4j2-config-volume
+ configMap:
+ name: log4j2-config
---
apiVersion: v1
kind: Service
これで、コンテナに/etc/log4j2/log4j2.xmlというファイルが作られるようになる。
Metabase への指示
JAVA_OPTSという環境変数を定義しその値を-Dlog4j.configurationFile=file:/etc/log4j2/log4j2.xmlにすることで、Log4j 2の設定ファイルとして/etc/log4j2/log4j2.xmlを使うように指定できる。
envFrom:
- secretRef:
name: metabase-secret
+ env:
+ - name: JAVA_OPTS
+ value: "-Dlog4j.configurationFile=file:/etc/log4j2/log4j2.xml"
resources:
requests:
memory: "512Mi"
JSON 形式で出力されることを確認する
この状態で再び$ kubectl apply -f manifest.yamlしてデプロイする。
これで JSON 形式で出力されるようになる。

JSON 形式に変えるだけならこれで終わりだが、ログに色をつけるための情報(\u001B[0mなど)は不要になったので、これを消すための設定も行っておく。
そもそも上述したように Datadog などのサービスでログを見ることが多いはずであり、その場合、この情報はノイズになりやすい。
環境変数MB_COLORIZE_LOGSをfalseにすることで消せる。
env:
- name: JAVA_OPTS
value: "-Dlog4j.configurationFile=file:/etc/log4j2/log4j2.xml"
+ - name: MB_COLORIZE_LOGS
+ value: "false"
resources:
requests:
memory: "512Mi"
再度デプロイして確認すると、消えている。

ちなみにターミナルエミュレータなどで JSON 形式のログを見る場合、jqコマンドを使うと綺麗に表示できる。
以下は$ kubectl logs -f <pod-name> | jq -R 'fromjson? // .'した例。
