Friday, June 7, 2024
HomeiOS DevelopmentFashionable logging with the OSLog framework in Swift – Donny Wals

Fashionable logging with the OSLog framework in Swift – Donny Wals


Everyone knows that print is probably the most ubiquitous and helpful debugging software in a developer’s toolbox. Positive, we have now 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 informed that you are able to do approach 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. After all, we don’t name it printing anymore; we name it logging.

Logging is a key methodology to amassing vital information on your app. From easy debugging strings to recording total chains of occasions, having a superb logging technique might help you debug issues when you’re writing your app in Xcode and in addition when you’ve shipped your app to the shop.

On this publish, I’d like to point out 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 allow you to debug your app and achieve insights about issues your customers expertise.

Establishing a Logger object

To arrange a logger object all it’s essential do is import OSLog and create an occasion of the Logger object:

import OSLog

let logger = Logger()

struct MyApp: App {
  // ... 
}

This strategy creates a world 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 stated, it’s smart to really present two items of configuration on your logger:

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

For instance, I prefer 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 would create loggers for each module in your app and provides every module its personal subsystem for instance. That approach, you possibly can simply determine 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’ll be able to create a number of loggers for a single subsystem for instance with the intention 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 afterward.

When you’ve created an occasion of your logger and located a pleasant place to carry on to it (I often prefer to have it obtainable as a world fixed however you may 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

While you log messages by way of your logger occasion, these messages will find yourself elsewhere relying on which form of log stage you’re utilizing. We’ll focus on log ranges later so for now we’ll simply use the easy log methodology 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, similar to it will with print…

Nonetheless, as a result of we’re utilizing a Logger, we are able to get Xcode to point out us extra data.

Right here’s an instance of the varieties of data you possibly can view in your console.

An example of a message logged with a Logger

Personally, I discover the timestamp to be probably the most attention-grabbing side of this. Usually your print statements received’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 appears to be like like after we print it utilizing print

An example of a message logged with print

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

Xcode received’t present you all the data above by default although. You must allow it by way of the metadata menu within the console space. The great factor is, you don’t have to have finished this earlier than you began debugging so you possibly can allow that everytime you’d like.

The metadata menu in Xcode's console area

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

Searching 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 provides to both embrace or exclude subsystems matching a given string.

This lets you simply drown out all of your logging noise and see precisely what you’re considering. You’ll be able to have as many subsystems, classes, and loggers as you’d like in your app so I extremely suggest to create loggers which are used for particular functions and modules for those who can. It’ll make debugging a lot simpler.

Accessing logs outdoors of Xcode

There are a number of methods so that you can achieve 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 means of the Console app in your mac you possibly can hook up with your cellphone and see a reside feed of all log messages which are being despatched to the console. That features messages that you just’re sending from your individual apps, as you possibly can see right here:

Console.app

The console gives loads of filtering choices to ensure you solely see logs which are 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, power 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 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 individuals’s units with out having to construct instantly to those units from Xcode. Very quick, very helpful.

Accessing logs in your app

If you understand that you just’d like to have the ability to obtain logs from customers with the intention to debug points with full entry to your log messages, you possibly can 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 appears to be like 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 {
        Record(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 quite simply. Including a view like this to your app and increasing it with an choice to export a JSON file that incorporates 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 wish to log data that might be thought of privateness delicate in an effort to make debugging simpler. This data may not 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 amassing when it’s being logged on person’s units.

By default, whenever you insert variables into your strings these variables can be thought of as information that must be redacted. Right here’s an instance:

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

I’m logging an entry token on this log message. Once I profile my app with the debugger connected, all the things I log can be printed as you’d anticipate; I can see the entry token.

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

Hi there, world! <personal>

The variable that you just’ve added to your log is redacted to guard your person’s privateness. If you happen to think about the data you’re inserting to be non-privacy delicate data, you possibly can mark the variable as public as follows:

 appLogger.log(stage: .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 have to mark this data as public.

Be aware that whether or not or not your log messages are recorded when the debugger isn’t connected 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. Nonetheless, the debug and information log ranges are solely proven when the debugger is connected.

Different log ranges which are helpful whenever you wish to ensure you can see them even when the debugger isn’t connected are error and fault.

In order for you to have the ability to observe whether or not privateness delicate data stays the identical all through your app, you possibly can ask the logger to create a hash for the privateness delicate worth. This lets you guarantee information consistency with out really realizing the content material of what’s being logged.

You are able to do this as follows:

 appLogger.log(stage: .default, "Hi there, world! (accessToken, privateness: .personal(masks: .hash))")

This lets you debug information consistency points with out sacrificing your person’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 software that you need to use whereas creating your app to exchange 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 person’s units.

I extremely suggest that you just begin experimenting with Logging right now by changing your print statements with debug stage logging so that you just’ll be capable of apply higher filtering and looking in addition to stream logs in your macOS console.

Don’t overlook you can 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.



Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments