Everyone knows that print
is probably the most ubiquitous and helpful debugging device in a developer’s toolbox. Certain, we now have breakpoints too however what’s the enjoyable in that? Sprinkling some prints all through our codebase to debug an issue is far more enjoyable! And naturally after we print greater than we are able to deal with we simply add some helpful prefixes to our messages and we’re good to go once more.
What if i instructed that you are able to do manner higher with just some strains of code. You may ship your prints to extra locations, give them a precedence, and extra. After all, we don’t name it printing anymore; we name it logging.
Logging is a key technique to gathering vital information on your app. From easy debugging strings to recording total chains of occasions, having logging technique will help you debug issues whilst you’re writing your app in Xcode and likewise when you’ve shipped your app to the shop.
On this put up, I’d like to indicate you how one can arrange a Logger
from the OSLog
framework in your app, and the way you need to use it to log messages that may assist you debug your app and acquire insights about issues your customers expertise.
Organising a Logger object
To arrange a logger object all you must do is import OSLog
and create an occasion of the Logger
object:
import OSLog
let logger = Logger()
struct MyApp: App {
// ...
}
This method creates a worldwide logger object that you need to use from wherever inside your app. Since I didn’t go any customized configuration, the logger will simply log messages utilizing the default parameters.
That mentioned, it’s smart to truly present two items of configuration on your logger:
By offering these two parameters, you can also make filtering log messages lots simpler, and it permits you to group messages from a number of loggers collectively.
For instance, I wish to create a knowledge mannequin debugger that I can use to log information mannequin associated info. Right here’s how I can create such a logger:
let modelLogger = Logger.init(
subsystem: "com.myapp.fashions",
class: "myapp.debugging"
)
Apple recommends that we title our subsystems utilizing reverse-DNS notation. So for instance, com.myapp.fashions
for a subsystem that encompasses fashions inside my app. You possibly can create loggers for each module in your app and provides every module its personal subsystem for instance. That manner, you’ll be able to simply work out which module generated which log messages.
The second argument offered to my logger is a class
. I can use this class to group associated messaged collectively, even once they originated from totally different subsystems. Apple doesn’t present any naming conventions for class
so you are able to do no matter you need right here.
It’s completely acceptable for a single app to have a number of loggers. You may create a number of loggers for a single subsystem for instance so to present totally different classes. Having narrowly scoped loggers in your apps with well-named classes and subsystems will tremendously enhance your debugging expertise as we’ll see in a while.
When you’ve created an occasion of your logger and located a pleasant place to carry on to it (I often wish to have it out there as a worldwide fixed however you may need to inject it or wrap it in a category of your individual) you can begin sending your first log messages. Let’s see how that works.
Logging your first messages
While you log messages by means of your logger occasion, these messages will find yourself somewhere else relying on which sort of log degree you’re utilizing. We’ll focus on log ranges later so for now we’ll simply use the straightforward log
technique to log our messages.
Let’s log a easy “Hi there, world!” message in response to a button faucet in SwiftUI:
Button("Hi there, world") {
modelLogger.log("Hi there, world!")
}
Calling log
in your Logging
occasion will trigger a message to be printed in your Xcode console, identical to it might with print…
Nevertheless, as a result of we’re utilizing a Logger
, we are able to get Xcode to indicate us extra info.
Right here’s an instance of the varieties of knowledge you’ll be able to view in your console.
Personally, I discover the timestamp to be probably the most attention-grabbing facet of this. Usually your print statements gained’t present them and it may be laborious to differentiate between issues that occurred a second or two aside and issues that occur concurrently or in very speedy succession.
For comparability, right here’s what the identical string seems to be like after we print it utilizing print
There’s no additional info so we now have no clue of when precisely this assertion was printed, by which subsystem, and how much debugging we had been attempting to do.
Xcode gained’t present you all the data above by default although. You might want to allow it by means of the metadata menu within the console space. The great factor is, you don’t must have performed this earlier than you began debugging so you’ll be able to allow that everytime you’d like.
Gaining a lot perception into the data we’re logging is tremendous invaluable and may actually make debugging a lot simpler. Particularly with logging classes and subsystems it’ll be a lot simpler to retrace the place a log message got here from with out resorting to including prefixes or emoji to your log messages.
If you wish to filter all of your log messages by subsystem or class, you’ll be able to truly simply seek for your log message utilizing the console’s search space.
Discover how Xcode detects that I’m looking for a string that matches a identified subsystem and it presents to both embody or exclude subsystems matching a given string.
This lets you simply drown out all of your logging noise and see precisely what you’re desirous about. You may have as many subsystems, classes, and loggers as you’d like in your app so I extremely advocate to create loggers which can be used for particular functions and modules in case you can. It’ll make debugging a lot simpler.
Accessing logs outdoors of Xcode
There are a number of methods so that you can acquire entry to log messages even when Xcode isn’t operating. My private favourite is to make use of Console app.
Discovering logs within the Console app
By way of the Console app in your mac you’ll be able to hook up with your cellphone and see a stay feed of all log messages which can be being despatched to the console. That features messages that you just’re sending from your individual apps, as you’ll be able to see right here:
The console gives loads of filtering choices to be sure you solely see logs which can be attention-grabbing to you. I’ve discovered the Console app logging to be invaluable whereas testing stuff that entails background up- and downloads the place I might shut my app, drive it out of reminiscence (and detach the debugger) so I may see whether or not all delegate strategies are known as on the proper occasions with the anticipated values.
It’s additionally fairly helpful to have the ability to plug in a cellphone to your Mac, open Console, and browse your app’s logs. Inside an workplace this has allowed me to do some tough debugging on different folks’s gadgets with out having to construct instantly to those gadgets from Xcode. Very quick, very helpful.
Accessing logs in your app
If that you just’d like to have the ability to obtain logs from customers so to debug points with full entry to your log messages, you’ll be able to implement a log viewer in your app. To retrieve logs from the OSLog retailer, you need to use the OSLogStore
class to fetch your log messages.
For instance, right here’s what a easy view seems to be like that fetches all log messages that belong to subsystems that I’ve created for my app:
import Basis
import OSLog
import SwiftUI
struct LogsViewer: View {
let logs: [OSLogEntryLog]
init() {
let logStore = attempt! OSLogStore(scope: .currentProcessIdentifier)
self.logs = attempt! logStore.getEntries().compactMap { entry in
guard let logEntry = entry as? OSLogEntryLog,
logEntry.subsystem.begins(with: "com.donnywals") == true else {
return nil
}
return logEntry
}
}
var physique: some View {
Listing(logs, id: .self) { log in
VStack(alignment: .main) {
Textual content(log.composedMessage)
HStack {
Textual content(log.subsystem)
Textual content(log.date, format: .dateTime)
}.daring()
}
}
}
}
It’s a fairly easy view however it does assist me to acquire saved log messages somewhat simply. Including a view like this to your app and increasing it with an choice to export a JSON file that accommodates all of your logs (primarily based by yourself Codable
fashions) could make acquiring logs out of your customers a breeze.
Logging and privateness
Typically, you may need to log info that might be thought of privateness delicate in an effort to make debugging simpler. This info may not be required so that you can truly debug and profile your app. It’s a good suggestion to redact non-required private info that you just’re gathering when it’s being logged on consumer’s gadgets.
By default, once you insert variables into your strings these variables will likely be thought of as information that needs to be redacted. Right here’s an instance:
appLogger.log(degree: .default, "Hi there, world! (accessToken)")
I’m logging an entry token on this log message. Once I profile my app with the debugger connected, the whole lot I log will likely be printed as you’ll count on; I can see the entry token.
Nevertheless, once you disconnect the debugger, launch your app, after which view your logs within the Console app whilst you’re not operating your app by means of Xcode, the log messages will look extra like this:
Hi there, world!
The variable that you just’ve added to your log is redacted to guard your consumer’s privateness. If you happen to take into account the data you’re inserting to be non-privacy delicate info, you’ll be able to mark the variable as public as follows:
appLogger.log(degree: .default, "Background standing: (newStatus, privateness: .public)")
On this case I need to have the ability to see the standing of my background motion handler so I must mark this info as public.
Word that whether or not or not your log messages are recorded when the debugger isn’t connected is dependent upon the log degree you’re utilizing. The default
log degree will get persevered and is offered in Console app once you’re not debugging. Nevertheless, the debug
and data
log ranges are solely proven when the debugger is connected.
Different log ranges which can be helpful once you need to be sure you can see them even when the debugger isn’t connected are error
and fault
.
If you need to have the ability to observe whether or not privateness delicate info stays the identical all through your app, you’ll be able to ask the logger to create a hash for the privateness delicate worth. This lets you guarantee information consistency with out truly understanding the content material of what’s being logged.
You are able to do this as follows:
appLogger.log(degree: .default, "Hi there, world! (accessToken, privateness: .personal(masks: .hash))")
This lets you debug information consistency points with out sacrificing your consumer’s privateness which is very nice.
In Abstract
With the ability to debug and profile your apps is crucial to your app’s success. Logging is a useful device that you need to use whereas growing your app to interchange your customary print
calls and it scales superbly to manufacturing conditions the place you want to have the ability to receive collected logs out of your consumer’s gadgets.
I extremely advocate that you just begin experimenting with Logging
right this moment by changing your print statements with debug
degree logging so that you just’ll be capable to apply higher filtering and looking out in addition to stream logs in your macOS console.
Don’t neglect that you may make a number of Logger
objects for various elements of your app. With the ability to filter by subsystem and class is extraordinarily helpful and makes debugging and tracing your logs a lot simpler.