What's the difference between print, NSLog and println and when should I use each?
For example, in Python if I wanted to print a dictionary, I'd just print myDict, but now I have 2 other options. How and when should I use each?
A few differences:
print vs println:
The print function prints messages in the Xcode console when debugging apps.
The println is a variation of this that was removed in Swift 2 and is not used any more. If you see old code that is using println, you can now safely replace it with print.
Back in Swift 1.x, print did not add newline characters at the end of the printed string, whereas println did. But nowadays, print always adds the newline character at the end of the string, and if you don't want it to do that, supply a terminator parameter of "".
NSLog:
NSLog adds a timestamp and identifier to the output, whereas print will not;
NSLog statements appear in both the device’s console and debugger’s console whereas print only appears in the debugger console.
NSLog in iOS 10-13/macOS 10.12-10.x uses printf-style format strings, e.g.
NSLog("%0.4f", CGFloat.pi)
that will produce:
2017-06-09 11:57:55.642328-0700 MyApp[28937:1751492] 3.1416
NSLog from iOS 14/macOS 11 can use string interpolation. (Then, again, in iOS 14 and macOS 11, we would generally favor Logger over NSLog. See next point.)
Nowadays, while NSLog still works, we would generally use “unified logging” (see below) rather than NSLog.
Effective iOS 14/macOS 11, we have Logger interface to the “unified logging” system. For an introduction to Logger, see WWDC 2020 Explore logging in Swift.
To use Logger, you must import os:
import os
Like NSLog, unified logging will output messages to both the Xcode debugging console and the device console, too
Create a Logger and log a message to it:
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "network")
logger.log("url = \(url)")
When you observe the app via the external Console app, you can filter on the basis of the subsystem and category. It is very useful to differentiate your debugging messages from (a) those generated by other subsystems on behalf of your app, or (b) messages from other categories or types.
You can specify different types of logging messages, either .info, .debug, .error, .fault, .critical, .notice, .trace, etc.:
logger.error("web service did not respond \(error.localizedDescription)")
So, if using the external Console app, you can choose to only see messages of certain categories (e.g. only show debugging messages if you choose “Include Debug Messages” on the Console “Action” menu). These settings also dictate many subtle issues details about whether things are logged to disk or not. See WWDC video for more details.
By default, non-numeric data is redacted in the logs. In the example where you logged the URL, if the app were invoked from the device itself and you were watching from your macOS Console app, you would see the following in the macOS Console:
url = <private>
If you are confident that this message will not include user confidential data and you wanted to see the strings in your macOS console, you would have to do:
logger.log("url = \(url, privacy: .public)")
Prior to iOS 14/macOS 11, iOS 10/macOS 10.12 introduced os_log for “unified logging”. For an introduction to unified logging in general, see WWDC 2016 video Unified Logging and Activity Tracing.
Import os.log:
import os.log
You should define the subsystem and category:
let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "network")
When using os_log, you would use a printf-style pattern rather than string interpolation:
os_log("url = %#", log: log, url.absoluteString)
You can specify different types of logging messages, either .info, .debug, .error, .fault (or .default):
os_log("web service did not respond", type: .error)
You cannot use string interpolation when using os_log. For example with print and Logger you do:
logger.log("url = \(url)")
But with os_log, you would have to do:
os_log("url = %#", url.absoluteString)
The os_log enforces the same data privacy, but you specify the public visibility in the printf formatter (e.g. %{public}# rather than %#). E.g., if you wanted to see it from an external device, you'd have to do:
os_log("url = %{public}#", url.absoluteString)
You can also use the “Points of Interest” log if you want to watch ranges of activities from Instruments:
let pointsOfInterest = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
And start a range with:
os_signpost(.begin, log: pointsOfInterest, name: "Network request")
And end it with:
os_signpost(.end, log: pointsOfInterest, name: "Network request")
For more information, see https://stackoverflow.com/a/39416673/1271826.
Bottom line, print is sufficient for simple logging with Xcode, but unified logging (whether Logger or os_log) achieves the same thing but offers far greater capabilities.
The power of unified logging comes into stark relief when debugging iOS apps that have to be tested outside of Xcode. For example, when testing background iOS app processes like background fetch, being connected to the Xcode debugger changes the app lifecycle. So, you frequently will want to test on a physical device, running the app from the device itself, not starting the app from Xcode’s debugger. Unified logging lets you still watch your iOS device log statements from the macOS Console app.
If you're using Swift 2, now you can only use print() to write something to the output.
Apple has combined both println() and print() functions into
one.
Updated to iOS 9
By default, the function terminates the line it prints by adding a line break.
print("Hello Swift")
Terminator
To print a value without a line break after it, pass an empty string as the terminator
print("Hello Swift", terminator: "")
Separator
You now can use separator to concatenate multiple items
print("Hello", "Swift", 2, separator:" ")
Both
Or you could combine using in this way
print("Hello", "Swift", 2, separator:" ", terminator:".")
Moreover, Swift 2 has debugPrint() (and CustomDebugStringConvertible protocol)!
Don't forget about debugPrint() which works like print() but most suitable for debugging.
Examples:
Strings
print("Hello World!") becomes Hello World
debugPrint("Hello World!") becomes "Hello World" (Quotes!)
Ranges
print(1..<6) becomes 1..<6
debugPrint(1..<6) becomes Range(1..<6)
Any class can customize their debug string representation via CustomDebugStringConvertible protocol.
To add to Rob's answer, since iOS 10.0, Apple has introduced an entirely new "Unified Logging" system that supersedes existing logging systems (including ASL and Syslog, NSLog), and also surpasses existing logging approaches in performance, thanks to its new techniques including log data compression and deferred data collection.
From Apple:
The unified logging system provides a single, efficient, performant API for capturing messaging across all levels of the system. This unified system centralizes the storage of log data in memory and in a data store on disk.
Apple highly recommends using os_log going forward to log all kinds of messages, including info, debug, error messages because of its much improved performance compared to previous logging systems, and its centralized data collection allowing convenient log and activity inspection for developers. In fact, the new system is likely so low-footprint that it won't cause the "observer effect" where your bug disappears if you insert a logging command, interfering the timing of the bug to happen.
You can learn more about this in details here.
To sum it up: use print() for your personal debugging for convenience (but the message won't be logged when deployed on user devices). Then, use Unified Logging (os_log) as much as possible for everything else.
iOS logger
NSLog - add meta info (like timestamp and identifier) and allows you to output 1023 symbols. Also print message into Console. The slowest method. Is not safe because other applications has an access to log file
#import Foundation
NSLog("SomeString")
print - prints all string to Xcode. Has better performance than previous
#import Foundation
print("SomeString")
println (only available Swift v1) and add \n at the end of string
os_log (from iOS v10) - prints 32768 symbols also prints to console. Has better performance than previous
#import os.log
os_log("SomeIntro: %#", log: .default, type: .info, "someString")
Logger (from iOS v14) - prints 32768 symbols also prints to console. Has better performance than previous
#import os
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "someCategory")
logger.log("\(s)")
There's another method called dump() which can also be used for logging:
func dump<T>(T, name: String?, indent: Int, maxDepth: Int, maxItems: Int)
Dumps an object’s contents using its mirror to standard output.
From Swift Standard Library Functions
Related
I am building a Swift 5 application with XCode 10.3. For this, I have a framework which contains a implementation for a logsystem (for debugging purposes). The default implementation for this logsystem is based on OSLog/os_log. When using the system in the consuming app, then none of the logs appear in the Console app. However, when placing breakpoints, I can see that the os_log statement (see code example below) is reached and that the correct parameters are passed on to the os_log function. However, when I use os_log or NSLog in the host application, then they do show up.
I have verified that it is not an issue with the LogSystem / DefaultLogImplementation types as all of the breakpoints that need to be hit, are hit, and all unit tests are green. In addition, the os_log statement is reached and executed, but the logs do not appear. I have verified that all messages are shown in the console app, I have verified that I have selected the correct device, I have tried multiple filters (and even dug through all the logs without filters enabled)...
Can anyone help / give a pointer at what the issue may be? I am currently suspecting that there is a bug in the implementation of OSLog/os_log.
Code sample
App-side code, which consumes code similar to the examples provided below
class SomeClass {
private let logSystem = LogSystem()
func doSomethingImportant() {
// Do important stuff and log the result
logSystem.debug("Finished process with result \(result)")
}
}
Framework-side code, which is consumed by the app
public class LogSystem {
private let logImplementation: LogImplementation
init(_ logImplementation: LogImplementation = DefaultLogImplementation()) {
self.logImplementation = logImplementation
}
public func debug(_ message: String) {
logImplementation.log(message, type: .debug) // And some other parameters...
}
public func error(_ message: String) {
// Similar to debug(:)...
}
}
public protocol LogImplementation {
func log(_ message: String, type: LogType, ...other parameters...)
}
public class DefaultLogImplementation: LogImplementation {
func log(_ message: String, type: LogType, ...other parameters...) {
let parsedType = parseLogType(type) // Function that parses LogType to OSLogType
let log = OSLog(subsystem: "my.subsystem.domain", category: "myCategory")
os_log("%{private}#", log: log, type: parsedType, message) // Breakpoint here is reached, but log does not appear in console app (even with debugger attached, which should remove the effect of "%{private}%". Either way, at the very least censored logs should still appear in console app.
}
}
Additional info
Swift version: 5.0
XCode version: 10.3
Target device: iOS 12.2 iPhone X simulator
NSLog appears in console app: Yes
Selected correct device in console app: Yes
Correct filters: Yes
Update 2019-08-16 13:00 (Amsterdam time)
It appears that only .debug level messages are not appearing in the Console app. This bug occurs when using any simulator device in combination with OSLog. I have tried several commands to fix this:
sudo log config --mode level:debug,persist:debug
sudo log config --subsystem my.reverse.domain.name --mode level:debug,persist:debug
Neither of them fixed the issue. In fact, not a single debug-level message of the simulator is showing up in the console app (not even from iOS itself). Yes, the option to show .info and .debug level messages is enabled.
I tried setting the log-level for the simulator specifically through the following command:
xcrun simctl spawn booted log config --mode level:debug
But this result in an error:
log: Simulator unable to set system mode
In the console app, there are two options to include / hide debug and info messages:
I've spoken with an Apple DTS Engineer and it's a know issues not being able to see debug messages using a simulator (in Xcode 11 too): open a feedback
Using Unified Logging, I'm getting multiple copies of each log entry (23 to be exact). I'm wondering what I might be doing wrong.
I'm using Swift 4.1, Xcode 9.4.1, Console Version 1.0 (2.0.52), and building an iOS 11.0 app.
The relevant code looks like:
import os.log
...
// property
let log = OSLog(subsystem: Constants.subsystem, category: Constants.category)
override func viewDidLoad() {
super.viewDidLoad()
os_log("Useless message...", log: log, type: .debug)
}
And, in the Console (only showing the Message column), I get:
Subsystem, Category, Time, and Process values are identical for every entry.
And, the output only shows once in Xcode's console window.
What silly thing have I done now? (Or, is this a 🤪 bug?)
Update: I set a breakpoint at the log statement to test. It's only hit once, but as soon as it executes, the 23 entries show in the Console.
Update 2: added UUID to log for test: same result: UUID was the same in all 23 entries.
os_log("Useless message... %#", log: log, type: .debug, String(describing: UUID()))
Results:
Update 3: Fixed...
Cleaned the project, deleted derived data, erased the content in the simulator - all of which I'd done multiple times before.
This time, though, I also shut down, then restarted Xcode - and now it's working fine! Weirdness!
When I debug on Xcode it takes around 30 seconds or more to print results of po on Xcode console.
Unfortunately, this is only few information I have on the issue.
However, there is another point to consider. This issue is very specific to a project. This is because when I use po for other projects on same Macbook, it works immediately. Also, this particular project is slow on all other Macbook, and for all team.
I googled it but no relevant answer found. I find it easy to use print(...) rather than using debugging on Xcode console. However, it's more work and requires lots of rebuilds.
I have several explanations:
There is a lot of code (Xcode slows down after a certain amount of code) Also make sure your print statement is towards the top of your page. Xocde goes from top to bottom.
Your Mac is slow. Some Macs after a certain amount of usage slow down. Also if you have a Mac mini or air they are slower than others.
Xcode Beta. If you are using Xcode beta then there might just be a bug.
If none of those answer you provide me with more info and I provide other solutions.
Swift:
Try this solution which helps to reduce log time in debug mode.
Step 1: Create a new file called Utils.swift (File name based on your preference)
Step 2: Add following code in file
import Foundation
import UIKit
struct Utils { }
public func PrintLogs(_ message: Any, file: String = #file, function: String = #function, line: Int = #line) {
#if DEBUG
let className = file.components(separatedBy: "/").last ?? ""
let classNameArr = className.components(separatedBy: ".")
NSLog("\n\n--> Class Name: \(classNameArr[0]) \n--> Function Name: \(function) \n--> Line: \(line)")
print("--> Log Message: \(message)")
#endif
}
Usage: Call PrintLogs("Hello") instead of print("Hello")
Sample Output:
--> Class Name: HomeViewController
--> Function Name: logTest()
--> Line: 81
--> Log Message: Hello
I have a problem I would like to use the function Process() but when I call it in a function I get
Use of unresolved identifier 'Process'
But when I call it directly in my code it works.
Someone knows why?
As you neither mentioned which Platform you are targeting to nor provided any kind of code, you'll need to take note of the following things:
Process() was renamed to CommandLine() in Swift 3.0
If you are targeting iOS, CommandLine() isn't available, so you'll need to do the following:
Using NSTask() on iOS:
The best way to achieve this, or at least the one I prefer the most (also I didn't find any other way to do this), is to use a custom Objective-C header file which creates the object NSTask() and everything it needs.
Then, in order to use this code with Swift, you'll need to create a Bridging-Header, in which you'll need to import the NSTask.h for it to be exposed to Swift and being able to use it in your Swift code.
Once done this, just use the following function in your code whenever you want to run a task:
func task(launchPath: String, arguments: String...) -> NSString {
let task = NSTask.init()
task?.setLaunchPath(launchPath)
task?.arguments = arguments
// Create a Pipe and make the task
// put all the output there
let pipe = Pipe()
task?.standardOutput = pipe
// Launch the task
task?.launch()
task?.waitUntilExit()
// Get the data
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
return output!
}
And call it like this:
task(launchPath: "/usr/bin/echo", arguments: "Hello World")
This will also return the value, so you can even display it by doing:
print(task(launchPath: "/usr/bin/echo", arguments: "Hello, World!"))
Which will print:
~> Hello, World!
For this to work and not throwing an NSInternalInconsistencyException, you'll need to set the launchPath to the executable's full path instead to just the directory containing it.
You'll also need to set all the command arguments separated by commas.
Tested on both iPad Mini 2 (iOS 12.1 ~> Jailbroken) and iPhone Xr (iOS 12.2 ~> not jailbroken).
NOTE: Even though this works both on non-jailbroken and jailbroken devices, your App will be rejected on the AppStore, as #ClausJørgensen said:
You're using private APIs, so it'll be rejected on the App Store. Also, Xcode 11 has some new functionality that will trigger a build failure when using certain private APIs.
I'd only recommend the usage of this for apps that won't be uploaded to the App Store, otherwise, try to achieve what you want without using commands, there sure will be any other way to do that.
If your app is targeting jailbroken iOS devices and will be uploaded to a third-party store like Cydia, Zebra, Thunderbolt or Sileo, then this would work correctly.
Here is an example of what I want to do:
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError)
{
let nm = NetworkModel()
nm.sendlog("file name :AppDelegate , line number : 288", info: " Failed to register: \(error)")
}
current scenario i done that hard coded value line number and file name . but is it possible to programatically pick line number and file name .
Literal Type Value
#file String The name of the file in which it appears.
#line Int The line number on which it appears.
#column Int The column number in which it begins.
#function String The name of the declaration in which it appears.
#dsohandle UnsafeMutablePointer The dso handle.
Example
print("Function: \(#function), line: \(#line)")
With default values in parameters you can also create a function
public func track(_ message: String, file: String = #file, function: String = #function, line: Int = #line ) {
print("\(message) called from \(function) \(file):\(line)")
}
which can be used like this
track("enters app")
In Swift 2.1
Literal Type Value
__FILE__ String The name of the file in which it appears.
__LINE__ Int The line number on which it appears.
__COLUMN__ Int The column number in which it begins.
__FUNCTION__ String The name of the declaration in which it appears.
for more info see the documentation
You can use #function, #file, #line
Here is the implementation of log method in swift : https://github.com/InderKumarRathore/SwiftLog
Below is the snippet
public func debugLog(object: Any, functionName: String = #function, fileName: String = #file, lineNumber: Int = #line) {
#if DEBUG
let className = (fileName as NSString).lastPathComponent
print("<\(className)> \(functionName) [#\(lineNumber)]| \(object)\n")
#endif
}
For swift 4 and swift 5:
func printLog(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
#if DEVELOPMENT
let className = file.components(separatedBy: "/").last
print(" ❌ Error ----> File: \(className ?? ""), Function: \(function), Line: \(line), Message: \(message)")
#endif
}
// "❌ Error ----> File: classNameViewController.swift, function: functionName(), Line: 123, Message: messageError"
Example Logging Code (TL;DR)
import os.log
#available(OSX 11.0, iOS 14.0, *)
extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
/// Logs the payment flows like Apple Pay.
#available(OSX 11.0, iOS 14.0, *)
static let payments = Logger(subsystem: subsystem, category: "payments")
}
static func DLog(message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, column: UInt = #column, category: String, type: OSLogType = .info, bundle: Bundle = .main) {
// This method is only for iOS 14+
if #available(OSX 11.0, iOS 14.0, *) {
Logger.payments.debug("\(file) : \(function) : \(line) : \(column) - \(message, privacy: .private)")
// This makes the message unreadable without a debugger attached.
} else {
// Fallback on earlier versions
let customLog = OSLog(subsystem: bundle.bundleIdentifier!,
category: category)
// IMPORTANT: I have assumed here that you only print out non-sensitive data! Using %{private}# or %{public}# in an interpolated string is not portable to `Logger`!
os_log(message, log: customLog, type: type)
// Unfortunately this legacy API doesn't support non-StaticString logs. :(
}
}
Note that you may need to rework this code for more flexibility over private/public access levels - os_log and Logger don’t handle privacy levels in the same way. This is just to show how to use the API if you don’t add os_log privacy levels in message.
Apple recommend using OS Logging which is why I have used this approach rather than print statements. I added this answer because of the new Logger API in iOS 14 that also enables string interpolation.
Motivation
I think the answers here solve the question for < iOS 14, but they can be improved further for memory efficiency since this Debug Logging function will likely be used all across a codebase in iOS 14+ (new Logger API). Apple recommend using OS Logging which is why I have used this approach rather than print statements (although these will still work).
These are minor improvements but I think they can help because even minor improvements add up. In fact, the Swift Standard Library uses these optimizations everywhere they can)! These optimisations are contributing factors to the incredible memory efficiency of Swift despite being a high-level language (and part of great API Design!). (-:
Because this function would probably fit well as part of a generalized (OS) Logging service, I have also included considerations I make when logging within apps. I think these could help you when logging for debugging purposes (I answered this question because it was probably for debug logging!).
Improvements
I prefer to use UInt in certain situations if I know the Int is going to be positive, and any edge case crashes are unlikely. Note, I don't use UInt when interfacing with Foundation classes that use Int types for this reason. This allocates less memory at runtime and I find being more specific also helps me understand the code better.
I prefer to use StaticString where I know the string is known at compile-time. From the docs, StaticString only provides low-level access to String contents and is immutable. Thus only use it where appropriate. Even concatenated String literals cannot be used to initialize a StaticString. Because its functionality is more restricted than String this means that it is lightweight - only requiring an address pointer and length under the hood. Using StaticString also provides a small performance boost with OS-level memory management because it is neither allocated nor deallocated (no need for reference counting).
Using Apple's recommended Logger API for logging is better than print statements for multiple reasons: performance, privacy and unified management. It can be better for debugging issues in released products (OS Logs provide more context).
I would recommend using StaticString where you can, for example with the function and file names, and UInt for the line and column numbers. This is only now possible with the Logger iOS 14+ API.
(Bonus) Logging Considerations
Should I do LOTS of logging?
I think logging is a balancing act. Lots of logging can be very helpful to debug issues when viewing crash reports if you have access to these. However, you need to balance Privacy, Size of the Compiled Binary and Overwhelming the Logging System.
1. Privacy: Redact sensitive info using "\(message, privacy: .private)" and "\(message, privacy: .public)" with Logger, and DON'T print sensitive information when using NSLog or print statements. I would recommend replacing print statements (for production) with OS Logs when they are for debugging purposes. Or use a preprocessor directive (#if DEBUG) inside the logging service to print only on a Debug scheme..
2. Size of the Compiled Binary: Logging statements are essentially more lines of code. The more code you add, the bigger your binary. This is usually not an issue, as long as you don't log everything under the sun since only a large increase in this metric would affect the binary size.
3. Overwhelming the logging system: If you log excessively, the device may have to discard some logs in order to keep running (limited RAM or swap space at runtime due to OS multitasking - worse for older devices). Apple is usually reliable in handling lots of user logging, and it's hard to achieve this in practice. Nevertheless, if you do encounter this, some more useful log events could be pushed out of the window.
OS Logs
The most important thing to get right is the log level. By default on iOS log entries at .info and below will be suppressed at the point of log generation, so the only real negative with those is the binary size. If you log at .log (.default for os_log) or higher, you need to be careful about not logging too much. Apple's general advice is that you look at each of these higher-level log entries to make sure they contain info that might be useful while debugging.
Finally, make sure to set the subsystem and category. The unified logging system processes a lot of log entries - the subsystem with category makes it much easier for you to focus on specific problems.
How to choose the category and subsystem?
This is a pretty opinionated topic, so I will focus on what I would think about when deciding. YMMV: there may be better options and if so, please let me know.
1. By feature (product-specific): For example, if you are a shopping app, maybe organise the subsystem by the payment, login or other app flows. If you aren't already using a modular codebase for organisation into features (or frameworks for shared code) then I can recommend this tutorial series.
2. By technology: What I mean by this is the domain of the code, is it for Push Notifications, Deep Links, User Defaults (or persistence) logic. This could be helpful for the category parameter.
3. By target: I like to also use the bundleIdentifier of the current Bundle if I can for the subsystem. This makes even more sense if you have Extension Targets (Push Notifications), multiple Targets, or different Platforms (like WatchOS, SiriKit extensions and similar). You can get the current bundle, using this code:
let myBundle = Bundle(for: MyClass.self)
Debugging user issues using OS device logs
See here for a detailed walkthrough (Access Device Console Logs). The main idea is to connect the device to your Mac (or use the Mac itself for Mac apps ;)), open Console.app, run the app and try to reproduce the crash. If you manage to get that far in the first place, then you can correlate the time of the crash with the logs for more context.
Any other reasons for logging less?
If you are an Apple Early-Adopter then you know how buggy some of the iOS Betas can be ;). When posting bug reports to Apple, the OS Logs get included in sysdiagnose files, so you may get a faster turnaround time because your bug reports have less noise.
Should I use a third-party logging service?
Depends if you can afford it and whether you really need it. I prefer to minimise the number of third-party dependencies I import in my code (if I can control this) and the native (Apple) Logging APIs worked fine for most of my use cases. These services charge extra per month, but the advantage is the user cannot view the logs (even if he wants to) by hooking his device up to Console.app on his Mac. Note that this is NOT an issue if you apply the .private log levels, so I don't usually use third-party services (YMMV). A potential reason to go for web-logging is more security against reverse-engineering though, as long as you trust the third-party vendor against data breaches (BUT also it's not as eco-friendly ;)).
static func DLog(message: String, file: String = #file, function: String = #function, line: Int = #line, column: Int = #column) {
print("\(file) : \(function) : \(line) : \(column) - \(message)")
}
add where ever you want in your code (inside a struct/var/closure/func...) the next line:
let _ = print("**** \(#file)" + String(describing: type(of: self)) + ".\(#function)[\(#line)]")
and it will print the next line:
**** /path/to/my/file.swift MyClass.someFunc()[17]