The Logger type from the os framework writes into the unified logging system. Use it when a bug only shows up after days of real use: the log tells you what the user did, without a debugger attached.
Write Logs With One Logger Per Feature
Declare one Logger as a private static constant per feature. Use the bundle identifier as subsystem and the type name as category, so you can filter logs later.
import os
@MainActor final class ProductsViewModel: ObservableObject {
private static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: ProductsViewModel.self)
)
func fetch() async {
do {
Self.logger.trace("Start product list fetching")
products = try await service.fetch()
Self.logger.notice("Product list fetching is finished")
} catch {
Self.logger.warning("\(error.localizedDescription, privacy: .public)")
}
}
}
Pick the Level by Persistence
The levels differ in what the system keeps. trace acts as a debug print and is never stored. notice, warning, and critical persist up to a storage limit, so use them for anything you must read back from a user's device.
Log with critical right before a deliberate crash, so the reason survives the fatalError.
} catch {
logger.critical("Can't fetch iCloud account status.")
fatalError()
}
Read Logs in Console
Logs appear in the Xcode debug console while running from Xcode. For a device in the field, connect it by cable and open the Console app, then filter by your subsystem and category.
Interpolated Values Are Private by Default
The Console shows only the static parts of a message. Every interpolated value renders as <private> unless you mark it. Pass privacy: .public to reveal it, or mask it while keeping it comparable.
logger.trace("Scene phase: \(newPhase, privacy: .public)")
logger.trace("Counter: \(counter, privacy: .private(mask: .hash))")
logger.trace("Counter: \(counter, align: .right(columns: 10))")
logger.trace("Counter: \(counter, format: .hex, align: .right(columns: 10))")
The interpolation also takes format and align options, so you can format values without building strings first.