DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Logging & Observability Architecture (Production-Grade)

Most teams “log” like this:

print("Something happened")
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

Instead:

enum LogEvent {
    case userAction(name: String)
    case networkRequest(endpoint: String)
    case stateTransition(from: String, to: String)
    case error(AppError)
}
Enter fullscreen mode Exit fullscreen mode

Typed logs prevent chaos.


📦 2. Logging Protocol

protocol Logger {
    func log(_ event: LogEvent, level: LogLevel)
}
Enter fullscreen mode Exit fullscreen mode
enum LogLevel {
    case debug
    case info
    case warning
    case error
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)