Swiftにおけるロギング – WWDC2020

Session概要

Xcode 12で導入された、Swiftの統合ロギングAPI(os_log)の最新バージョンについて。
アプリでプライバシーの保護を実現し、イベントやエラーのログを取る方法を学びます。
パフォーマンスを落とすことなく、データフォーマット用のパワフル、かつ読みやすいオプションについて。
また、アプリ内の予期せぬ動作を認識しデバッグするのに役立つ、ログメッセージの収集および処理方法について https://developer.apple.com/videos/play/wwdc2020/10168/

Xcode 12での統合ログを使用するAPI

  • アプリ実行時に発生する重要なイベントをこれらのAPIで記録できる
  • ログはOSによりアーカイブされるため、後でデバイスから取得可能
  • 新しいAPIはオーバーヘッドが低く、アプリの動作遅延を招くことなく広範囲で使用可能
  • Loggerで文字列補間を使うと実行時間データをログメッセージに追加可能
  • ログメッセージには様々なデータ型が含まれる可能性があり、CustomStirngConvertibleプロトコルに準拠する型をロギング可能
    • そのため、独自の型をロギングする場合はCustomStringConvertibleプロトコルに準拠させる
  • 実行時の時間をロギングする場合は、初期設定で数値をログで編集するように設定されている点に注意
    • すなわち、数値情報はログで見れないようになっている
    • ログで可視化するためには、privacy: .public を付与する
  • ロギングされたメッセージはOSにより圧縮された形式でデバイスに保存される
    • Macの log collect コマンドで保存したログを取得可能
      • デバイスをMacに接続 → log collect コマンドの実行 → コンソールでアーカイブログを開く
      • コンソールアプリでは検索やフィルターができるためログの確認を容易にできる
// Add runtime data to the log messsage using string interpolation

import os

let logger = Logger(subsystem: "com.example.Fruta", category: "giftcards")

func beginTask(url: URL, handler: (Data) -> Void) {
    launchTask(with: url) {
       handler($0)
    }
    logger.log("Started a task \(taskId)")
}

Log Level

ログの重要度を示すレベルで、ロギング時に指定可能
下記は重要度が低いレベルから順に並んでいる
ErrorとFaultはコンソールアプリで黄色と赤で表現される
  • Debug
    • 有用なメッセージに対しデバッグ時に限り使用
    • 持続性はなく、アプリの実行が完了した後に取得することはできない
  • Info
    • トラブルシューティングエラーに有用ではあるが不可欠でないメッセージに使用
    • 持続性はないが、例外としてログ収集コマンドまでの短い時間に作成されたメッセージは残される
  • Notice(Default)
    • トラブルシューティングに不可欠なメッセージに使用
    • 継続性があり、後で取得可能
  • Error
    • 実行中に発生するエラーを記録するために使用
    • Noticeより継続性があり、後で取得可能
  • Fault
    • プログラム内の潜在的なバグが原因で発生する状況を記録するために使用
    • Noticeより継続性があり、後で取得可能

Log Levelを選ぶ際の考慮事項

  • 持続性
    • アプリの実行が終了した時点でログメッセージをアーカイブしたり取得することが可能かどうか
    • 持続性がないログの場合、アプリの実行中にしかストリーミングできなくなります
    • メッセージに持続性があるか否かはLog Levelによって異なる
      • メソッドの重要度が高いほど持続性も高くなる
    • ただし、アーカイブするメッセージの数には容量制限がある
      • 制限を超過すると、古いメッセージから削除され閲覧できなくなる
    • 通常、メッセージは数日間保持されるが、デバイスの空き容量によって異なる
  • パフォーマンス
    • ロギングでは一般的にオーバーヘッドが低くなるが、ログレベルでは互いに相対的に異なるパフォーマンスが保持され、重要度が低いレベル程速い

フォーマットによるログメッセージの整形

  • リーダビリティを向上させるデータのフォーマット方法
    • format
    • align
    • privacy
      • 対象データをマスクするかどうか
import SwiftUI
import os

let statisticsLogger = Logger(subsystem: "com.example.Fruta", category: "statistics")

// Log statistics about communication with a server.
// .left(columns: GiftCard.maxIDLength) により表示文字数を制限
// format: .fixed(precision: 2) で小数点第2位以下を省く
func logStatistics(taskID: UUID, giftCardID: String, serverID: Int, seconds: Double) {
    statisticsLogger.log("\(taskID) \(giftCardID, align: .left(columns: GiftCard.maxIDLength)) \(serverID) \(seconds, format: .fixed(precision: 2))")

    // logger.log("\(data, format: .hex, align: .right(columns: width))")

    // mask: .hash(等値保持ハッシュ)を使用することで実データを隠しつつ、
    // ログのフィルタリングに有用なロギング値が一致するタイミングを把握できる
    // 例えば、口座番号をハッシュ値で記録することで、口座番号を伏せたまま同じハッシュ値が参照された箇所を把握することができる
    // logger.log("Paid with bank account: \(accountNumber, privacy: .private(mask: .hash))")
}
最新情報をチェックしよう!