From swift-skills
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/swift-skills:swift-structured-loggingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.
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.
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.
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.
// Success
log.info("createEvent: \"\(title)\"")
// Failure — method prefix, then dash, then error
log.error("createEvent: failed — \(error)")
// State
log.info("syncStatus: events=\(eventStatus), reminders=\(reminderStatus)")
// Decision
log.info("perform: requesting access for events")
// Rejection
log.info("speakOnly: rejected — phase is \(phase)")
// Warning
log.warning("perform: access denied for reminders")
// Missing method prefix — useless in mixed log output
log.info("Created event: \(title)")
// Generic — which method? Which service?
log.error("Failed to create event")
// Verbose — log lines aren't prose
log.info("The event was successfully created and saved to the calendar")
Adding logging to a new PaymentService:
// 1. Logger+App.swift — add category
static nonisolated let payments = SBLogger(subsystem: subsystem, category: "Payments")
// 2. AppLogger.swift — add domain label
"Payments": "💳 MyApp.Payments",
// 3. PaymentService.swift — file scope logger + usage
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
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".
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.LoggerLogSink — actor with DateFormatter, writes [timestamp] [LEVEL] [label] message to stderrnpx claudepluginhub foxtrottwist/swift-skills --plugin swift-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.