Most teams “log” like this:
print("Something happened")
Six months later:
- logs are useless
- issues are impossible to reproduce
- production bugs are invisible
- crash reports lack context
- no one trusts the data
Logging is not about printing.
It is about observability.
This post shows how to design a structured, scalable logging architecture for SwiftUI:
- consistent logs
- correlated events
- safe production output
- actionable debugging
🧠 The Core Principle
If you can’t observe your app, you can’t operate it.
Logs are not for developers.
They are for systems.
🧱 1. Structured Logs, Not Strings
Never do:
print("User tapped button")
Instead:
enum LogEvent {
case userAction(name: String)
case networkRequest(endpoint: String)
case stateTransition(from: String, to: String)
case error(AppError)
}
Typed logs prevent chaos.
📦 2. Logging Protocol
protocol Logger {
func log(_ event: LogEvent, level: LogLevel)
}
enum LogLevel {
case debug
case info
case warning
case error
}
Concrete implementation translates to:
- OSLog
- files
- remote collector
- crash reporter
🔗 3. Correlation IDs
Every user session and request gets an ID:
struct TraceContext {
let sessionID: UUID
let requestID: UUID
}
Injected through the dependency graph.
Logs now tell a story, not fragments.
🧭 4. Layer Ownership
Each layer logs only its responsibility:
| Layer | Logs |
|---|---|
| ViewModel | user intent, state transitions |
| Domain | business events |
| Network | request lifecycle |
| Storage | persistence failures |
Never log from views.
🧠 5. Enrichment
Add context automatically:
func log(_ event: LogEvent, level: LogLevel) {
let enriched = EnrichedLog(
event: event,
device: deviceInfo,
appVersion: version,
sessionID: context.sessionID
)
send(enriched)
}
Views don’t add metadata.
🔐 6. Safe Logging
Never log:
- tokens
- emails
- phone numbers
- passwords
- raw payloads
Always:
- hash IDs
- redact fields
- drop sensitive keys
- gate debug logs
Security and observability must coexist.
🧪 7. Testing Logs
final class MockLogger: Logger {
var events: [LogEvent] = []
func log(_ event: LogEvent, level: LogLevel) {
events.append(event)
}
}
Now you can assert:
- correct transitions
- correct errors
- correct flows
⚠️ 8. Production Log Levels
| Level | Use |
|---|---|
| Debug | Local only |
| Info | Important state changes |
| Warning | Recoverable issues |
| Error | User-impacting failures |
Never ship verbose logs to production.
❌ 9. Common Anti-Patterns
Avoid:
- print statements
- string logs
- logging from views
- no correlation IDs
- no filtering
- logging secrets
- logging everything
Noise hides signal.
🧠 Mental Model
Think:
Event
→ Log Event
→ Enrichment
→ Transport
→ Analysis
Not:
“Just print something”
🚀 Final Thoughts
Proper observability gives you:
- faster debugging
- safer releases
- confidence in production
- fewer blind spots
- better decisions
Logging is not overhead.
It is operational clarity.
Top comments (0)