WWDC 20
Explore logging in Swift
Xcode 12 introduces new APIs for the unified logging system:
import os let logger = Logger(subsystem: "com.example.Fruta", category: "giftcards") logger.log("Started a task")
It's now much easier to pass arguments to logs using new Swift interpolation than the old os_log
API with C-style formatters:
logger.log("Started a task \(taskId)")
Like in os_log
, it does not generate the resulting formatted string at the moment of logging, but stores an optimized representation and defers formatting to a later moment
You can pass any values like numeric types, ObjC types that implement description
and any type conforming to CustomStringConvertible
Non-numeric data is private by default:
logger.log("Paid with bank account \(accountNr)") // -> Paid with bank account <private>
To actually display the values, use the privacy:
parameter:
logger.log("Ordered smoothie \(smoothieName, privacy: .public)")
You can also log private data in an obfuscated form, but hashed in such a way that each value gets its unique hash which you can track through a chain of logs:
logger.log("Paid with bank account: \(accountNumber, privacy: .private(mask: .hash))") // -> Paid with bank account <mask.hash:'CSvWylJ63...'>
Collecting recent logs from a connected device to a file:
log collect --device --start "2020-06-22 9:41" --output fruta.logarchive
Log levels:
debug
– useful only during debugginginfo
– helpful but not essential for troubleshootingnotice
(default) – essential for troubleshootingerror
– expected errorsfault
– unexpected errors, assumptions that weren’t true, potential bugs
The Logger has a separate method for each log level:
logger.debug(…) logger.error(…) // etc.
Take into account that not all logs are persisted after the app finishes execution, some can only be live streamed
debug
⭢ not persisted by default
info
⭢ saved to memory, only persisted if collected using log collect
before they disappear
notice
⭢ persisted up to a storage limit, older messages are purged after some time
error
, fault
⭢ persisted for a longer time than notice messages (typically a few days)
The level also affects logging performance: debug logs are the fastest, error and fault are the slowest
It’s safe to call even some slower code within log messages that will not be logged – the code isn’t run unless the log is enabled:
logger.debug("\(slowFunction(data))")
Logger provides a number of built-in formatters that let you customize how an interpolated value is displayed:
logger.log("\(cardId, align: .left(columns: 5))") logger.log("\(seconds, format: .fixed(precision: 2))") logger.log("\(data, format: .hex, align: .right(columns: 4))")
+ others like decimal, exponential, octal
Existing os_log(…)
function also accepts the new interpolating parameters (when running on latest OS versions)