Trendy logging with the OSLog framework in Swift – Donny Wals


Everyone knows that print is essentially the most ubiquitous and helpful debugging software in a developer’s toolbox. Certain, now we 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 will deal with we simply add some helpful prefixes to our messages and we’re good to go once more.

What if i informed that you are able to do manner higher with only a few strains of code. You’ll be able to ship your prints to extra locations, give them a precedence, and extra. In fact, we don’t name it printing anymore; we name it logging.

Logging is a key technique to gathering necessary information to your app. From easy debugging strings to recording whole chains of occasions, having an excellent logging technique might help you debug issues when you’re writing your app in Xcode and likewise when you’ve shipped your app to the shop.

On this submit, I’d like to point out you how one can arrange a Logger from the OSLog framework in your app, and the way you should utilize it to log messages that may allow you to debug your app and acquire insights about issues your customers expertise.

Organising a Logger object

To arrange a logger object all you should do is import OSLog and create an occasion of the Logger object:

import OSLog

let logger = Logger()

struct MyApp: App {
  // ... 
}

This strategy creates a worldwide logger object that you should utilize from anyplace 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 really present two items of configuration to your logger:

By offering these two parameters, you may make filtering log messages lots simpler, and it lets you group messages from a number of loggers collectively.

For instance, I wish to create an information mannequin debugger that I can use to log information mannequin associated data. 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 may create loggers for each module in your app and provides every module its personal subsystem for instance. That manner, you may 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 completely 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’ll be able to create a number of loggers for a single subsystem for instance so to present completely different classes. Having narrowly scoped loggers in your apps with well-named classes and subsystems will enormously 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 accessible as a worldwide fixed however you would possibly wish 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

Once you log messages by your logger occasion, these messages will find yourself in other places relying on which type of log stage you’re utilizing. We’ll talk about log ranges later so for now we’ll simply use the straightforward log technique to log our messages.

Let’s log a easy “Hey, world!” message in response to a button faucet in SwiftUI:

Button("Hey, world") {
  modelLogger.log("Hey, world!")
}

Calling log in your Logging occasion will trigger a message to be printed in your Xcode console, identical to it will with print…

Nevertheless, as a result of we’re utilizing a Logger, we will get Xcode to point out us extra data.

Right here’s an instance of the sorts of knowledge you may view in your console.

An example of a message logged with a LoggerAn example of a message logged with a Logger

Personally, I discover the timestamp to be essentially the most attention-grabbing facet of this. Usually your print statements gained’t present them and it may be laborious to tell apart 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

An example of a message logged with printAn example of a message logged with print

There’s no additional data so now we have no clue of when precisely this assertion was printed, by which subsystem, and what sort of debugging we have been attempting to do.

Xcode gained’t present you all the data above by default although. You have to allow it by the metadata menu within the console space. The good factor is, you don’t must have carried out this earlier than you began debugging so you may allow that everytime you’d like.

The metadata menu in Xcode's console areaThe metadata menu in Xcode's console area

Gaining a lot perception into the data we’re logging is tremendous beneficial 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 may really simply seek for your log message utilizing the console’s search space.

Searching for a subsystem in the consoleSearching for a subsystem in the console

Discover how Xcode detects that I’m trying to find 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 occupied with. You’ll be able to have as many subsystems, classes, and loggers as you’d like in your app so I extremely advocate to create loggers which might be used for particular functions and modules should you can. It’ll make debugging a lot simpler.

Accessing logs exterior of Xcode

There are a number of methods so that you can acquire entry to log messages even when Xcode isn’t working. 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 may hook up with your telephone and see a dwell feed of all log messages which might be being despatched to the console. That features messages that you just’re sending from your individual apps, as you may see right here:

Console.appConsole.app

The console gives loads of filtering choices to be sure to solely see logs which might 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, pressure it out of reminiscence (and detach the debugger) so I may see whether or not all delegate strategies are referred to as on the proper occasions with the anticipated values.

It’s additionally fairly helpful to have the ability to plug in a telephone 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 straight to those gadgets from Xcode. Very quick, very helpful.

Accessing logs in your app

If you recognize 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 may implement a log viewer in your app. To retrieve logs from the OSLog retailer, you should utilize 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 = strive! OSLogStore(scope: .currentProcessIdentifier)
        self.logs = strive! 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 but it surely does assist me to acquire saved log messages fairly 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 would possibly wish to log data that may very well be thought-about privateness delicate as a way to make debugging simpler. This data won’t be required so that you can really debug and profile your app. It’s a good suggestion to redact non-required private data that you just’re gathering when it’s being logged on consumer’s gadgets.

By default, whenever you insert variables into your strings these variables will likely be thought-about as information that needs to be redacted. Right here’s an instance:

 appLogger.log(stage: .default, "Hey, world! (accessToken)")

I’m logging an entry token on this log message. Once I profile my app with the debugger hooked up, every part I log will likely be printed as you’ll count on; I can see the entry token.

Nevertheless, whenever you disconnect the debugger, launch your app, after which view your logs within the Console app when you’re not working your app by Xcode, the log messages will look extra like this:

Hey, world! <non-public>

The variable that you just’ve added to your log is redacted to guard your consumer’s privateness. For those who contemplate the data you’re inserting to be non-privacy delicate data, you may mark the variable as public as follows:

 appLogger.log(stage: .default, "Background standing: (newStatus, privateness: .public)")

On this case I would like to have the ability to see the standing of my background motion handler so I must mark this data as public.

Word that whether or not or not your log messages are recorded when the debugger isn’t hooked up depends upon the log stage you’re utilizing. The default log stage will get endured and is on the market in Console app whenever you’re not debugging. Nevertheless, the debug and information log ranges are solely proven when the debugger is hooked up.

Different log ranges which might be helpful whenever you wish to be sure to can see them even when the debugger isn’t hooked up are error and fault.

If you’d like to have the ability to monitor whether or not privateness delicate data stays the identical all through your app, you may ask the logger to create a hash for the privateness delicate worth. This lets you guarantee information consistency with out really figuring out the content material of what’s being logged.

You are able to do this as follows:

 appLogger.log(stage: .default, "Hey, world! (accessToken, privateness: .non-public(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 important to your app’s success. Logging is a useful software that you should utilize whereas growing your app to switch your normal 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 stage logging so that you just’ll be capable to apply higher filtering and looking 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.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles