Actionable rules for building, querying and operating logs on macOS with Unified Logging (OSLog), Swift, and the log CLI.
Your macOS app crashes in production. Security asks for audit trails. Performance bottlenecks need investigation. You reach for logs and get... noise. Thousands of irrelevant entries, mixed privacy levels, and no clear path to the critical data you need.
Modern macOS development faces logging challenges that go far beyond simple print() statements:
Traditional logging approaches—NSLog, third-party frameworks, or ad-hoc solutions—create more problems than they solve in enterprise environments.
These Cursor Rules transform your macOS logging from reactive debugging to proactive observability. Built around Apple's Unified Logging System, they provide enterprise-grade logging patterns that integrate seamlessly with your existing development workflow.
What you get:
Instead of scanning through thousands of log entries:
# Before: Drowning in system noise
sudo log show --last 1h | grep "error" | wc -l
# 2,847 entries
# After: Surgical precision
sudo log show --last 1h --predicate 'subsystem == "com.yourapp.auth" && eventType == fault' --style compact
# 3 critical entries, immediately actionable
Built-in privacy classification prevents accidental exposure:
// Automatically handles PII classification
authLogger.info("Login attempt for user: \(userID, privacy: .private(mask: .hash))")
// Output: "Login attempt for user: <private:ABC123>"
Integrated signpost patterns for automatic Instruments correlation:
let signpostID = OSSignpostID(log: networkLogger)
networkLogger.signpost(.begin, id: signpostID, name: "api-call")
// Your API call
networkLogger.signpost(.end, id: signpostID, name: "api-call")
Direct MDM and EDR integration patterns:
# Automated security monitoring
sudo log stream --predicate 'category == "security" && eventType == fault' --style json | your-siem-forwarder
Before: Reproduce locally, add print statements, redeploy, hope for the best
After: Query production logs with surgical precision
# Find all authentication failures in the last hour
sudo log show --last 1h --predicate 'subsystem == "com.yourapp.auth" && messageType == error' --info --style compact
# Stream real-time security events
sudo log stream --predicate 'category == "security"' --style json | jq '.eventMessage'
Before: Guess at bottlenecks, add timing code manually, clean up later
After: Built-in performance measurement with Instruments integration
// Automatic correlation with Xcode Instruments
private let performanceLogger = Logger(subsystem: "com.yourapp.performance", category: "database")
func fetchUserData() {
let signpostID = OSSignpostID(log: performanceLogger)
performanceLogger.signpost(.begin, id: signpostID, name: "user-fetch")
// Your database work
performanceLogger.signpost(.end, id: signpostID, name: "user-fetch")
}
Before: Scramble to implement audit logging, worry about privacy compliance
After: Audit-ready logs from day one
// Automatic security event logging with privacy controls
securityLogger.notice("Configuration changed: \(settingName, privacy: .public), user: \(userID, privacy: .private)")
Create centralized logger instances per subsystem:
// Sources/Shared/AppLoggers.swift
import OSLog
struct AppLoggers {
static let auth = Logger(subsystem: "com.yourapp.auth", category: "login")
static let network = Logger(subsystem: "com.yourapp.network", category: "api")
static let security = Logger(subsystem: "com.yourapp.security", category: "audit")
}
Replace existing logging calls with structured alternatives:
// Instead of this
print("User \(userEmail) failed login with error \(error)")
// Use this
AppLoggers.auth.error("Login failed for user: \(userID, privacy: .private), error: \(error.code)")
Wrap critical code paths with signposts:
func processPayment() throws {
let signpostID = OSSignpostID(log: AppLoggers.network)
AppLoggers.network.signpost(.begin, id: signpostID, name: "payment-processing")
defer {
AppLoggers.network.signpost(.end, id: signpostID, name: "payment-processing")
}
// Your payment processing logic
}
Create operational scripts for common scenarios:
#!/bin/bash
# Scripts/recent_errors.sh
# Get recent errors for your app
sudo log show --last 1h \
--predicate 'subsystem BEGINSWITH "com.yourapp"' \
--level error \
--style compact \
| grep -v "expected_warning_pattern"
Validate logging behavior in your test suite:
// Tests/LoggingTests.swift
import XCTest
import OSLog
class LoggingTests: XCTestCase {
func testAuthenticationLogging() throws {
// Capture logs for current process
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
// Trigger the code that should log
authService.attemptLogin(userID: "test-user")
// Validate log was emitted
let entries = try logStore.getEntries()
XCTAssertTrue(entries.contains { $0.composedMessage.contains("Login attempt") })
}
}
Your macOS apps deserve enterprise-grade observability. These Cursor Rules provide the foundation for logging that scales with your application and meets the demanding requirements of modern enterprise development.
Start with the basic logger setup, implement privacy-aware patterns for critical workflows, and gradually expand to full performance monitoring and enterprise integration. Your future debugging self will thank you.
You are an expert in macOS Unified Logging, Swift 5.9, Objective-C, shell scripting, the log CLI, MDM/EDR integrations, and enterprise log aggregation.
Key Principles
- Prefer Apple’s Unified Logging (OSLog) for all new code; avoid `NSLog` except for boot-level code.
- Write structured, level-based, privacy-aware logs; avoid free-text concatenation.
- Log only what is necessary: minimise PII, avoid secrets, meet data-retention regulations.
- Use predicates, categories, and subsystems to reduce noise and accelerate triage.
- Design for observability: every critical workflow path emits at least one `.info` entry and every error path emits at least one `.error` entry.
- Logs must be testable, searchable, and compliant with security-critical audit requirements.
Swift / Objective-C Rules
- Import `OSLog` (`os/log` in ObjC) and declare static `OSLog` instances per subsystem:
```swift
private let authLog = Logger(subsystem: "com.acme.auth", category: "login")
```
- Define log levels explicitly: `.debug`, `.info`, `.notice`, `.error`, `.fault`.
- Use string interpolation with privacy specifiers:
```swift
authLog.info("User id: \(userID, privacy: .private(mask: .hash)) failed at step \(step)" )
```
- Never embed `NSError`’s `localizedDescription` directly; log `code` & `domain` to stay localisation-agnostic.
- Wrap logging calls in helper functions for repeated patterns (e.g. timings, network response summaries).
- Gate verbose `.debug` logs behind `#if DEBUG` or a run-time feature flag to avoid release-build overhead.
- When interacting via shell, always call `log stream --style json` inside scripts and pipe to `jq` for automation.
Shell / `log` CLI Rules
- Retrieve recent logs: `sudo log show --last 1h --predicate 'subsystem == "com.acme.auth"' --info --style compact`.
- Always filter with predicates (`--predicate`) before exporting or transmitting to avoid MB/GB dumps.
- For continuous monitoring scripts use:
```bash
sudo log stream --predicate 'category == "update" && eventType == fault' --style json
```
- Rotate exported `.tracev3` files daily and compress with `zstd` before off-device transfer.
Error Handling & Validation
- Always log before returning an error to the caller; include context and correlation ID.
- Fail_fast: immediately log and return if required parameters are nil / invalid.
- Wrap third-party API failures in custom error enums and log at `.error` with associated values.
- Add guard statements for edge cases; the happy path should be the final section of the function.
OSLog Framework Rules
- Subsystem must match reverse-DNS bundle identifier; categories describe functional modules.
- Store `Logger` instances in static constants; do not instantiate inside hot loops.
- Use signpost APIs for performance measurements. Example:
```swift
let signpostID = OSSignpostID(log: netLog)
netLog.signpost(.begin, id: signpostID, name: "api-call")
// perform work …
netLog.signpost(.end, id: signpostID, name: "api-call")
```
- Group related signposts with the same `name` to allow automatic Xcode Instruments aggregation.
Testing
- Use `OSLogStore(scope: .currentProcessIdentifier)` in XCTests to capture live logs and assert on message content.
- Provide fixtures that reproduce critical fault conditions; validate that a `.fault` log is emitted.
Performance
- Prefer `.os_unfair_lock` over `DispatchQueue` for log-only critical sections to minimise latency.
- Avoid expensive string formatting when log level is filtered out; wrap computation in `Logger.isEnabled(for: .debug)`.
Security & Compliance
- Mark all user-supplied values with `.private` or `.public` as appropriate.
- Encrypt logs before off-device transfer using AES-256 and transmit over TLS 1.2+.
- Retention: 30 days on device, 365 days in secured, access-controlled bucket (per SOC 2).
- For failed login attempts emit additional unified log with `privacy: .public` and ship to SIEM within 5 minutes.
Operations
- Integrate with MDM: configure `com.apple.logging` profile to raise persistent log level to `info` on demand.
- Use EDR agents to subscribe to `.fault` and `.error` events from mission-critical subsystems.
- Periodically run `sysdiagnose` via `profiles schedule` for deep dives in production incidents.
Common Pitfalls
- DO NOT call `os_log()` with dynamic format strings—Apple disables privacy enforcement.
- DO NOT ship raw `log show --style syslog` output to cloud—format is lossy and deprecated.
- DO NOT log inside animation/render loops; this triggers performance watchdog.
Directory Structure
- project/
├─ Sources/
│ ├─ Auth/
│ │ └─ AuthLogger.swift
│ └─ Shared/
│ └─ Log+Utilities.swift
├─ Scripts/
│ └─ export_recent_logs.sh
└─ Tests/
└─ LoggingTests.swift
Adopt these rules to ship privacy-safe, low-noise, compliant logs on macOS while preserving debuggability and performance.