How does the unified logging system work: Logger, levels, subsystems and categories, privacy, and how do you read logs back?

23.4.1 · L3 · Interview

Predict first

A logger writes debug, info, notice and an error with a private value. Which entries does the process’s own log store return, and how does the private value appear?

import OSLog
import Foundation

let logger = Logger(subsystem: "com.example.probe", category: "net")
logger.debug("debug-line")
logger.info("info-line \(42)")
logger.notice("notice-line")
logger.error("error-line \("secret", privacy: .private)")
Thread.sleep(forTimeInterval: 0.5)

let store = try OSLogStore(scope: .currentProcessIdentifier)
let entries = try store.getEntries().compactMap { $0 as? OSLogEntryLog }.filter { $0.subsystem == "com.example.probe" }
for e in entries { print(e.level.rawValue, e.category, e.composedMessage) }
print("count", entries.count)
  • info, notice and error (3 entries); the private value shows as <private>
  • All four entries, with the secret visible
  • Only the error

Mental model. print is talking to yourself in an empty room. Unified logging is writing in the ship’s log, with a level, a section and a rule about which entries are confidential.

print writes to standard output, which exists only under Xcode and is lost on a user’s device. Unified logging (os.Logger, import OSLog) writes structured entries to a system-wide log that is fast (strings are formatted lazily, only if someone reads the entry, and stored compactly) and can be collected from devices. Create a Logger(subsystem:category:), with the subsystem usually your bundle identifier and the category a feature (“network”, “sync”), then call debug, info, notice, error or fault. Levels control retention: debug entries are not kept unless debug logging is enabled, info is kept only briefly in memory unless the system is told to keep it, and notice (the default), error and fault are persisted to disk, with fault meaning a bug or broken assumption. In the check below the debug line never reaches the store, while info, notice and error do.

Privacy is built in. Interpolated values are redacted in logs collected without a debugger unless you mark them: \(name, privacy: .public) to show it, .private to hide it (the check shows <private>), or .sensitive; numbers and constant strings are public by default, while dynamic strings are private (Q347). Use categories and subsystems so you can filter: Xcode’s console and Console.app filter by them; the log show and log stream commands accept predicates such as --predicate 'subsystem == "com.example.app"'; and the app can read its own entries with OSLogStore(scope: .currentProcessIdentifier), which is how an in-app “send diagnostics” feature gathers recent logs. Log what you will need at 3 a.m.: state transitions, decisions, identifiers and error details, not every loop iteration; avoid logging secrets and personal data, and remember logs flow to sysdiagnoses and crash reports.

Debug is not persisted by default, the others are readable, and a private interpolation is redacted when no debugger is attached.

Common traps

  • Logging personal data or tokens with .public. It lands in diagnostics files and in third-party tools.
  • Expecting debug or info lines in a field log. They are not persisted by default; log the things you must keep at notice or above.

They might follow up with

  • How do you read logs from a user’s device? A sysdiagnose, Console.app with the device connected, or an in-app export using OSLogStore.
  • Why not print? It is not structured, not filterable, not retained on device and costs more.

Related questions

  • 23.6.1 What is a sysdiagnose, how do you use Console.app and the log command, and what should you ask a user or…
  • 23.1.1 What kinds of breakpoints are there in Xcode and LLDB, and how do conditions, actions, symbolic and exception…
  • 14.3.3 How do you keep personal data out of logs, screenshots, the pasteboard and the app switcher?
  • 21.2.2 How do signposts and Points of Interest work, and how do you turn a measurement into an XCTest performance…