I'm trying to log function arguments into os_log like this:
func foo(x: String, y: [String:String]) {
//...
os_log("foo: \(x) \(y.description)", log: OSLog.default, type: .debug)
}
But getting error:
Cannot convert value of type 'String' to expected argument type 'StaticString'
So how can I log function arguments, or any other dynamic data?
See Logging:
Formatting Log Messages
To format a log message, use a standard NSString or printf format string, ...
and String Format Specifiers for the standard format string specifiers, such as %# and %d.
In your case:
os_log("foo: %# %#", log: .default, type: .debug, x, y.description)
The format string is restricted to static strings to prevent
(unintentional) expansion of format string specifiers. Here is an example demonstrating the
problem, using NSLog() because that does not restrict the format
to constant strings:
let s = "50%"
NSLog("\(s)percent")
// Output: 500x0ercent
The %p expects a pointer on the variable argument list, which is
not provided. This is undefined behavior, it can lead to crashes
or unexpected output.
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.
This is my approach:
import Foundation
import os.log
struct Log {
enum LogLevel: String {
case error = "⛔️"
case warning = "⚠️"
case debug = "💬"
}
static func debug(_ info: String, level: LogLevel = .debug, file: String = #file, function: String = #function, line: Int = #line) {
os_log("%# %#:%d %#: %#", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, info)
}
static func warning(_ info: String, level: LogLevel = .warning, file: String = #file, function: String = #function, line: Int = #line) {
os_log("%# %#:%d %#: %#", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, info)
}
static func error(_ error: NSError, level: LogLevel = .error, file: String = #file, function: String = #function, line: Int = #line) {
os_log("%# %#:%d %#: %#", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, "\(error)")
}
}
Usage:
Log.debug("MyLog")
Output example:
💬 AppDelegate.swift:26 application(_:didFinishLaunchingWithOptions:): MyLog
I was getting annoyed at not being able to use "\(variable)" Swift string interpolation in os_log.
I wrote a small extension to get around the issue:
import os.log
extension OSLog {
static func log(_ message: String, log: OSLog = .default, type: OSLogType = .default) {
os_log("%#", log: log, type: type, message)
}
}
This does cause "private" logging, which is expected.
App Name <private>
In Console.app, how can I reveal to what <private> tags are actually referring?
In Apple's WWDC 2016 presentation "Unified Logging and Activity Tracing" they say:
Avoid wrapping os log APIs in other functions.
If you wrap it in another function you then lose our ability to collect the file and line number for you.
If you absolutely have to wrap our APIs, then wrap them in macros and not in functions.
So this is perhaps not the best solution if you are concerned about additional collected information. Although that information still may not be available even using stock os_log: How to find source file and line number from os_log()
A "macro" alternative that allows "\(variable)" substitution would be welcome if anyone want to write it.
The macOS 11 Big Sur Release Notes state that os_log can now be passed Swift string interpolation:
https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11-beta-release-notes
Logging
New Features
New APIs are available for using os_log from Swift as part of the os framework:
A new type Logger can be instantiated using a subsystem and category and provides methods for logging at different levels ( debug(_:) , error(_:) , fault(_:) ).
The Logger APIs support specifying most formatting and privacy options supported by legacy os_log APIs.
The new APIs provide significant performance improvements over the legacy APIs.
You can now pass Swift string interpolation to the os_log function.
Note: The new APIs can't be back deployed; however, the existing os_log API remains available for back deployment. (22539144)
I just joined a project that has a lot of existing code. The previous programmer was perhaps unfamiliar with Swift or began development in the early stages of the Swift language. They seemed to be using the if let statement in an odd way. They seemed to want to use the statement as a if is let. Before I edit the code I would like to know if there is any valid use for this:
// In JSON parser
if value is String, let string = value as? String {
document.createdBy = string
}
First checking if value is of type String seems redundant to me. Doesn't Swift check for this in the let string = value as? String portion of the statement?
QUESTION
Why would this need to be checked twice? Or would there be a reason for this?
You're correct, this is redundant. If value is not a string, then value as? String would return nil, and the conditional binding would fail.
To check the type, and not use the casted result:
if value is String {
// Do something that doesn't require `value` as a string
}
To check the type and use the result:
if let value = value as? String { // The new name can shadow the old name
document.createdBy = value
}
Doing both makes no sense.
In Swift 3, what is the recommended way to put (potentially lots of) additional information in an error/exception that the catcher can use to solve/handle the problem? In all the examples I've seen, they use enums with associated values, and that seems overly cumbersome/verbose for lots of information.
Specifically, I am writing a simple parser and want a place to store the affected line and column numbers (and potentially other information in the future), but without requiring that every handler explicitly declare those as associated values, as that would be a burden on the caller.
At this point I can basically see two ways of doing this, neither of which seems particularly elegant and both of which require defining two different things:
Define an outer enum error that represents the type of error, and for each case accept a parameter that is an object that contains the additional exception details, or
Use the object as the actual Error and pass in a case from an enum to its constructor to represent the actual error condition.
Both of these feel somewhat unclean to me though as they take two separate concepts to represent a simple idea, an error, and I'm just wondering if there's a nicer way to do this.
Are there any conventions or recommended ways to handle errors that need to contain potentially lots of additional information?
I don't know if there is a "recommended" way, perhaps someone else can
answer that or provide a better solution.
But one possible approach would be to use a struct (with properties) as the error type and use optional properties for values which need
not be provided. Example:
struct ParserError: Error {
enum Reason {
case invalidCharacter
case unexpectedEOF
}
let reason: Reason
let line: Int?
let column: Int?
init(reason: Reason, line: Int? = nil, column: Int? = nil) {
self.reason = reason
self.line = line
self.column = column
}
}
One might also want to adopt the LocalizedError protocol to
provide sensible error descriptions even if the
concrete error type is not known by the catcher (compare How to provide a localized description with an Error type in Swift?):
extension ParserError: LocalizedError {
public var errorDescription: String? {
var description: String
switch reason {
case .invalidCharacter:
description = "Invalid Character in input file"
case .unexpectedEOF:
description = "Unexpected end of file"
}
if let line = line {
description += ", line \(line)"
}
if let column = column {
description += ", column \(column)"
}
return description
}
}
Usage example:
func parse() throws {
// Throw error with line number, but without column:
throw ParserError(reason: .invalidCharacter, line: 13)
}
do {
try parse()
} catch let error {
print(error.localizedDescription)
}
Output:
Invalid Character in input file, line 13
let address = "http://www.example.com/?cevent=imt%2Fguide%252525252525252F"
let address2 = "http://www.example.com/?cevent=imt%2Fguide%2525252525252521"
let bodyString=NSMutableString()
bodyString.appendFormat("\(address)")
when I test in playground, the code errors, but when I use
bodyString.appendFormat("\(address2)")
it works! Why?
The runtime error.
You're calling appendFormat but you're not using any formatter string.
It should be used like this, with a formatter as the first argument (here the normal text formatter):
bodyString.appendFormat("%#", address)