print() (Built-in)
Swift標準の出力関数。最もシンプルなデバッグ手段として、開発段階での迅速なログ出力に適している。設定不要で即座に利用可能だが、本番ビルドでは自動的に無効化されず、性能とセキュリティ面で課題がある。
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と組み合わせることで、強力なデバッグとトラブルシューティングが可能になります。