概要
ネット上にOSLogの使い方をまとめた記事はあったが、
- 手元で動かせるサンプルが欲しかった
- ログ初心者なので使い所の解釈がいまいち分からなかった
ので自分なりにまとめました。
OSLogとは?
Apple側で用意してくれているSwiftプロジェクト内で使用できるLogを取るためのライブラリ。
公式ドキュメントはここ。
サンプル
import SwiftUI
import OSLog
struct ContentView: View {
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ApplicationCode")
var body: some View {
VStack {
Button(action: {
logger.trace("Trace Log")
}, label: {
Text("Trace")
})
Button(action: {
logger.debug("Debug Log")
}, label: {
Text("Debug")
})
Button(action: {
logger.info("Info Log")
}, label: {
Text("Info")
})
Button(action: {
logger.notice("Notice Log")
}, label: {
Text("Notice")
})
Button(action: {
logger.warning("Warning Log")
}, label: {
Text("Warning")
})
Button(action: {
logger.error("Error Log")
}, label: {
Text("Error")
})
Button(action: {
logger.critical("Critical Log")
}, label: {
Text("Critical")
})
Button(action: {
logger.fault("Fault Log")
}, label: {
Text("Fault")
})
}
}
}
上記のコードを動かすと、以下のようにボタンが8つ並ぶ。
画面の各ボタンをタップしたときにログがXCodeのコンソール上に表示される。
それぞれの種類については後述。
詳細情報を表示したい場合はログが表示されているウィンドウのトグルボタンが二つ並んでいる箇所をタップすれば表示する情報を設定可能。
個人的にめっちゃ便利だと思ったのが、この直下の画像みたいに、ログのRowにカーソルを合わせるとどこのコードがログを出したかを教えてくれて、矢印マークをクリックした時にそのソースコードにジャンプしてくれること。
ログの確認方法としては、Console.app
を立ち上げ、以下のように検索バーでApplicationCode
カテゴリを検索するとNotice
,Warning
,Error
,Critical
,Fault
のログが確認できる。
調査して気になったポイント
1.短期的にはprintデバッグを使えば良いのでは?
そういえば明確な使い分けの基準を考えたことがありませんでした。
致命的なバグはログで拾った方が良いけど変数レベルの表示であればprint
で十分じゃない?程度の認識です。
一旦、print
デバッグとLogについて自分なりに整理してみました。
print
デバッグ
メリット
- 簡単に使える。単にコンソールにテキストを出力するだけで、特別なセットアップが不要。
デメリット
- 本番環境での
print
の多用はパフォーマンスに悪影響を与える可能性がある。 - XCodeでは
print
デバッグの情報はビルドの度に削除される(ビルド履歴から各ビルドでのコンソールの出力結果は確認可能だが、何回もビルドしているとどのビルドかぱっと見わからなくなる)ため、検索したい時に不便。 - ソース読んだ時にその場しのぎのprint()文があるとなんかモヤっとする(個人的な思想)
OSLog
によるロギング
メリット:
- ログをカテゴリ化し、適切なレベルで表示することができる。
- ログの出力先やレベルを制御するための設定が可能。
- 重要度の高い処理の有用なログが残るため、本番環境でのトラブルシューティングが可能。
デメリット:
- 一般的には
print
よりも設定や使い方が少し複雑。
それぞれの内容について整理してみたが、基本的にはOSLogで良くね?という感じ。
Console.app
を使えば過去のログも検索もしやすくなるのでデバッグしやすくなる。
基本路線として、自身のローカル環境でprintを使うのは構わないが、Pushするときにprintデバッグは無くしておきましょうの管理が個人的にはしっくりくる感じ。
今までの経験的に自分が必要だと思う情報は他人が触る時にも気になる情報だと思うので、必要ならdebugとかで宣言しておいてくれる方が優しいと思う。
2.種類がたくさんあるけど使い分けの基準は?
そこに関しては世間的に標準的な分け方が定まっているようで、基本的にはそこに従う形で良いと思われる。
ログ設計指針
色々調査してみた結果、以下のような分類がされていることが多いように見受けられる。
目的 | 使用基準 | 具体例(アプリケーションのログインをロギングする場合) | |
---|---|---|---|
Trace | 具体性の高い情報またはアプリケーション全体の処理状況などを追跡するために使用する | アプリケーションの各ステップやメソッド呼び出し、ループなどの詳細な動作をトレースするための情報 | ・大量のデータ処理の進捗状況や詳細なアルゴリズムの実行ステップ。 |
Debug | デバッグ情報や詳細なステップをトレースするために使用する | 変数の値や関数の呼び出し、処理の流れなど、開発者がアプリケーションの動作を理解するのに役立つ情報 | ・ユーザー名やパスワードの取得。 ・ログイン画面の表示。 |
Info | 一般的な情報や処理の進捗などの管理のために使用する | 重要な操作の開始や終了、ユーザーのアクション、ネットワークリクエストなどの重要なアプリケーションイベント | ・ユーザーがログインを試みたことの記録。 ・ログイン処理が開始されたことの記録。 |
Notice | 通常の動作の中で特筆すべき事象を通知するために使用する | 通常の動作だが特に注目する価値がある状況、特に重要なアプリケーションイベント | ・特定のモジュールの初期化が完了した時。 ・外部サービスへの接続が成功した時 |
Warning | 問題や異常が発生したが、アプリケーションの正常な動作に影響を与えないことを通知するために使用する | 予期しない状況、潜在的な問題、データの損失の可能性など、注意が必要な状況 | ・パスワードが弱い場合に警告する。 ・ログイン試行回数が一定以上になった場合の警告。 |
Error | アプリが正常に動作しなくなるようなエラーや重大な問題を通知するために使用する | エラーの発生、失敗した操作、予期しない状況など、アプリケーションが期待された振る舞いをしない状況 | ・ユーザー名が見つからない場合のエラー。 ・パスワードが間違っている場合のエラー。 |
Critical | システムの状態が危険で、直ちに対処する必要があることを通知するために使用する | エラーの発生、失敗した操作、予期しない状況など、アプリケーションが期待された振る舞いをしない状況 | ・サーバーとの通信が完全に失敗した時。 ・セキュリティ侵害の試みが検出された時。 |
Fault | システムが致命的な障害に陥ったことを通知するために使用する | システムの状態が致命的で、アプリケーションが停止したり、重大な損失が発生したりする可能性がある状況 | ・システム全体がクラッシュした時。 ・データベースが利用不可能になった時。 |
3.いちいち各ファイルでlet logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ApplicationCode")
宣言するのめんどいからプロジェクト内で共通化すべきでは?
用意しました。
ContentView
内からlogger
周りの宣言をAppLogger
に移してあげたのでSwiftUIファイル内のコードはスッキリします。
変更に伴い、ContentView
内でのロギング処理がlogger.trace("Trace Log")
からAppLogger.shared.logger.trace("Trace Log")
のような形式に変わっています。
人によってはextensionで書いている人もいるようなのですがそこら辺はチームでの運用次第かと。
import Foundation
import OSLog
class AppLogger {
static let shared = AppLogger()
let logger: Logger
private init() {
self.logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ApplicationCode")
}
}
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button(action: {
AppLogger.shared.logger.trace("Trace Log")
}, label: {
Text("Trace")
})
Button(action: {
AppLogger.shared.logger.debug("Debug Log")
}, label: {
Text("Debug")
})
Button(action: {
AppLogger.shared.logger.info("Info Log")
}, label: {
Text("Info")
})
Button(action: {
AppLogger.shared.logger.notice("Notice Log")
}, label: {
Text("Notice")
})
Button(action: {
AppLogger.shared.logger.warning("Warning Log")
}, label: {
Text("Warning")
})
Button(action: {
AppLogger.shared.logger.error("Error Log")
}, label: {
Text("Error")
})
Button(action: {
AppLogger.shared.logger.critical("Critical Log")
}, label: {
Text("Critical")
})
Button(action: {
AppLogger.shared.logger.fault("Fault Log")
}, label: {
Text("Fault")
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Swiftプロジェクト内では#function
で実行した関数の名前が出力されることになるので、Swiftの場合にはAppLogger.shared.logger.fault(#function)
のような形式で呼び出すと色々と捗りそう。
ただ、SwiftUIの場合にはView内で#function
と定義してもbody
としか返してくれないのでファイル名を出力したいなら#file
、コード列を出力したいなら#line
とするなど、欲しい情報をLogger内で呼び出すと良い。