OSLog / Logger

Apple公式の統一ロギングシステム。WWDC 2020で改良されたAPIが導入され、高性能でプライバシーを考慮した設計。システムログとの統合、カテゴリ別フィルタリング、メタデータの自動収集を提供。iOS、macOS、tvOS、watchOS全般でサポート。

iOSSwiftApple統合ログ構造化ログパフォーマンスプライバシー

ライブラリ

OSLog Logger

概要

OSLog Loggerは、Apple's Unified Logging Systemの中核を成すSwift用の現代的なログライブラリで、iOS 14以降で利用可能です。従来のNSLogやprintに代わるApple公式推奨の高性能ログソリューションとして設計。構造化ログ、プライバシー保護、効率的なデータ圧縮、遅延データ収集による優れたパフォーマンスを実現。サブシステムとカテゴリによる柔軟な分類、ログレベル制御、Console.appとの統合による包括的なログ管理体験を提供します。

詳細

OSLog Loggerは、AppleがiOS 10で導入したUnified Logging Systemの最新APIとして、iOS 14以降で推奨される統合ログソリューションです。従来のASL(Apple System Log)やSyslogを完全に置き換える設計で、バイナリ形式での効率的なデータ保存、ログデータ圧縮、遅延データ収集などの先進技術により劇的なパフォーマンス向上を実現。Swift 5.1のString Interpolationとの統合により、型安全で自然な構文でのログ記述が可能。プライバシー保護機能により機密データの自動マスキング、サブシステム・カテゴリによる論理的分類、ログレベル(debug, info, notice, error, fault)による重要度管理を標準サポート。

主な特徴

  • 高性能ログ処理: バイナリ圧縮と遅延収集による最適化されたパフォーマンス
  • プライバシー保護: 機密データの自動マスキングとプライバシー制御
  • 構造化ログ: サブシステムとカテゴリによる論理的な分類システム
  • ログレベル制御: debug/info/notice/error/faultによる重要度管理
  • Console.app統合: macOS Console.appでの高度なフィルタリングと検索
  • Swift String Interpolation: 型安全で自然な構文でのログ記述

メリット・デメリット

メリット

  • Apple公式推奨の現代的ログAPIで将来性と信頼性が保証
  • 従来のNSLogと比較して劇的なパフォーマンス向上を実現
  • プライバシー保護機能により個人情報の安全な取り扱いが可能
  • サブシステム・カテゴリによる大規模アプリケーションでの効率的ログ管理
  • Console.appとの統合による本格的なログ分析環境を提供
  • Swift String Interpolationによる型安全で読みやすいコード記述

デメリット

  • iOS 14以降の対応でレガシーバージョンでは利用不可
  • 初期設定でサブシステム・カテゴリ設計が必要で学習コストが存在
  • カスタムログフォーマットや外部ログサービス統合の柔軟性が限定的
  • プライバシー制御の複雑さにより意図しない情報マスキングが発生する可能性
  • デバッグビルドと本番ビルドでのログ出力動作の違いに注意が必要
  • Console.app以外のログ分析ツールとの連携に制約

参考ページ

書き方の例

基本セットアップ

import os

// Bundle IDをサブシステムとして使用するシンプルな設定
let logger = Logger()

// カスタムサブシステムとカテゴリの設定
let networkLogger = Logger(subsystem: "com.example.myapp", category: "networking")
let uiLogger = Logger(subsystem: "com.example.myapp", category: "ui")
let dataLogger = Logger(subsystem: "com.example.myapp", category: "data")

// アプリ全体で使用する統一ログシステム
struct AppLogger {
    static let network = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
    static let ui = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ui")
    static let data = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "data")
    static let general = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "general")
}

基本的なログ出力

import os

let logger = Logger(subsystem: "com.example.myapp", category: "general")

// 各ログレベルでの基本的な出力
logger.debug("デバッグ情報: アプリケーション開発中の詳細情報")
logger.info("情報: ユーザーがログインしました")
logger.notice("通知: 重要な状態変更が発生")
logger.error("エラー: ネットワーク接続に失敗しました")
logger.fault("致命的エラー: データベース接続が完全に失敗")

// 変数を含むString Interpolation
let userName = "田中太郎"
let userId = 12345
let loginTime = Date()

