| name | swift-structured-logging |
| description | Structured logging for Swift/macOS apps using the SBLogger pattern. Use when adding a new log category to a Swift service, writing log messages in Swift code, setting up logging infrastructure in a new Swift project, or reviewing log message format consistency. Triggers on: 'add logging', 'new log category', 'set up logging', 'log messages', 'SBLogger', 'Logger category'. Swift-specific โ covers Sendable isolation, nonisolated let, MainActor, os.Logger, and stderr debug output. |
Swift Structured Logging
Enforce consistent logging across Swift/macOS apps using the SBLogger pattern: actor-based stderr sink in DEBUG, os.Logger in release, domain-labeled categories, and a strict message format.
Adding a New Category
Two files, one line each:
-
Logger extension (e.g., Logger+AppName.swift):
static nonisolated let calendar = SBLogger(subsystem: subsystem, category: "Calendar")
-
SBLogger domain labels (e.g., AppLogger.swift):
"Calendar": "๐
AppName.Calendar",
Then at file scope in the service โ never inside the class:
private nonisolated let log = Logger.calendar
Bare private let picks up MainActor isolation from project settings and breaks in actors/nonisolated contexts. Always use private nonisolated let.
Message Format
Every log message follows: "methodName: description" with optional " โ detail" suffix.
The method prefix is non-negotiable โ without it, log output from multiple services is indistinguishable. The dash separator before error details enables grep filtering.
log.info("createEvent: \"\(title)\"")
log.error("createEvent: failed โ \(error)")
log.info("syncStatus: events=\(eventStatus), reminders=\(reminderStatus)")
log.info("perform: requesting access for events")
log.info("speakOnly: rejected โ phase is \(phase)")
log.warning("perform: access denied for reminders")
Levels
- debug โ verbose tracing, parameter dumps. Normally off.
- info โ operations worth recording. Method entry for key paths, completions.
- warning โ recoverable issues. Denied permissions, fallback paths.
- error โ failed operations. Thrown errors, exhausted retries.
What NOT to write
log.info("Created event: \(title)")
log.error("Failed to create event")
log.info("The event was successfully created and saved to the calendar")
Worked Example
Adding logging to a new PaymentService:
static nonisolated let payments = SBLogger(subsystem: subsystem, category: "Payments")
"Payments": "๐ณ MyApp.Payments",
private nonisolated let log = Logger.payments
@Observable final class PaymentService {
func processPayment(amount: Decimal, merchantId: String) async throws -> Receipt {
log.info("processPayment: \(amount) to \(merchantId)")
do {
let receipt = try await gateway.charge(amount: amount, merchant: merchantId)
log.info("processPayment: completed โ receipt \(receipt.id)")
return receipt
} catch {
log.error("processPayment: failed โ \(error)")
throw error
}
}
func refund(receiptId: String) async -> RefundResult {
guard let receipt = store.find(receiptId) else {
log.warning("refund: receipt not found โ \(receiptId)")
return .notFound
}
log.info("refund: processing \(receiptId)")
}
}
Output in DEBUG:
[14:23:01.445] [INFO] [๐ณ MyApp.Payments] processPayment: 29.99 to merch_abc123
[14:23:02.112] [INFO] [๐ณ MyApp.Payments] processPayment: completed โ receipt rec_xyz789
Verification
After adding logging to a file, grep to check format consistency:
grep -n 'log\.\(info\|error\|warning\|debug\)' Services/NewService.swift
Every match should show "methodName: immediately after the opening quote. Flag any that start with a capital letter without a method prefix, or use generic phrasing like "Failed to" or "Successfully".
Infrastructure Setup (New Projects Only)
When setting up logging in a new project (not adding to an existing one), read the existing SBLogger and LogSink implementations in SpokenBite as the reference pattern:
SBLogger โ Sendable struct, four levels, @autoclosure messages, #if DEBUG stderr via LogSink actor, release via os.Logger
LogSink โ actor with DateFormatter, writes [timestamp] [LEVEL] [label] message to stderr
- Domain labels โ static dict mapping category strings to emoji-prefixed display names
- Warning/error in release write to both os.Logger AND stderr for visibility