clails の Logging は柔軟なロギングシステムを提供します。 アプリケーション全体のログ出力を統一的に管理し、パッケージ階層に基づいた自動的なロガーの選択、複数の出力先への同時出力、フォーマットのカスタマイズなどをサポートします。
- Logger: ログメッセージを受け取り、設定されたログレベルに基づいて出力するかどうかを判断します
- Appender: ログの出力先を定義します(コンソール、ファイル、など)
- Formatter: ログメッセージの出力フォーマットを定義します(テキスト、JSON)
- Log Level: ログの重要度を表します(TRACE < DEBUG < INFO < WARN < ERROR < FATAL)
- Logger 階層: パッケージ階層に基づいて Logger を自動的に選択します
clails は以下のログレベルをサポートしています。
| レベル | 説明 | 用途 |
|---|---|---|
:trace |
最も詳細な情報 | デバッグ時の詳細なトレース情報 |
:debug |
デバッグ情報 | 開発時のデバッグ情報 |
:info |
一般的な情報 | アプリケーションの通常動作の記録 |
:warn |
警告 | 潜在的な問題の警告 |
:error |
エラー | エラーが発生したが、処理は継続可能 |
:fatal |
致命的なエラー | アプリケーションの実行を継続できない重大なエラー |
:none |
ログ出力なし | すべてのログを抑制 |
register-logger 関数を使用して Logger を登録します。
(use-package :clails/logger)
;; コンソールに出力する Logger を登録
(register-logger :root
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))
;; ファイルに出力する Logger を登録
(register-logger :sql
:level :debug
:appender (make-file-appender
:filepath "/var/log/myapp/sql.log"
:formatter (make-instance '<json-formatter>)))clails は パッケージ階層に基づいて Logger を自動的に選択します。
;; ルート Logger(すべてのログのデフォルト)
(register-logger :root
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))
;; CLAILS パッケージ用の Logger
(register-logger :clails
:level :debug
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))
;; CLAILS/MODEL パッケージ用の Logger
(register-logger :clails/model
:level :trace
:appender (make-file-appender
:filepath "/var/log/myapp/model.log"
:formatter (make-instance '<json-formatter>)))Logger が見つからない場合、親 Logger を自動的に検索します。
:clails/model/query→:clails/model→:clails→:root
get-logger 関数を使用して Logger を取得します。
;; 名前で Logger を取得
(get-logger :root) ; => ルート Logger
(get-logger :clails) ; => CLAILS Logger
;; パッケージ名から Logger を取得(自動的に階層検索)
(get-logger :clails/model/query) ; => :clails/model または :clails または :rootremove-logger 関数を使用して Logger を削除します。
(remove-logger :sql)clear-loggers 関数を使用してすべての Logger を削除します。
(clear-loggers)標準出力にログを出力します。
(make-console-appender
:formatter (make-instance '<text-formatter>))特徴:
- スレッドセーフ:
*standard-output*の動的な値を使用します - 複数スレッドから同時にログ出力しても安全です
ファイルにログを出力します。
(make-file-appender
:filepath "/var/log/myapp/app.log"
:formatter (make-instance '<text-formatter>))特徴:
- ファイルが存在しない場合は自動的に作成されます
- 既存のファイルには追記(append)されます
- ファイルストリームは自動的に管理されます
ファイル Appender を使用している場合は、アプリケーション終了時に明示的にクローズすることを推奨します。
(defvar *file-appender* (make-file-appender
:filepath "/var/log/myapp/app.log"
:formatter (make-instance '<text-formatter>)))
;; アプリケーション終了時
(close-appender *file-appender*)人間が読みやすいテキスト形式でログを出力します。
(make-instance '<text-formatter>)出力例:
[2024-01-15T10:30:45.123456+09:00] INFO: User logged in user-id=123 ip-address="192.168.1.1"
[2024-01-15T10:30:46.234567+09:00] ERROR: Database connection failed error="Connection timeout"
JSON 形式でログを出力します。機械処理や外部システムとの連携に便利です。
(make-instance '<json-formatter>)出力例:
{"timestamp":"2024-01-15T10:30:45.123456+09:00","level":"info","message":"User logged in","user-id":123,"ip-address":"192.168.1.1"}
{"timestamp":"2024-01-15T10:30:46.234567+09:00","level":"error","message":"Database connection failed","error":"Connection timeout"}現在のパッケージに基づいて自動的に Logger を選択してログを出力します。
(in-package #:myapp/model)
;; 各ログレベルでログを出力
(log-package.trace "Entering function" :function "find-user" :args '(123))
(log-package.debug "Query executed" :query "SELECT * FROM users WHERE id = ?" :params '(123))
(log-package.info "User found" :user-id 123 :user-name "Alice")
(log-package.warn "Slow query detected" :query-time 5.2 :threshold 3.0)
(log-package.error "Failed to save user" :error "Validation failed" :user-id 123)
(log-package.fatal "Database connection lost" :error "Connection timeout")Logger の選択:
- 現在のパッケージが
#:myapp/modelの場合 :myapp/model→:myapp→:rootの順で Logger を検索します- 最初に見つかった Logger を使用します
Logger 名を明示的に指定してログを出力します。
(log-to :root :info "Application started")
(log-to :sql :debug "Query executed" :query "SELECT * FROM users")
(log-to :myapp/service :error "Service failed" :service "UserService")特定の用途に特化したログ出力マクロを使用できます。
データベースクエリの記録に使用します。
(log.sql "SELECT * FROM users WHERE id = ?"
:params '(123)
:query-time 0.05
:rows-affected 1)- Logger 名:
:sql - ログレベル:
:debug
HTTP リクエスト/レスポンスの記録に使用します。
(log.web-access "GET /users/123"
:method "GET"
:path "/users/123"
:status 200
:duration 0.123
:ip-address "192.168.1.1")- Logger 名:
:web-access - ログレベル:
:info
セキュリティ関連のイベントやユーザーアクションの記録に使用します。
(log.audit "User login successful"
:user-id 123
:username "alice"
:ip-address "192.168.1.1"
:action "login")- Logger 名:
:audit - ログレベル:
:info
タスク実行のイベント記録に使用します。
(log.task "Task started"
:task-name "db:migrate"
:namespace :db)
(log.task "Task completed"
:task-name "cleanup"
:namespace :system
:duration 1.234
:status :success)- Logger 名:
:task - ログレベル:
:info
with-log-context マクロを使用して、ブロック内のすべてのログメッセージに共通のコンテキストを追加できます。
(with-log-context (:request-id "req-12345" :user-id 123)
(log-package.info "Processing request")
(log-package.debug "Fetching user data")
(log-package.info "Request completed"))
;; 出力例:
;; [2024-01-15T10:30:45.123456+09:00] INFO: Processing request request-id="req-12345" user-id=123
;; [2024-01-15T10:30:45.234567+09:00] DEBUG: Fetching user data request-id="req-12345" user-id=123
;; [2024-01-15T10:30:45.345678+09:00] INFO: Request completed request-id="req-12345" user-id=123コンテキストはネストして使用できます。
(with-log-context (:request-id "req-12345")
(log-package.info "Request started")
(with-log-context (:operation "fetch-user" :user-id 123)
(log-package.debug "Fetching user")
(log-package.info "User fetched"))
(log-package.info "Request completed"))
;; 出力例:
;; [2024-01-15T10:30:45.123456+09:00] INFO: Request started request-id="req-12345"
;; [2024-01-15T10:30:45.234567+09:00] DEBUG: Fetching user request-id="req-12345" operation="fetch-user" user-id=123
;; [2024-01-15T10:30:45.345678+09:00] INFO: User fetched request-id="req-12345" operation="fetch-user" user-id=123
;; [2024-01-15T10:30:45.456789+09:00] INFO: Request completed request-id="req-12345"アプリケーション実行中にログレベルを動的に変更できます。
;; Logger のログレベルを変更
(set-logger-level :root :debug)
(set-logger-level :clails/model :trace)
(set-logger-level :sql :info);; 現在のパッケージで指定したログレベルが有効かどうかを確認
(log-level-enabled-p :debug) ; => T or NIL
;; 特定のパッケージで指定したログレベルが有効かどうかを確認
(log-level-enabled-p :debug :clails/model) ; => T or NIL
(log-level-enabled-p :trace :sql) ; => T or NILログレベルが有効な場合のみ、重い処理を実行してログ出力できます。
(when (log-level-enabled-p :debug)
(let ((expensive-data (compute-expensive-debug-info)))
(log-package.debug "Debug info" :data expensive-data)))実行時に Logger に Appender を追加できます。
;; 既存の Logger に Appender を追加
(add-appender :root
(make-file-appender
:filepath "/var/log/myapp/debug.log"
:formatter (make-instance '<text-formatter>)))
;; 複数の Appender を持つ Logger
(let ((logger (get-logger :root)))
;; この Logger は console と file の両方に出力する
(log-to :root :info "This message goes to both console and file"))(defun setup-logging ()
"アプリケーションのログ設定を初期化"
;; ルート Logger: コンソールと一般ログファイル
(register-logger :root
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))
(add-appender :root
(make-file-appender
:filepath "/var/log/myapp/app.log"
:formatter (make-instance '<text-formatter>)))
;; SQL Logger: SQL専用ログファイル(JSON形式)
(register-logger :sql
:level :debug
:appender (make-file-appender
:filepath "/var/log/myapp/sql.log"
:formatter (make-instance '<json-formatter>)))
;; Web アクセスログ: アクセスログ専用ファイル
(register-logger :web-access
:level :info
:appender (make-file-appender
:filepath "/var/log/myapp/access.log"
:formatter (make-instance '<text-formatter>)))
;; 監査ログ: 監査専用ファイル(JSON形式)
(register-logger :audit
:level :info
:appender (make-file-appender
:filepath "/var/log/myapp/audit.log"
:formatter (make-instance '<json-formatter>)))
;; タスクログ: タスク実行専用ファイル
(register-logger :task
:level :info
:appender (make-file-appender
:filepath "/var/log/myapp/task.log"
:formatter (make-instance '<text-formatter>))))
;; アプリケーション起動時
(setup-logging)(in-package #:myapp/controller)
(defun show-user (params)
"ユーザー情報を表示"
(let ((user-id (parse-integer (getf params :id))))
(with-log-context (:action "show-user" :user-id user-id)
(log-package.info "Fetching user")
(handler-case
(let ((user (find-user-by-id user-id)))
(if user
(progn
(log-package.info "User found")
(render-user user))
(progn
(log-package.warn "User not found")
(render-404))))
(error (e)
(log-package.error "Failed to fetch user" :error (princ-to-string e))
(render-500))))))(in-package #:myapp/model)
(defmethod save ((user <user>))
"ユーザーを保存"
(log-package.debug "Saving user" :user-id (ref user :id))
(when (log-level-enabled-p :trace)
(log-package.trace "User data" :data (show-model-data user)))
(handler-case
(progn
(validate user)
(if (has-error-p user)
(progn
(log-package.warn "Validation failed"
:errors (get-all-errors user))
nil)
(progn
(with-transaction
(if (ref user :id)
(update1 user)
(insert1 user)))
(log-package.info "User saved successfully"
:user-id (ref user :id))
user)))
(error (e)
(log-package.error "Failed to save user"
:error (princ-to-string e)
:user-id (ref user :id))
(error e))))(defun setup-logging-for-environment ()
"環境に応じたログ設定"
(ecase (get-environment)
(:development
;; 開発環境: すべて DEBUG レベル、コンソール出力
(register-logger :root
:level :debug
:appender (make-console-appender
:formatter (make-instance '<text-formatter>))))
(:test
;; テスト環境: すべて INFO レベル、ファイル出力
(register-logger :root
:level :info
:appender (make-file-appender
:filepath "/tmp/test.log"
:formatter (make-instance '<text-formatter>))))
(:production
;; 本番環境: INFO レベル、JSON形式でファイル出力
(register-logger :root
:level :info
:appender (make-file-appender
:filepath "/var/log/myapp/app.log"
:formatter (make-instance '<json-formatter>)))
;; エラーログは別ファイルに出力
(register-logger :root
:level :error
:appender (make-file-appender
:filepath "/var/log/myapp/error.log"
:formatter (make-instance '<json-formatter>))))));; 推奨: パッケージベースのログ出力
(log-package.info "User created" :user-id 123)
;; 非推奨: Logger 名を明示的に指定(必要な場合を除く)
(log-to :myapp/controller :info "User created" :user-id 123);; TRACE: 詳細なデバッグ情報(通常は無効)
(log-package.trace "Function arguments" :args args)
;; DEBUG: 開発時のデバッグ情報
(log-package.debug "Query result" :count (length results))
;; INFO: 通常の動作記録
(log-package.info "User logged in" :user-id 123)
;; WARN: 潜在的な問題
(log-package.warn "Slow query detected" :query-time 5.2)
;; ERROR: エラーが発生したが継続可能
(log-package.error "Failed to send email" :error error-message)
;; FATAL: 致命的なエラー
(log-package.fatal "Database connection lost" :error error-message);; 推奨: キーワード引数で構造化
(log-package.info "User action"
:user-id 123
:action "update"
:resource "profile")
;; 非推奨: メッセージに情報を埋め込む
(log-package.info (format nil "User ~A performed ~A on ~A" 123 "update" "profile"));; 推奨: ログレベルチェック
(when (log-level-enabled-p :debug)
(let ((debug-info (expensive-debug-computation)))
(log-package.debug "Debug information" :info debug-info)))
;; 非推奨: 常に重い処理を実行
(log-package.debug "Debug information"
:info (expensive-debug-computation));; 推奨: with-log-context でリクエストIDを共有
(with-log-context (:request-id request-id)
(process-request)
(save-result)
(send-response))
;; 非推奨: すべてのログに手動で追加
(log-package.info "Processing" :request-id request-id)
(log-package.info "Saved" :request-id request-id)
(log-package.info "Sent" :request-id request-id)原因1: Logger が登録されていない
;; 確認: Logger が登録されているか
(get-logger :root) ; => NIL の場合は未登録
;; 対処: Logger を登録
(register-logger :root
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))原因2: ログレベルが適切でない
;; 確認: ログレベルが有効か
(log-level-enabled-p :debug) ; => NIL の場合は無効
;; 対処: ログレベルを変更
(set-logger-level :root :debug)原因3: 階層が一致しない
;; :myapp/controller パッケージでログ出力
(in-package #:myapp/controller)
(log-package.info "Test") ; => 出力されない
;; 対処: ルート Logger を登録
(register-logger :root
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))
;; または: パッケージに対応する Logger を登録
(register-logger :myapp
:level :info
:appender (make-console-appender
:formatter (make-instance '<text-formatter>)))原因: ファイルパスが不正
;; 対処: ディレクトリが存在することを確認
(ensure-directories-exist "/var/log/myapp/")
(register-logger :root
:level :info
:appender (make-file-appender
:filepath "/var/log/myapp/app.log"
:formatter (make-instance '<text-formatter>)))clails の Logging システムは以下の特徴を持ちます。
- 階層的な Logger: パッケージ階層に基づいて自動的に Logger を選択
- 柔軟な出力先: コンソール、ファイルなど、複数の出力先をサポート
- カスタマイズ可能なフォーマット: テキスト、JSON など、用途に応じたフォーマット
- 動的な設定変更: 実行時にログレベルや Appender を変更可能
- コンテキスト管理:
with-log-contextによる構造化されたログ出力 - 用途別のログ出力: SQL、Web アクセス、監査、タスクなど、用途に応じたログ出力マクロ
- スレッドセーフ: 複数スレッドから安全にログ出力可能
詳細な API リファレンスについては、各関数の docstring を参照してください。