logger.info("ユーザーログイン成功: \(userName, privacy: .public) (ID: \(userId, privacy: .private)) at \(loginTime)")

// プライバシー制御の詳細指定
let email = "[email protected]"
let password = "secretpassword"

logger.debug("認証試行: email=\(email, privacy: .public), password=\(password, privacy: .private)")

// 数値と配列のログ出力
let responseTime = 1.234
let userList = ["Alice", "Bob", "Charlie"]

logger.info("API レスポンス時間: \(responseTime, format: .fixed(precision: 3))秒")
logger.debug("ユーザーリスト: \(userList, privacy: .public)")

高度な設定

import os

// カテゴリ別ログ管理システム
class LoggingManager {
    // 各機能領域別のLogger
    private let networkLogger = Logger(subsystem: "com.example.myapp", category: "network")
    private let dbLogger = Logger(subsystem: "com.example.myapp", category: "database")
    private let uiLogger = Logger(subsystem: "com.example.myapp", category: "ui")
    private let securityLogger = Logger(subsystem: "com.example.myapp", category: "security")
    
    static let shared = LoggingManager()
    
    private init() {}
    
    // ネットワーク関連のログ
    func logNetworkRequest(url: String, method: String, headers: [String: String]?) {
        networkLogger.info("リクエスト開始: \(method, privacy: .public) \(url, privacy: .public)")
        if let headers = headers {
            networkLogger.debug("ヘッダー情報: \(headers, privacy: .private)")
        }
    }
    
    func logNetworkResponse(statusCode: Int, responseTime: TimeInterval) {
        if statusCode >= 400 {
            networkLogger.error("リクエスト失敗: ステータスコード \(statusCode)")
        } else {
            networkLogger.info("リクエスト成功: ステータスコード \(statusCode), 応答時間 \(responseTime, format: .fixed(precision: 3))秒")
        }
    }
    
    // データベース関連のログ
    func logDatabaseOperation(operation: String, table: String, recordCount: Int? = nil) {
        if let count = recordCount {
            dbLogger.info("DB操作: \(operation, privacy: .public) on \(table, privacy: .public) - \(count)件")
        } else {
            dbLogger.info("DB操作: \(operation, privacy: .public) on \(table, privacy: .public)")
        }
    }
    
    func logDatabaseError(operation: String, error: Error) {
        dbLogger.error("DB操作エラー: \(operation, privacy: .public) - \(error.localizedDescription)")
    }
    
    // UI関連のログ
    func logScreenView(screenName: String, loadTime: TimeInterval? = nil) {
        if let loadTime = loadTime {
            uiLogger.info("画面表示: \(screenName, privacy: .public) - 読み込み時間 \(loadTime, format: .fixed(precision: 3))秒")
        } else {
            uiLogger.info("画面表示: \(screenName, privacy: .public)")
        }
    }
    
    func logUserAction(action: String, context: [String: Any]? = nil) {
        uiLogger.info("ユーザーアクション: \(action, privacy: .public)")
        if let context = context {
            uiLogger.debug("アクションコンテキスト: \(context, privacy: .private)")
        }
    }
    
    // セキュリティ関連のログ
    func logSecurityEvent(event: String, severity: SecuritySeverity, details: [String: Any]? = nil) {
        switch severity {
        case .low:
            securityLogger.notice("セキュリティイベント: \(event, privacy: .public)")
        case .medium:
            securityLogger.error("セキュリティ警告: \(event, privacy: .public)")
        case .high:
            securityLogger.fault("セキュリティ脅威: \(event, privacy: .public)")
        }
        
        if let details = details {
            securityLogger.debug("イベント詳細: \(details, privacy: .private)")
        }
    }
}

enum SecuritySeverity {
    case low, medium, high
}

// パフォーマンス監視用のロガー
class PerformanceLogger {
    private let logger = Logger(subsystem: "com.example.myapp", category: "performance")
    private var timers: [String: CFAbsoluteTime] = [:]
    
    func startTimer(_ name: String) {
        timers[name] = CFAbsoluteTimeGetCurrent()
        logger.debug("パフォーマンス測定開始: \(name, privacy: .public)")
    }
    
    func endTimer(_ name: String) {
        guard let startTime = timers.removeValue(forKey: name) else {
            logger.error("パフォーマンス測定エラー: タイマー '\(name, privacy: .public)' が見つかりません")
            return
        }
        
        let duration = CFAbsoluteTimeGetCurrent() - startTime
        
        if duration > 1.0 {
            logger.notice("パフォーマンス警告: \(name, privacy: .public) - \(duration, format: .fixed(precision: 3))秒 (閾値超過)")
        } else {
            logger.info("パフォーマンス測定完了: \(name, privacy: .public) - \(duration, format: .fixed(precision: 3))秒")
        }
    }
}

エラーハンドリング

import os

class ErrorLogger {
    private let logger = Logger(subsystem: "com.example.myapp", category: "error")
    
    func logError(_ error: Error, context: String? = nil, file: String = #file, line: Int = #line, function: String = #function) {
        let fileName = (file as NSString).lastPathComponent
        let location = "\(fileName):\(line) \(function)"
        
        if let context = context {
            logger.error("エラー発生 [\(location, privacy: .public)]: \(context, privacy: .public) - \(error.localizedDescription)")
        } else {
            logger.error("エラー発生 [\(location, privacy: .public)]: \(error.localizedDescription)")
        }
        
        // エラーの詳細情報をデバッグレベルで記録
        logger.debug("エラー詳細: \(String(describing: error), privacy: .private)")
    }
    
    func logNetworkError(_ error: Error, url: String, statusCode: Int? = nil) {
        if let statusCode = statusCode {
            logger.error("ネットワークエラー: \(url, privacy: .public) - ステータス \(statusCode) - \(error.localizedDescription)")
        } else {
            logger.error("ネットワークエラー: \(url, privacy: .public) - \(error.localizedDescription)")
        }
        
        // URLErrorの詳細解析
        if let urlError = error as? URLError {
            switch urlError.code {
            case .notConnectedToInternet:
                logger.fault("致命的: インターネット接続なし")
            case .timedOut:
                logger.error("ネットワークタイムアウト")
            case .cannotFindHost:
                logger.error("ホスト解決失敗: \(url, privacy: .public)")
            default:
                logger.error("URL Error Code: \(urlError.errorCode)")
            }
        }
    }
    
    func logValidationError(field: String, value: Any, rule: String) {
        logger.error("バリデーションエラー: フィールド '\(field, privacy: .public)' - ルール '\(rule, privacy: .public)'")
        logger.debug("バリデーション失敗値: \(String(describing: value), privacy: .private)")
    }
    
    func logDatabaseError(_ error: Error, operation: String, table: String? = nil) {
        if let table = table {
            logger.error("データベースエラー: \(operation, privacy: .public) on \(table, privacy: .public) - \(error.localizedDescription)")
        } else {
            logger.error("データベースエラー: \(operation, privacy: .public) - \(error.localizedDescription)")
        }
        
        // Core Dataエラーの特別処理
        if let coreDataError = error as NSError? {
            logger.debug("Core Data Error Details: Domain=\(coreDataError.domain), Code=\(coreDataError.code)")
            
            if let userInfo = coreDataError.userInfo as? [String: Any] {
                for (key, value) in userInfo {
                    logger.debug("UserInfo: \(key)=\(String(describing: value), privacy: .private)")
                }
            }
        }
    }
}

// 使用例
let errorLogger = ErrorLogger()

// 一般的なエラーログ
do {
    try someRiskyOperation()
} catch {
    errorLogger.logError(error, context: "ユーザーデータ処理中")
}

// ネットワークエラー専用ログ
URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        let statusCode = (response as? HTTPURLResponse)?.statusCode
        errorLogger.logNetworkError(error, url: url.absoluteString, statusCode: statusCode)
    }
}.resume()

実用例

import os

// アプリケーション全体のログ管理
class AppLifecycleLogger {
    private let logger = Logger(subsystem: "com.example.myapp", category: "lifecycle")
    
    func logAppStart() {
        logger.notice("アプリケーション開始")
        logger.info("Bundle Version: \(Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown", privacy: .public)")
        logger.info("Device Model: \(UIDevice.current.model, privacy: .public)")
        logger.info("iOS Version: \(UIDevice.current.systemVersion, privacy: .public)")
    }
    
