1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SwiftのOSLogでログを取得するときにログ内にログレベルを埋め込む方法と、OSLogStoreでログレベルが記載されているログだけを抽出する方法

Last updated at Posted at 2024-01-29

概要

以前、OSLogの記事を書きましたが、こちらの記事で記述した方法だとログ本体にログレベルが付与されないため、ログを分析する基盤に送付したとしても分析することが難しいという点に気付きました。

当記事では、

  • OSLogを使用して取得したログにログレベルを自動で付与する
  • OSLogStoreでそれらのログを取得するときにログレベルが付与されたログのみを抽出する

という内容について解説します。


詳細

OSLogを使用して取得したログにログレベルを自動で付与する

OSLogにはTrace,Debug,Info,Notice,Warning,Error,Critical,Faultの8種類のログがあるので、それぞれのログに自動的にログレベルを付与するために、こちらの方の記事を参考にして、以下のようなextensionを作成しました。

extension Logger {
    static var level = OSLogType.info

    init(label: String) {
        self.init(subsystem: "", category: label)
    }

    func trace(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .debug, "[TRACE][\(file):\(function):\(line)] \(message)")
    }

    func debug(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .debug, "[DEBUG][\(file):\(function):\(line)] \(message)")
    }

    func info(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .info, "[INFO][\(file):\(function):\(line)] \(message)")
    }

    func notice(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .debug, "[NOTICE][\(file):\(function):\(line)] \(message)")
    }

    func warning(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .error, "[WARNING][\(file):\(function):\(line)] \(message)")
    }

    func error(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .error, "[ERROR][\(file):\(function):\(line)] \(message)")
    }

    func critical(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .fault, "[CRITICAL][\(file):\(function):\(line)] \(message)")
    }

    func fault(
        _ message: String = "", function: String = #function, line: Int = #line,
        file: String = #file
    ) {
        self.log(level: .fault, "[FAULT][\(file):\(function):\(line)] \(message)")
    }
}

このextensionにより、自動的にログレベル呼び出し元の関数呼び出し元の関数のファイル内の行が自動でログに付与されるようになります。

実例として、SwiftUIのボタンをタップしたときに全ての種類のログが実行されるようにしてみます。

Button("ログ出力") {
            AppLogger.shared.logger.trace("trace:\(Date())")
            AppLogger.shared.logger.debug("debug:\(Date())")
            AppLogger.shared.logger.info("info:\(Date())")
            AppLogger.shared.logger.notice("notice:\(Date())")
            AppLogger.shared.logger.warning("warning:\(Date())")
            AppLogger.shared.logger.error("error:\(Date())")
            AppLogger.shared.logger.critical("critical:\(Date())")
            AppLogger.shared.logger.fault("fault:\(Date())")
        }

このボタンをタップすると、以下のような形式でログが出力されます。

[TRACE][...ContentView.swift:body:25] trace:2024-01-24 08:55:24 +0000
[DEBUG][...ContentView.swift:body:25] debug:2024-01-24 08:55:24 +0000
[INFO][...ContentView.swift:body:26] info:2024-01-24 08:55:24 +0000
[NOTICE][...ContentView.swift:body:27] notice:2024-01-24 08:55:24 +0000
[WARNING][...ContentView.swift:body:28] warning:2024-01-24 08:55:24 +0000
[ERROR][...ContentView.swift:body:29] error:2024-01-24 08:55:24 +0000
[CRITICAL][...ContentView.swift:body:30] critical:2024-01-24 08:55:24 +0000
[FAULT][...ContentView.swift:body:31] fault:2024-01-24 08:55:24 +0000

これで目的だったログレベルが自動で付与されるようになりました。

OSLogStoreでそれらのログを取得するときにログレベルが付与されたログのみを抽出する

次に、抽出処理です。
先述のログを外部のログ分析基盤(Cloudwatch Logsなど)に送付する場合、OSLogStoreを使用することになると思いますが、こちらの記事で解説している通り、OSLogStoreで蓄積されたログを取得すると、iPhone本体のログなどが混ざるため、送付するログとしては適切ではありません。

