swift-log入門:Logger/LogHandler/MetadataProvider
概要
apple/swift-log
ライブラリは、Appleが提供し、さまざまなライブラリから使用されている安定したロギングライブラリです。インターフェイスはシンプルで利用する際に考慮することはほとんどありませんが、この記事ではapple/swift-log
の基本的なAPIについて紹介します。このAPIはapple/swift-metrics
やapple/swift-distributed-tracing
などのエコシステムでもほとんど同じAPIであるため、これを理解しておくことで他のライブラリの理解が容易になります。
apple/swift-log: Logging
apple/swift-log
はApple公式のロギングのエコシステムです。以下のことができます。
- 安定したインターフェイスでログを記録できる
-
LogHandler
でログの出力先を変更可能 -
MetadataProvider
でログに情報を付加できる - 指定したログレベル未満のログを無視する
依存追加方法
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "swift-log-playground",
platforms: [.macOS(.v15)],
dependencies: [
+ .package(url: "https://github.com/apple/swift-log", from: "1.0.0")
],
targets: [
.executableTarget(
name: "Playground",
dependencies: [
+ .product(name: "Logging", package: "swift-log"),
],
swiftSettings: swiftSettings,
)
],
swiftLanguageModes: [.v6],
)
var swiftSettings: [SwiftSetting] {
[
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("NonescapableTypes"),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault"),
]
}
デフォルトの挙動
デフォルトでは、標準エラー(standard error)にログが書き込まれます。
Label.init(label:)
で初期化した場合には、LoggingSystem
に設定されているMetadataProvider
とLogHandler
のFactory
が使用されます。
またLoggingSystem
ではデフォルトで
-
LogHandler
:StreamLogHandler.standardError(label: String)
-
MetadataProvider
: nil
を使用します。
import Logging
@main
enum Playground {
static func main() async throws {
let logger = Logger(label: "default")
logger.info("デフォルトログ")
// 2025-10-05T21:47:15+0900 info default: [Playground] デフォルトログ
}
}
ログの形式は以下のものが空白区切りになっています
時刻(%Y-%m-%dT%H:%M:%S%z)
ログレベル(trace|debug|info|notice|warning|error|critical)
-
ラベル名
: キー順でソートされたメタデータ(例: key1=value1 key2=value2 key3=value3)
- [
ソース
] メッセージ
LogHandlerのカスタマイズ
Logger
の初期化時にLogHandler
のfactory
関数を指定するか、LoggingSystem.bootstrap(factory: (String) -> any LogHandler)
でデフォルトのLogHandler
を変更することによって使用するLogHandler
をカスタマイズすることができます。
特定のLogger
だけカスタマイズしたい場合には初期化時に、グローバルにカスタマイズしたい場合にはLoggingSystem
で設定します。
import Logging
@main
enum Playground {
static func main() async throws {
struct CustomLogHandler: LogHandler {
var label: String
var metadata: Logger.Metadata
var logLevel: Logger.Level
subscript(metadataKey key: String) -> Logging.Logger.Metadata.Value? {
get { metadata[key] }
set { metadata[key] = newValue }
}
func log(
level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt
) {
print("❤️ level", level)
print("🧡 message", message)
print("💛 metadata", metadata?.description ?? "<none>")
print("💚 source", source)
print("🩵 file", file)
print("💙 function", function)
print("💜️ line", line)
}
}
LoggingSystem.bootstrap { label in
CustomLogHandler(label: label, metadata: ["meta": "data"], logLevel: .debug)
}
let logger = Logger(label: "custom")
logger.info("カスタムログ")
/*
❤️ level info
🧡 message カスタムログ
💛 metadata <none>
💚 source Playground
🩵 file Playground/Playground.swift
💙 function main()
💜️ line 36
*/
}
}
インターフェイスから、ログ出力時には、以下のものが得られることがわかります。
- ログレベル(trace|debug|info|notice|warning|error|critical)
- メッセージ
- メタデータ(
[String: MetadataValue]
) - ソース(モジュール名)
- file(ファイルパス)
- function(関数名)
- line(行番号)
MetadataProviderのカスタマイズ
MetadataProvider
もLoggingSystem.bootstrap
またはLogger.init
の際に指定することで使用することができます。MetadataProvider
はデフォルト値がnil
であるため、指定しない限りはメタデータはログに出力されません。主にトレースIDなどの情報の付与に使用されます。
import Logging
@main
enum Playground {
@TaskLocal static var metadata = Logger.Metadata()
static func main() async throws {
let metadataProvider = Logger.MetadataProvider { metadata }
LoggingSystem.bootstrap(
{ label, provider in
StreamLogHandler.standardOutput(label: label, metadataProvider: metadataProvider)
},
metadataProvider: metadataProvider
)
let logger = Logger(label: "custom")
$metadata.withValue([
"user_id": "1",
"teams": ["a", "b", "c"],
"roles": ["a": "admin", "b": "normal"],
]) {
logger.info("メタデータログ")
// 2025-10-05T22:18:36+0900 info custom: roles=["a": "admin", "b": "normal"] teams=["a", "b", "c"] user_id=1 [Playground] メタデータログ
}
}
}
VaporにおけるLoggingSystem
Vapor
の標準テンプレートでは以下のようなコードでLoggingSystem
の設定が行われています。
import Vapor
import Logging
import NIOCore
import NIOPosix
@main
enum Entrypoint {
static func main() async throws {
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
// ....
}
}
Vapor 4
では、ログの出力に
-
vapor/console-kit
を使用して標準出力 standard outputに書き込まれるようになります -
MetadataProvider
はnil
に設定されており、metadataはデフォルトで出力されません。 - ログレベルは次の優先順で決まります
- 起動時の引数
--log
で指定されたLogLevel
- 環境変数または
.env
のLOG_LEVEL
- デフォルトで
production
モードであればnotice
そうでなければinfo
- 起動時の引数
- デフォルトではログに以下の情報を空白区切りで出力します。
- [
ラベル
] ※ログレベルがtrace以下の場合のみ出力 - [
ログレベル(レベルによって色が変わる)
] メッセージ
- [
メタデータ(例: meta1: value1, meta2: value2)
] - (
ファイル名
:行番号
) ※ログレベルがdebug以下の場合のみ出力
- [
あとがき
普段気にしていませんでしたが、デフォルトのLogger
ってStandard Error
側に出るんですね。そして、Vapor
使うとStandard Output
に出るんですね。初めて知りました。
LoggingSystem
とかMetricsSystem
とかではやはり@unchecked Sendable
がよく使われていました。これらの設定が動的に変更するような場面はないので大丈夫なのでしょうし、互換性の都合上仕方がないものではありますが、Apple公式に使用されると少し切ない気持ちになりますね。
Discussion