はじめに
os.Logger
とは、iOS 14.0+から使えるロギングのAPIです。
print(_:)
よりもログに特化していて便利なので、使い方などを紹介します。
環境
- OS: macOS Ventura 13.5.2
- Swift: 5.9
- Xcode: 15.0 (15A240d)
ロガーのインスタンス生成
まずは os.Logger
のインスタンスを生成します。
毎回サブシステムやカテゴリを指定するのが手間なので、私は簡単なラッパーを作成して使っています。
import Foundation
import os
public enum Logger {
public static let standard: os.Logger = .init(
subsystem: Bundle.main.bundleIdentifier!,
category: LogCategory.standard.rawValue
)
}
// MARK: - Privates
private enum LogCategory: String {
case standard = "Standard"
}
これで以下のようにワンライナーで呼び出せます。
Logger.standard.info("Info")
サブシステムとカテゴリ
サブシステムとカテゴリは任意で指定します。
サブシステムはアプリの大きな機能領域に対応し、カテゴリはアプリの特定のサブシステム内の特定の領域に対応しています。
サブシステムについて、通常はバンドルIDと同じ値を使うと 公式ドキュメント に記載されているので、 Bundle.main.bundleIdentifier!
を指定しています。
カテゴリは本来はしっかり考えて指定すべきですが、小規模な個人アプリの場合は Standard
のみでもいいかなと思っています。
noppe さんの場合、カテゴリは #file
にしているとのことです。
ログ出力メソッド
ログの出力はスコープごとに8種類のメソッドが用意されています。
機能的に同等なメソッドがいくつかあるので、実質は5種類です。
すべてのメッセージはメモリ内に保存されます。
ログレベルに応じてディスク上にもストレージ制限まで書き込まれます。事前に定義されたサイズを越えると、古いメッセージを削除します。
ログレベルが上がると、システムが追加情報を取得して、そのすべてをディスク上に書き込むことが多いため、より多くのオーバーヘッドが発生します。
具体的には error
以上でアクティビティオブジェクトが存在する場合、関連するプロセスチェーンの情報を取得して書き込みます。
以上より、適切なログレベルのメソッドを呼び出すのが望ましいです。
メソッドをログレベルの重篤度の昇順に紹介します。
メソッド(ログレベル) | 説明 | ディスクに書き込むか | 備考 |
---|---|---|---|
debug(_:) |
デバッグのみに役立つ情報を開発中に書き込む。 | ❌ | |
trace(_:) |
トレース情報を書き込む。 | ❌ |
debug(_:) と同等 |
info(_:) |
必須ではないが、問題のトラブルシューティングに役立つ情報を書き込む。 | 🔺ログツールで収集する場合のみ書き込む | |
notice(_:) |
デフォルトのログタイプ。問題のトラブルシューティングに不可欠な情報を書き込む。 | ✅ |
log(_:) と同等 |
error(_:) |
コードの実行中に発生したエラーを書き込む。 | ✅ | |
warning(_:) |
警告に関する情報を書き込む。 | ✅ |
error(_:) と同等 |
fault(_:) |
コード内の不具合に関する情報を書き込む。 | ✅ | |
critical(_:) |
アプリの実行における重要なイベントを書き込む。 | ✅ |
fault(_:) と同等 |
プロジェクトで指針を決めるのがベストだと思いますが、どれを使うか迷ったら以下のように考えるといいと思います。
- 開発のために値を出力したい →
debug(_:)
- 何となく役立ちそうな値を出力したい →
info(_:)
- 確実に役立つ情報を出力したい →
notice(_:)
- エラーを出力したい →
error(_:)
- エラー以外の不具合に関する情報を出力したい →
fault(_:)
trace(_:)
・ warning(_:)
・ critical(_:)
はログレベルをより細かく分類したい場合のみ使うといいかもしれません。
OSLogMessage
各メソッドの引数は OSLogMessage
ですが、インスタンスを直接生成することはありません。
補完文字列を渡すと、システムが自動的に OSLogMessage
へ変換します。
そのため、例えば String
型を渡すようなラッパーを作ると補完文字列が使えなくなるのでオススメしません。
public final class MyLogger {
public func debug(_ message: String) {
Logger.standard.debug(message) // 🔺: 補完文字列が使えない
}
}
Message Argument Formatters
型認識のフォーマッターを使って、メッセージの補完値のプライバシーと表示を管理します。
よく使うのは OSLogPrivacy
です。
変数はユーザーのプライバシー保護のため、値によって自動的にredact(公表する文書から個人情報などを削除)します。
デフォルトでは、整数値、浮動小数点値、ブール値はredactしませんが、動的な文字列と複雑で動的なオブジェクトの内容はredactします。
動的な文字列をredactさせないためには、 privacy
に .public
を指定します。
let uhooiName = "Uhooi"
Logger.standard.info("Uhooi name: \(uhooiName, privacy: .public)")
String
型では privacy
のような補完文字列を使えないため、この部分はラップできないというわけです。
ログ出力
以下を実行してログを出力します。
let uhooiName = "Uhooi"
Logger.standard.debug("Debug: \(uhooiName, privacy: .public)")
Logger.standard.info("Info: \(uhooiName, privacy: .public)")
Logger.standard.notice("Notice: \(uhooiName, privacy: .public)")
Logger.standard.error("Error: \(uhooiName, privacy: .public)")
Logger.standard.fault("Fault: \(uhooiName, privacy: .public)")
Xcodeに出力される結果です。
2023-09-16 18:26:11.842691+0900 Develop[99999:999999] [Standard] Debug: Uhooi
2023-09-16 18:26:11.842722+0900 Develop[99999:999999] [Standard] Info: Uhooi
2023-09-16 18:26:11.842766+0900 Develop[99999:999999] [Standard] Notice: Uhooi
2023-09-16 18:26:11.842811+0900 Develop[99999:999999] [Standard] Error: Uhooi
2023-09-16 18:26:11.842858+0900 Develop[99999:999999] [Standard] Fault: Uhooi
{Timestamp} {Library}[{PID}:{TID}] [{Category}] {Message}
の形式で出力されています。
おわりに
os.Logger
の説明と使い方を紹介しました。
便利なのにあまり使われている話を聞かないので、これで少しでもユーザーが増えると嬉しいです
またXcode 15 + iOS 17からはIDEにロギングが統合され、さらに便利になりました。
Xcode 15が正式リリースされたら別記事で紹介したいです。