そこで、ログレベルが付与されているログだけを抽出するために以下のような関数を用意しました。

func exportLog(startDate: Date, userName: String) async {
            do {
                let store = try OSLogStore(scope: .currentProcessIdentifier)
                let logLevels = [
                    "[INFO]", "[TRACE]", "[DEBUG]", "[WARNING]", "[ERROR]",
                    "[CRITICAL]", "[FAULT]",
                ]
                
                let position = store.position(date: startDate)
                let enumerator = try store.__entriesEnumerator(
                    options: [.reverse], position: position, predicate: nil)

                enumerator.forEach { element in
                    if let logEntry = element as? OSLogEntry {
                        let message = logEntry.composedMessage
                        for logLevel in logLevels {
                            if message.starts(with: logLevel) {
                                logMessages += message + "\n"
                            }
                        }
                    }
                }
            // ここに送信処理を実装
            } catch {
                print("Error exporting logs: \(error)")
            }
        }

この関数を使用することによって、OSLogのログの内、ログレベルが埋め込まれているログのみを抽出することが可能です。

OSLogのログ
...
CF Read: domain = com.apple.Accessibility, preference = SpeechSettingsDisabledByManagedConfiguration, appID = (null) result = (null) (-1 - empty, 0 - false, 1 - true)
CF Read: domain = com.apple.Accessibility, preference = QuickSpeak, appID = (null) result = (null) (-1 - empty, 0 - false, 1 - true)
Evaluating dispatch of UIEvent: 0x600003914000; type: 0; subtype: 0; backing type: 11; shouldSend: 1; ignoreInteractionEvents: 0, systemGestureStateChange: 0
Sending UIEvent type: 0; subtype: 0; to windows: 1
Sending UIEvent type: 0; subtype: 0; to window: <UIWindow: 0x108214410>; contextId: 0xF00E590D
Evaluating dispatch of UIEvent: 0x600003914000; type: 0; subtype: 0; backing type: 11; shouldSend: 0; ignoreInteractionEvents: 0, systemGestureStateChange: 1
Evaluating dispatch of UIEvent: 0x600003914000; type: 0; subtype: 0; backing type: 11; shouldSend: 1; ignoreInteractionEvents: 0, systemGestureStateChange: 0
Sending UIEvent type: 0; subtype: 0; to windows: 1
Sending UIEvent type: 0; subtype: 0; to window: <UIWindow: 0x108214410>; contextId: 0xF00E590D
[TRACE][body:24] trace:2024-01-23 10:03:26 +0000
[DEBUG][body:25] debug:2024-01-23 10:03:26 +0000
[INFO][body:26] info:2024-01-23 10:03:26 +0000
[NOTICE][body:27] notice:2024-01-23 10:03:26 +0000
[WARNING][body:28] warning:2024-01-23 10:03:26 +0000
[ERROR][body:29] error:2024-01-23 10:03:26 +0000
[CRITICAL][body:30] critical:2024-01-23 10:03:26 +0000
[FAULT][body:31] fault:2024-01-23 10:03:26 +0000
....
関数を使用して特定のログのみを抽出した結果(一部ディレクトリを省略しています)
[TRACE][...ContentView.swift:body:25] trace:2024-01-24 08:55:24 +0000
[DEBUG][...ContentView.swift:body:25] debug:2024-01-24 08:55:24 +0000
[INFO][...ContentView.swift:body:26] info:2024-01-24 08:55:24 +0000
[NOTICE][...ContentView.swift:body:27] notice:2024-01-24 08:55:24 +0000
[WARNING][...ContentView.swift:body:28] warning:2024-01-24 08:55:24 +0000
[ERROR][...ContentView.swift:body:29] error:2024-01-24 08:55:24 +0000
[CRITICAL][...ContentView.swift:body:30] critical:2024-01-24 08:55:24 +0000
[FAULT][...ContentView.swift:body:31] fault:2024-01-24 08:55:24 +0000

以上です。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?