    func logAppState(state: UIApplication.State) {
        switch state {
        case .active:
            logger.info("アプリケーションがアクティブ状態に移行")
        case .background:
            logger.info("アプリケーションがバックグラウンド状態に移行")
        case .inactive:
            logger.info("アプリケーションが非アクティブ状態に移行")
        @unknown default:
            logger.notice("アプリケーションが未知の状態に移行")
        }
    }
    
    func logMemoryWarning(availableMemory: Int64) {
        logger.error("メモリ警告: 利用可能メモリ \(availableMemory / 1024 / 1024) MB")
    }
}

// API通信のログ管理
class APILogger {
    private let logger = Logger(subsystem: "com.example.myapp", category: "api")
    
    func logRequest<T: Codable>(endpoint: String, method: String, parameters: T?) {
        logger.info("API Request: \(method, privacy: .public) \(endpoint, privacy: .public)")
        
        if let parameters = parameters {
            do {
                let jsonData = try JSONEncoder().encode(parameters)
                let jsonString = String(data: jsonData, encoding: .utf8) ?? "Invalid JSON"
                logger.debug("Request Parameters: \(jsonString, privacy: .private)")
            } catch {
                logger.error("Parameter encoding error: \(error.localizedDescription)")
            }
        }
    }
    
    func logResponse<T: Codable>(endpoint: String, statusCode: Int, responseTime: TimeInterval, data: T?) {
        if statusCode >= 200 && statusCode < 300 {
            logger.info("API Success: \(endpoint, privacy: .public) - \(statusCode) in \(responseTime, format: .fixed(precision: 3))s")
        } else {
            logger.error("API Error: \(endpoint, privacy: .public) - Status \(statusCode)")
        }
        
        if let data = data {
            do {
                let jsonData = try JSONEncoder().encode(data)
                let jsonString = String(data: jsonData, encoding: .utf8) ?? "Invalid JSON"
                logger.debug("Response Data: \(jsonString, privacy: .private)")
            } catch {
                logger.error("Response encoding error: \(error.localizedDescription)")
            }
        }
    }
}

// ユーザー行動分析ログ
class UserAnalyticsLogger {
    private let logger = Logger(subsystem: "com.example.myapp", category: "analytics")
    
    func logScreenView(screenName: String, previousScreen: String? = nil, loadTime: TimeInterval) {
        if let previous = previousScreen {
            logger.info("Screen Navigation: \(previous, privacy: .public) -> \(screenName, privacy: .public) (\(loadTime, format: .fixed(precision: 3))s)")
        } else {
            logger.info("Screen View: \(screenName, privacy: .public) (\(loadTime, format: .fixed(precision: 3))s)")
        }
    }
    
    func logUserAction(action: String, screen: String, properties: [String: Any]? = nil) {
        logger.info("User Action: \(action, privacy: .public) on \(screen, privacy: .public)")
        
        if let properties = properties {
            for (key, value) in properties {
                logger.debug("Action Property: \(key, privacy: .public)=\(String(describing: value), privacy: .private)")
            }
        }
    }
    
    func logConversionEvent(event: String, value: Double?, currency: String?) {
        if let value = value, let currency = currency {
            logger.notice("Conversion: \(event, privacy: .public) - \(value, format: .fixed(precision: 2)) \(currency, privacy: .public)")
        } else {
            logger.notice("Conversion: \(event, privacy: .public)")
        }
    }
}

// 使用例
let appLogger = AppLifecycleLogger()
let apiLogger = APILogger()
let analyticsLogger = UserAnalyticsLogger()

// アプリケーション開始時
appLogger.logAppStart()

// API呼び出し
struct UserRequest: Codable {
    let username: String
    let email: String
}

let userRequest = UserRequest(username: "johndoe", email: "[email protected]")
apiLogger.logRequest(endpoint: "/api/users", method: "POST", parameters: userRequest)

// ユーザー行動記録
analyticsLogger.logScreenView(screenName: "Profile", previousScreen: "Home", loadTime: 0.5)
analyticsLogger.logUserAction(action: "Button Tap", screen: "Profile", properties: ["button_id": "save_profile"])
analyticsLogger.logConversionEvent(event: "Purchase", value: 29.99, currency: "USD")