CaddyでJSON形式のアクセスログをカスタムする
何?
Caddy v2でアクセスログをJSON形式でいい感じにカスタムして出力する方法をメモします。ログのフィルター機能とか、カスタムフィールドの追加とか、スニペットでの再利用とか、そのあたりを含めてまとめてみました。
実際に設定するときは https://caddyserver.com/docs/caddyfile/directives/log, https://caddyserver.com/docs/logging#structured-logs あたりも参照してください。
最小構成なJSONログの設定
CaddyでアクセスログをJSON形式で出力する基本となる設定はこんな感じ:
:8080 {
log {
format json
output file /var/log/caddy/access.log
}
}
より高度なカスタム設定
フィルターで不要フィールドを割愛する
基本の設定ではいらない情報がいっぱい含まれてしまうので、filterで必要なものだけ残すようにします:
log access-log {
format filter {
request delete
bytes_read delete
resp_headers delete
wrap json {
time_key time # ts -> time
time_format rfc3339
time_local
duration_format ms
}
}
output file /var/log/caddy/access_log {
mode 644
roll_size 500mb
roll_keep 5
}
}
ここでやってること:
-
request delete
: リクエストボディの詳細を除外 -
bytes_read delete
: 読み取りバイト数を除外 -
resp_headers delete
: レスポンスヘッダーを除外 -
time_format rfc3339
: RFC 3339形式のタイムスタンプ -
time_local
: ローカル時間で記録
ts
, level
, logger
, msg
フィールドにはfilterをかけることができないので注意
カスタムフィールドを突っ込む
log_append
で独自のフィールドを追加できます:
route {
log_append method {http.request.method}
log_append uri {http.request.orig_uri}
log_append referer {http.request.header.Referer}
log_append user-agent {http.request.header.User-Agent}
}
これで以下のしていしたフィールドがアクセスログに追加されます
スニペットで設定を再利用
複数のサイトで同じログ設定を使いまわしたい場合、スニペットが便利です:
{
# Caddy本体のログ設定
log default {
format json
output file /var/log/caddy/default_log {
mode 644
roll_size 500mb
roll_keep 5
}
exclude http.log.access # アクセスログを除外
}
}
# 再利用可能なアクセスログスニペット
(access-log) {
log access-log {
format filter {
request delete
bytes_read delete
resp_headers delete
wrap json {
time_key time
time_format rfc3339
time_local
duration_format ms
}
}
output file /var/log/caddy/{args[0]}_access_log {
mode 644
roll_size 500mb
roll_keep 5
}
}
route {
log_append method {http.request.method}
log_append uri {http.request.orig_uri}
log_append referer {http.request.header.Referer}
log_append user-agent {http.request.header.User-Agent}
}
intercept {
@userId header X-User-Id *
handle_response @userId {
vars http.auth.user.id {http.response.header.X-User-Id}
}
}
}
# 各サイトでスニペットを使用
:8000 {
root /app/public
import access-log api
}
こうするとimport access-log api
みたいな形で簡単に呼び出せて、引数(この場合api
)をファイル名として使うようになります。
ユーザーIDを動的にセットしたいとき
レスポンスヘッダーからユーザーIDを取得してログに記録したい場合はこんな感じ:
intercept {
@userId header X-User-Id *
handle_response @userId {
# ログとして出力される json の user_id フィールドが予約語になっていて log_append による独自の値を詰めることができない
# user_id は http.auth.user.id を無理やり上書きする必要がある(本来Basic認証で使うuser_idが入る)
# http.response.header を vars でセットするには intercept ディレクティブで行う必要がある
# @see: https://github.com/caddyserver/caddy/pull/6108
vars http.auth.user.id {http.response.header.X-User-Id}
}
}
X-User-Id
ヘッダーがあるときだけ、その値がuser_id
フィールドに記録されます。user_id
フィールドは予約語なので、無理やりhttp.auth.user.id
を上書きしてます。
そのための例外対応が施されているようです。 https://github.com/caddyserver/caddy/pull/6108
汎用的な設定口を用意してほしいですね...。
※内情としては、ここでアクセスログのuser_id
に、http.auth.user.id
がセットされるようになっていて、本来ならここで認証されたユーザーIDがセットされる想定のようです...。
出力例
こんな感じのJSONログが出てきます:
{
"level": "info",
"time": "2025-08-26T10:30:45+09:00",
"logger": "http.log.access.access-log",
"msg": "handled request",
"method": "GET",
"uri": "/api/users?page=1",
"status": 200,
"duration": 114.514191,
"size": 1024,
"referer": "https://example.com/dashboard",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"user_id": "12345"
}
〆
Caddyのログ設定はクセがあると感じています。デフォルトのままでOKなら問題ないのですが、ログの構造に縛りがあるなど、スキーマを任意に調整したいときに悩むことが多いのでメモしました。
Discussion