Swiftプロジェクト内でOSLogを使用する

# 概要

ネット上にOSLogの使い方をまとめた記事はあったが、
- 手元で動かせるサンプルが欲しかった
- ログ初心者なので使い所の解釈がいまいち分からなかった

ので自分なりにまとめました。

## OSLogとは？
Apple側で用意してくれているSwiftプロジェクト内で使用できるLogを取るためのライブラリ。
公式ドキュメントは[ここ](https://developer.apple.com/documentation/os/oslog)。

## サンプル
https://github.com/Kuehar/OSLogSample

```Swift:ContentView.swift
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つ並ぶ。

![スクリーンショット 2023-10-16 11.07.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/ca9b9ba0-c113-46ad-301f-1b9ccf7b34fa.png)





画面の各ボタンをタップしたときにログがXCodeのコンソール上に表示される。
それぞれの種類については後述。


![スクリーンショット 2023-10-16 11.09.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/67a804b1-361b-788c-1293-501b26a877bc.png)


詳細情報を表示したい場合はログが表示されているウィンドウのトグルボタンが二つ並んでいる箇所をタップすれば表示する情報を設定可能。

![スクリーンショット 2023-10-16 11.10.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/f29028e6-c7ab-2f76-1ffe-b8cf979992d0.png)

全てにチェックを入れた場合はこんな感じの情報が表示される。
![スクリーンショット 2023-10-16 11.12.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/6b7b9973-d648-2360-824a-8ce9153d1435.png)

個人的にめっちゃ便利だと思ったのが、この直下の画像みたいに、ログのRowにカーソルを合わせるとどこのコードがログを出したかを教えてくれて、矢印マークをクリックした時にそのソースコードにジャンプしてくれること。

![スクリーンショット 2023-10-16 11.13.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/a8424e30-6bcc-4b14-2c65-343dc965cbba.png)


ログの確認方法としては、`Console.app`を立ち上げ、以下のように検索バーで`ApplicationCode`カテゴリを検索すると`Notice`,`Warning`,`Error`,`Critical`,`Fault`のログが確認できる。

![スクリーンショット 2023-10-16 11.20.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412789/7489ecf9-356c-9793-de1d-5178a724915d.png)


## 調査して気になったポイント

### 1.短期的にはprintデバッグを使えば良いのでは？

そういえば明確な使い分けの基準を考えたことがありませんでした。
致命的なバグはログで拾った方が良いけど変数レベルの表示であれば`print`で十分じゃない？程度の認識です。

一旦、`print`デバッグとLogについて自分なりに整理してみました。

**`print`デバッグ**

***メリット***
- 簡単に使える。単にコンソールにテキストを出力するだけで、特別なセットアップが不要。

**デメリット**
- 本番環境での`print`の多用はパフォーマンスに悪影響を与える可能性がある。
- XCodeでは`print`デバッグの情報はビルドの度に削除される(ビルド履歴から各ビルドでのコンソールの出力結果は確認可能だが、何回もビルドしているとどのビルドかぱっと見わからなくなる)ため、検索したい時に不便。
- ソース読んだ時にその場しのぎのprint()文があるとなんかモヤっとする(個人的な思想)

**`OSLog`によるロギング**

メリット:
- ログをカテゴリ化し、適切なレベルで表示することができる。
- ログの出力先やレベルを制御するための設定が可能。
- 重要度の高い処理の有用なログが残るため、本番環境でのトラブルシューティングが可能。

デメリット:
- 一般的には`print`よりも設定や使い方が少し複雑。


それぞれの内容について整理してみたが、基本的にはOSLogで良くね？という感じ。
`Console.app`を使えば過去のログも検索もしやすくなるのでデバッグしやすくなる。

基本路線として、自身のローカル環境でprintを使うのは構わないが、Pushするときにprintデバッグは無くしておきましょうの管理が個人的にはしっくりくる感じ。
今までの経験的に自分が必要だと思う情報は他人が触る時にも気になる情報だと思うので、必要ならdebugとかで宣言しておいてくれる方が優しいと思う。


### 2.種類がたくさんあるけど使い分けの基準は？

そこに関しては世間的に標準的な分け方が定まっているようで、基本的にはそこに従う形で良いと思われる。
[ログ設計指針](https://qiita.com/nanasess/items/350e59b29cceb2f122b3) 

色々調査してみた結果、以下のような分類がされていることが多いように見受けられる。

|  | 目的 | 使用基準 | 具体例(アプリケーションのログインをロギングする場合) |
| ---- | ---- | ---- | ---- |
| Trace | 具体性の高い情報またはアプリケーション全体の処理状況などを追跡するために使用する | アプリケーションの各ステップやメソッド呼び出し、ループなどの詳細な動作をトレースするための情報 | ・大量のデータ処理の進捗状況や詳細なアルゴリズムの実行ステップ。 |
| Debug | デバッグ情報や詳細なステップをトレースするために使用する | 変数の値や関数の呼び出し、処理の流れなど、開発者がアプリケーションの動作を理解するのに役立つ情報 | ・ユーザー名やパスワードの取得。<br>・ログイン画面の表示。 |
| Info | 一般的な情報や処理の進捗などの管理のために使用する | 重要な操作の開始や終了、ユーザーのアクション、ネットワークリクエストなどの重要なアプリケーションイベント | ・ユーザーがログインを試みたことの記録。<br>・ログイン処理が開始されたことの記録。 |
| Notice | 通常の動作の中で特筆すべき事象を通知するために使用する | 通常の動作だが特に注目する価値がある状況、特に重要なアプリケーションイベント | ・特定のモジュールの初期化が完了した時。<br>・外部サービスへの接続が成功した時 |
| Warning | 問題や異常が発生したが、アプリケーションの正常な動作に影響を与えないことを通知するために使用する | 予期しない状況、潜在的な問題、データの損失の可能性など、注意が必要な状況| ・パスワードが弱い場合に警告する。<br>・ログイン試行回数が一定以上になった場合の警告。 |
| Error | アプリが正常に動作しなくなるようなエラーや重大な問題を通知するために使用する  | エラーの発生、失敗した操作、予期しない状況など、アプリケーションが期待された振る舞いをしない状況| ・ユーザー名が見つからない場合のエラー。<br>・パスワードが間違っている場合のエラー。 |
| Critical | システムの状態が危険で、直ちに対処する必要があることを通知するために使用する  | エラーの発生、失敗した操作、予期しない状況など、アプリケーションが期待された振る舞いをしない状況| ・サーバーとの通信が完全に失敗した時。<br>・セキュリティ侵害の試みが検出された時。 |
| Fault | システムが致命的な障害に陥ったことを通知するために使用する | システムの状態が致命的で、アプリケーションが停止したり、重大な損失が発生したりする可能性がある状況| ・システム全体がクラッシュした時。<br>・データベースが利用不可能になった時。 |

### 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で書いている人もいるようなのですがそこら辺はチームでの運用次第かと。

```Swift:AppLogger.swift
import Foundation
import OSLog

class AppLogger {
    static let shared = AppLogger()
    let logger: Logger
    
    private init() {
        self.logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ApplicationCode")
    }
}
```

```Swift:ContentView.swift
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内で呼び出すと良い。