Xcode Logger Notes

[This article was first published on Posts | Joshua Cook, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This post is to primarily serve as a repository for my notes on the Explore logging in Swift video originally presented at WWDC 2020. It provides a general overview of how to use the logging system, then touches on a few specific features to keep in mind.

Additional Resources


Creating a logger object

Here are some basic rules to follow when creating a new logger object.

  1. The subsystem should be the same for every logger in the project.
  2. The category describes the part of the app the logger is reporting on.

Below is the general architecture I like to use for orgniazing my loggers in apps.

// Logger-extension.swift
import Foundation
import os
extension Logger {
static let subsystem = "com.joshuacook.MyApplication"
// Logger for the User model class.
static let userLogger = Logger(
subsystem: subsystem,
category: "UserModel"
)
// Logger for the user preferences manager class.
static let settingsLogger = Logger(
subsystem: subsystem,
category: "SettingsManager"
)
}

These logging objects can then be used in the general form shown below.

// User.swift
import os
class User {
let logger = Logger.userLogger
// ...
func changeName(to newName: String) {
logger.info("Changing user name - old: \(self.name), new: \(newName)")
// ...
}
}

All non-numeric objects/types included as run-time info in a log message will be redacted by default. This can be adjusted by setting the privacy inside the parentheses of the string interpolation. This is covered in the Privacy section below.

Log levels

Level Use Persistence
Debug for messages that are useful only during debugging not stored
Info helpful but not essential for troubleshooting only persisted during “log collect”
Notice (defualt) essential for troubleshooting persistent up to storage limit
Error for errors during execution persistent up to storage limit
Fault for bugs in the program (unexpected errors) persistent up to storage limit

Obtaining logs

The logs of a currently running process can be seen in the Console application. There are many ways of filtering the messages to only see the ones you are interested in, including filtering by subsystem, category, and type of log message. Helpfully, these filters can be saved and reused later by clicking the “Save” button under the search bar (see the image below).

Below is an example screenshot of the log messages as I am debugging a macOS application I am currently developing.

Brief console example

The logs can be gathered from a test device using the following command. Make sure to set the start time a few minutes before seeing the bug or other event of interest.

log collect \
--device \
--start '2020-06-22 9:41:00' \
--output 'app-name.logarchive'

The output (“app-name.logarchive” in this case) can then be opened in the Console application.

Formatting log messages

Log messages can be aligned or formatted in a variety of ways using the align and format arguments. The options for these parameters depend on the type of the input variable; here are the links to the documentation for aligning strings and formatting integers, floats, and booleans.

logger.log("\(number, align: .<methods>)")
logger.log("\(number, format: .<methods>)")

Below are some examples taken from Apple’s documentation. The first example shows how to specify the width of a variable in the message.

let shapeType: String = getShapeType()
let selectedColor: String = getSelectedColor()
logger.log("Shape type: \(shapeType, align: .right(columns: 15)) Color: \(selectedColor, align: .left(columns: 10))")

Integers can be formatted to decimals, hex, or octal values. The precision of floating point values can be refined, too.

let bigNumber = 1.0234e30
logger.log("The big number is \(bigNumber, format: .exponential(precision: 10, explicitPositiveSign: true, uppercase: false) )")

Boolean values can be formatted to values such as true/false or yes/`no.

let theAnswer = true
logger.log("The answer is \(theAnswer, format: .answer)")

Privacy

All non-numeric objects/types included as run-time info in log message will be redacted by default. This can be adjusted by setting the privacy inside the parentheses of the string interpolation.

The original string can be included using the .public privacy option. This should only be used for non-identifiable and non-sensitive data.

logger.log("my message: \(text, privacy: .public)")

Another useful option is to make a hash of the value so that it remains anonymous yet unique for debugging purposes.

logger.log("my message: \(word, privacy: .private(mask: .hash))")

To leave a comment for the author, please follow the link and comment on their blog: Posts | Joshua Cook.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.