print() (Built-in)

Swift標準の出力関数。最もシンプルなデバッグ手段として、開発段階での迅速なログ出力に適している。設定不要で即座に利用可能だが、本番ビルドでは自動的に無効化されず、性能とセキュリティ面で課題がある。

print

Swiftにおけるログ出力は、シンプルなprint関数から、Appleの統合ログシステム(Unified Logging System)まで、用途に応じて選択できます。本番環境では、パフォーマンスとプライバシーを考慮したos_logの使用が推奨されています。

主な特徴

  • print関数 - 開発時の簡易デバッグ用
  • os_log / Logger - 本番環境向けの統合ログシステム
  • 構造化ログ - カテゴリとサブシステムによる整理
  • プライバシー制御 - センシティブ情報の自動保護
  • Console.appとの統合 - 強力なログ分析機能

基本的な使い方

print関数(開発用)

// 基本的な出力
print("Hello, World!")

// 複数の値を出力
let name = "Swift"
let version = 5.9
print("言語:", name, "バージョン:", version)

// セパレータとターミネータのカスタマイズ
print("A", "B", "C", separator: "-", terminator: ".\n")
// 出力: A-B-C.

// デバッグ情報の出力
let numbers = [1, 2, 3, 4, 5]
debugPrint(numbers)  // より詳細な情報を含む

dump関数(詳細デバッグ)

struct User {
    let id: Int
    let name: String
    let email: String
}

let user = User(id: 1, name: "田中", email: "[email protected]")

// オブジェクトの構造を詳細に出力
dump(user)
// ▿ User
//   - id: 1
//   - name: "田中"
//   - email: "[email protected]"

os_log(統合ログシステム)

基本設定

import os

// iOS 14以降の新しいAPI
extension Logger {
    // バンドル識別子をサブシステムとして使用
    private static var subsystem = Bundle.main.bundleIdentifier!
    
    // カテゴリ別のロガー作成
    static let viewCycle = Logger(subsystem: subsystem, category: "viewcycle")
    static let network = Logger(subsystem: subsystem, category: "network")
    static let database = Logger(subsystem: subsystem, category: "database")
    static let auth = Logger(subsystem: subsystem, category: "authentication")
}

ログレベル

// 利用可能なログレベル(重要度順)
Logger.viewCycle.critical("致命的エラー: アプリケーションがクラッシュする可能性")
Logger.viewCycle.error("エラー: 操作が失敗しました")
Logger.viewCycle.warning("警告: パフォーマンスの問題")
Logger.viewCycle.notice("通知: 重要なイベント")
Logger.viewCycle.info("情報: 一般的な情報")
Logger.viewCycle.debug("デバッグ: 開発時の詳細情報")
Logger.viewCycle.trace("トレース: 最も詳細な情報")

実用的な例

import SwiftUI
import os

struct ContentView: View {
    private static let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier!,
        category: String(describing: ContentView.self)
    )
    
    @State private var userCount = 0
    
    var body: some View {
        VStack {
            Text("ユーザー数: \(userCount)")
            
            Button("ユーザー追加") {
                Self.logger.info("ユーザー追加ボタンがタップされました")
                userCount += 1
                Self.logger.debug("現在のユーザー数: \(userCount)")
            }
        }
        .onAppear {
            Self.logger.trace("ContentViewが表示されました")
        }
    }
}

プライバシー制御

プライバシーレベルの設定

let userId = "12345"
let email = "[email protected]"
let creditCard = "4111-1111-1111-1111"

// パブリック(ログに記録される)
Logger.auth.info("ユーザーID: \(userId, privacy: .public)")

// プライベート(デバッガ外では隠される - デフォルト)
Logger.auth.info("メールアドレス: \(email, privacy: .private)")

// ハッシュマスク(ハッシュ値として記録)
Logger.auth.info("カード番号: \(creditCard, privacy: .private(mask: .hash))")

// 自動編集(センシティブな情報を自動的に隠す)
Logger.auth.info("パスワード: \(password, privacy: .auto)")

フォーマット指定

let counter = 42
let price = 1234.56

// 整列とフォーマット
Logger.database.debug("カウンター: \(counter, align: .right(columns: 10))")
Logger.database.debug("価格: \(price, format: .fixed(precision: 2))")
Logger.database.debug("16進数: \(counter, format: .hex)")

高度な使い方

カスタムロギングクラス

import os

class AppLogger {
    private let logger: Logger
    
    init(category: String) {
        self.logger = Logger(
            subsystem: Bundle.main.bundleIdentifier!,
            category: category
        )
    }
    
    func logPerformance<T>(
        operation: String,
        block: () throws -> T
    ) rethrows -> T {
        let signpostID = OSSignpostID(log: logger)
        
        os_signpost(.begin, log: logger, name: "Performance", 
                   signpostID: signpostID, "%{public}s", operation)
        
        defer {
            os_signpost(.end, log: logger, name: "Performance", 
                       signpostID: signpostID)
        }
        
        do {
            let result = try block()
            logger.info("\(operation) completed successfully")
            return result
        } catch {
            logger.error("\(operation) failed: \(error.localizedDescription)")
            throw error
        }
    }
}

// 使用例
let dbLogger = AppLogger(category: "database")

let result = try dbLogger.logPerformance(operation: "データベースクエリ") {
    // 重い処理
    return fetchDataFromDatabase()
}

OSLogStoreを使用したログエクスポート

import OSLog

class LogExporter {
    func exportLogs(hours: Int = 24) async throws -> [String] {
        let store = try OSLogStore(scope: .currentProcessIdentifier)
        let position = store.position(timeIntervalSinceLatestBoot: -Double(hours * 3600))
        
        let predicate = NSPredicate(
            format: "subsystem == %@",
            Bundle.main.bundleIdentifier!
        )
        
        let entries = try store.getEntries(
            at: position,
            matching: predicate
        )
        
        var logs: [String] = []
        
        for entry in entries {
            if let logEntry = entry as? OSLogEntryLog {
                let timestamp = logEntry.date.formatted()
                let category = logEntry.category
                let message = logEntry.composedMessage
                
                logs.append("[\(timestamp)] [\(category)] \(message)")
            }
        }
        
        return logs
    }
    
    func shareLogsFile() async throws -> URL {
        let logs = try await exportLogs()
        let content = logs.joined(separator: "\n")
        
        let tempURL = FileManager.default.temporaryDirectory
            .appendingPathComponent("app_logs_\(Date().timeIntervalSince1970).txt")
        
        try content.write(to: tempURL, atomically: true, encoding: .utf8)
        
        return tempURL
    }
}

Console.appでの分析

フィルタリング

// Console.appで効果的にフィルタリングするための構造化
class NetworkLogger {
    private static let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier!,
        category: "network"
    )
    
    static func logRequest(_ request: URLRequest) {
        logger.info("""
            [REQUEST] \(request.httpMethod ?? "GET") \(request.url?.absoluteString ?? "")
            Headers: \(request.allHTTPHeaderFields ?? [:], privacy: .private)
            """)
    }
    
    static func logResponse(_ response: HTTPURLResponse, data: Data?) {
        logger.info("""
            [RESPONSE] Status: \(response.statusCode)
            Size: \(data?.count ?? 0) bytes
            """)
    }
}

アクティビティトレーシング

import os.activity

class ActivityLogger {
    private static let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier!,
        category: "activity"
    )
    
    static func trackUserFlow() {
        let activity = OSActivity(subsystem: Bundle.main.bundleIdentifier!,
                                 category: "userFlow")
        
        os_activity_scope(activity) {
            logger.info("ユーザーフロー開始")
            
            // 複数の操作をグループ化
            performLogin()
            loadDashboard()
            
            logger.info("ユーザーフロー完了")
        }
    }
}

パフォーマンスの考慮事項

条件付きログ

// 非効率な例
logger.debug("計算結果: \(expensiveCalculation())")

// 効率的な例
if logger.logLevel <= .debug {
    logger.debug("計算結果: \(expensiveCalculation())")
}

signpostによるパフォーマンス測定

import os.signpost

let performanceLogger = OSLog(
    subsystem: Bundle.main.bundleIdentifier!,
    category: .pointsOfInterest
)

func measurePerformance() {
    let signpostID = OSSignpostID(log: performanceLogger)
    
    os_signpost(.begin, log: performanceLogger, name: "DataProcessing", 
               signpostID: signpostID)
    
    // 測定したい処理
    processLargeDataSet()
    
    os_signpost(.end, log: performanceLogger, name: "DataProcessing", 
               signpostID: signpostID)
}

ベストプラクティス

1. 環境に応じた使い分け

#if DEBUG
// 開発環境ではprintを使用
func log(_ message: String) {
    print("[DEBUG] \(message)")
}
#else
// 本番環境ではos_logを使用
func log(_ message: String) {
    Logger.app.info("\(message)")
}
#endif

2. カテゴリの適切な設計

extension Logger {
    // 機能別にカテゴリを分ける
    static let ui = Logger(subsystem: subsystem, category: "ui")
    static let data = Logger(subsystem: subsystem, category: "data")
    static let sync = Logger(subsystem: subsystem, category: "sync")
    static let payment = Logger(subsystem: subsystem, category: "payment")
    
    // 重要度別のカテゴリも有効
    static let critical = Logger(subsystem: subsystem, category: "critical")
    static let performance = Logger(subsystem: subsystem, category: "performance")
}

3. エラーハンドリングとログ

enum DataError: LocalizedError {
    case networkError(String)
    case parsingError(String)
    
    var errorDescription: String? {
        switch self {
        case .networkError(let message):
            return "ネットワークエラー: \(message)"
        case .parsingError(let message):
            return "パースエラー: \(message)"
        }
    }
}

func fetchData() async throws {
    do {
        let data = try await networkRequest()
        Logger.network.info("データ取得成功: \(data.count) bytes")
    } catch {
        Logger.network.error("データ取得失敗: \(error.localizedDescription)")
        throw error
    }
}

まとめ

Swiftのログシステムは、開発時の簡易的なprintから本番環境向けのos_logまで、段階的に利用できます。特にos_log(統合ログシステム)は、パフォーマンス、プライバシー、構造化、分析機能において優れており、プロダクション環境での使用に最適です。Console.appと組み合わせることで、強力なデバッグとトラブルシューティングが可能になります。