【Xcode15】OSLog・Loggerの最適化に本気で向き合ってみる。【swift】【swiftUI】
XcodeのOSLogが便利になったことを知る
Xcode15から、OSLogがXcodeコンソールに統合されるとの発表がありました。
これによって、カテゴリなどに合わせてログをフィルタリングすることが可能になります。
OSLogは正直雑多で活用しづらい印象がありましたし、私はこれまであまり使っていませんでした。以前勤めていた会社ではわざわざ独自のLoggerを作成してコードに埋め込んでいたくらいです。しかし、今回のアップデートで標準のLoggerがかなり便利になると確信したので、本気で向き合ってみることにしました。
print()でよくない?
軽いデバッグならprintでも良いかもしれませんが、定常的に確認するためのログを仕込むにはOSLogの方が適しています。このログが何時何分何秒に呼ばれたものなのか、などの情報がOSLogには含まれるからです。
大きな規模のプロジェクトになればなるほど、開発初期段階でログを仕込んでおくだけでも、開発効率が爆上がりすること間違いなしです。どこでエラーが発生しているのか、コンソールをみるだけである程度想像できるような状態にするのが理想的ですね。
また、今回のアプデでさらに便利に使えるようになりました。特に注目すべきなのが「Jump to Source」。ログから直接該当箇所のソースコードまで飛ぶことが可能になりました。これはとても嬉しい機能ですね!!(AndroidStudioでは当たり前のようにできる機能ですが。。)
今回は、Loggerをより便利に活用する方法について本気で向き合ってみました。
Xcode15のbeta版が利用できるので、こちらで試してみたいと思います。
環境・前提条件
Xcode15 beta
Simulator iPhone14ProMax iOS17
:::note warn
Xcode15であることと、動かしているシミュレーターないし実機がiOS17以上でないとうまく動作しないのでご注意ください!自分はこれにハマってしばらく時間を溶かしてしまいました。
:::
なお、OSLogの実装はiOS14以降とiOS13以前で異なります。今回はiOS14以降の方法で実装します。
iOS13以前の方法が気になる方は以下がとても参考になるので、参照してみてください。
Loggerを用いたログ出力の方法
Loggerを使用する場合、まずは以下の手順を踏む必要があります。
1. Loggerインスタンスを生成
以下のようにLoggerインスタンスを生成します。
/// 共通Loggerインスタンス
let logger = Logger(subsystem: "com.tamina.gachagacha", category: "Logger")
このインスタンスで指定しているSubSystemやCategoryによって、Xcodeのコンソールでフィルタリングすることが出来るようになります。
また、これまではTimestampなどの情報が常にログ表示されていたと思いますが、フィルタリングをかけることで非表示にすることも可能となります。自分がアクセスしたい情報により簡単にアクセスしやすくなります。
上記の写真では、例えば「viewDidAppear(_:)」のように関数名を表示されていますが、これは私が指定しています。この方法についても後ほど説明しますが、現時点ではとりあえずフィルタリング機能が便利になったことだけ認識してもらえればOKです。
2. ログレベルに応じてログ出力関数を呼び出す
先ほど定義したloggerインスタンスを使って、ログレベルに応じた関数を選びます。
以下のようにログレベルが設定されているらしいので、必要に応じて使い分ける必要があります。私の場合は基本デバッグ目的でしかログを出力させないので、trace、info、warning、errorあたりを使うつもりでいます。
public enum Level: String, Codable, CaseIterable {
/// Appropriate for messages that contain information only when debugging a program.
case trace
/// Appropriate for messages that contain information normally of use only when
/// debugging a program.
case debug
/// Appropriate for informational messages.
case info
/// Appropriate for conditions that are not error conditions, but that may require
/// special handling.
case notice
/// Appropriate for messages that are not error conditions, but more severe than
/// `.notice`.
case warning
/// Appropriate for error conditions.
case error
/// Appropriate for critical error conditions that usually require immediate
/// attention.
///
/// When a `critical` message is logged, the logging backend (`LogHandler`) is free to perform
/// more heavy-weight operations to capture system state (such as capturing stack traces) to facilitate
/// debugging.
case critical
}
この状態ですでにログ出力の実装は完了です。ビルド実行してみると、Xcodeのコンソールに適切にログが出力されていることを確認できると思います。意外と簡単ですね。
普通に使う分にはこれでOKだと思います。
…が、この状態では満足できないとある理由がありました。
トレースログは、脳死で仕込めるような状態にしたい。
私は主にトレースログ目的で使うつもりだったので、仕込む際にあまり頭を働かせたくありませんでした。
logger.trace()
のような感じで、各関数にサクッと仕込めるような形が理想的だったのです。しかしながら、以下のような気になる点が出てきてしまいました。
気になる点①:引数を必ず渡さなくてはならない
logger.trace("")
のように、ログ関数を呼び出す際には必ずOSLogMessage型の引数を与えなければならないようでした。
私の場合はただどの関数が呼ばれているのかを把握したいだけなので、引数を与えずに呼び出せるのが理想的。なんとか引数を与えずに呼び出したいと工夫を試みましたが結論出来ませんでした。
extensionを使ってLoggerの拡張を試みましたが、OSLogMessageという型の縛りがあるためうまくいかず…これが純粋なString型であれば何とかなりそうだったのですが…。
気になる点②:引数を空文字列にすると、(null)というログが出力される
空文字を指定すると、このようなログが出力されてしまいます。
使えないとまでは言いませんが、このようなログが大量に吐き出されるなんて、想像しただけでログを読む気が失せてしまいますよね。。
もっと解析に使えそうな情報が表示されるように調整したいと考えました。
解決策:
上記の課題を解決するために考えた結果、私は以下のようにして呼び出すことにしました。
logger.trace(#function)
#function は、Swiftプログラミング言語で使用される特殊な識別子です。この識別子は、現在実行中の関数やメソッドの名前を表すために使用されます。その関数やメソッドの名前が文字列として取得できるのです。
functionの他にもさまざまな情報が取得できます。
ファイル名 ▶︎ #file
関数名 ▶︎ #function
コード列 ▶︎ #line
この呼び出し方にすることで、先ほど上でお店したように関数名を表示できるようになります。かなり読みやすくなることがわかると思います。私と同じようにトレースログ目的で使いたい方は、ぜひ参考にしてみてくださいね!
おまけ.JumpToSourceを試してみる
最後にJumpToSourceを試してみたいと思います。
やり方は該当のログを右クリックして、JumpToSourceを選択するだけです。
選択すると、ソースコードの該当箇所へと飛ぶことができます!とっても便利です!!!!!
さらにおまけ
Twitterもやってます。
開発したアプリの一覧はこちらから!一番バズったアプリは「ガチャメーカー」です!
参考
Discussion
logger.trace(#function)
めっちゃいいですね! 使ってみます!