In Xcode 12 / Swift 5.3 / iOS 14, you don't have to call os_log
directly at all. Instead, replace your use of the OSLog class with the new Logger class (available when you import os
). Here's an example:
let myLog = Logger(subsystem: "testing", category: "exploring")
You can then call a method directly on your Logger object to log with that subsystem and category:
myLog.log("logging at \(#function)")
To log at a level other than the default, use that level as the name of the method:
myLog.debug("logging at \(#function)")
In the message string, as you can see, Swift string interpolation is legal. It is allowed for Int, Double, Objective-C objects with a description
, and Swift objects that conform to CustomStringConvertible.
The legality of Swift string interpolation here is surprising, because the point of os_log
format specifiers is to postpone evaluation of the arguments, pushing it out of your app (so that your app is not slowed down by logging) and into the logging mechanism itself. Well, surprise! Thanks to the custom Swift string interpolation hooks that were introduced in Swift 5, the interpolation is postponed.
And the use of custom string interpolation has two further benefits here. First, the custom string interpolation mechanism allows an interpolation to be accompanied by additional parameters specifying its behavior. That's how you prevent a value from being redacted:
myLog.log("logging at \(#function, privacy: .public)")
You can also use additional parameters to perform various sorts of string formatting that you would otherwise have had to perform using NSLog
format specifiers, such as dictating the number of digits after the decimal point and other sorts of padding and alignment:
myLog.log("the number is \(i, format: .decimal(minDigits: 5))") // e.g. 00001
So you'll never need to call os_log
directly again, and you won't have to use the NSLog
-type format specifiers any more.
OLD ANSWER FOR iOS 13 AND BEFORE:
Two points expanding on Martin R's answer:
os_log("foo: %@ %@", log: .default, type: .debug, x, y.description)
You can omit the type:
parameter, but you cannot omit the log:
parameter; you must have it, including the log:
label, or os_log
will misinterpret your intentions.
Also, the log:
value does not have to be .default
. It is usual to create one or more OSLog objects up front, to use as the argument to the log:
parameter. The advantage here is that you get to specify the Subsystem and Category for the OSLog object, and these in turn permit you to filter on the results, in the Xcode console or the Console application.
Also, with regard to pkamb's answer, if we know our message is always going to be a string, we can write the OSLog extension like this (taking advantage of the new Swift 5.2 callAsFunction
method):
extension OSLog {
func callAsFunction(_ s: String) {
os_log("%{public}s", log: self, s)
}
}
The result is that we can now treat our OSLog object myLog
itself as a function:
myLog("The main view's bounds are \(self.view.bounds)")
That's nice because it's as simple as a basic print
statement. I appreciate that the WWDC 2016 warns against that sort of preformatting, but if it's what you were already doing in a print
statement I can't imagine it's all that harmful.