Advanced logging wrapper for Apple's unified logging with async, structured metadata, runtime details and more.
- Adaptive backend: Apple's modern
os.Loggeron supported OS versions, seamlessos_logfallback otherwise - A global, shared
Loggersingleton plus multiple keyed shared instances for packages/modules - Extensible, enum-style
Tags and JSON-like structured metadata viaLogMetadataValue - Runtime filtering by
LogLevel, async logging helper, and zero-overhead disabled logs - Optional in-app console with SwiftUI view, environment modifier, and composable store
- Signpost convenience APIs for performance tracing
- Optional SwiftLog integration (
LoggingSystem.bootstrapChronicle)
dependencies: [
.package(url: "https://github.com/aeastr/Chronicle.git", from: "3.0.0")
]import Chronicle| Target | Description |
|---|---|
Chronicle |
Core logging module |
ChronicleConsole |
Optional SwiftUI in-app console |
ChronicleSwiftLogBridge |
Optional swift-log integration for server-side Swift |
import Chronicle
// Define your tags
extension Tag {
static let cache = Tag("Cache")
static let network = Tag("Network")
static let ui = Tag("UI")
}
// Configure at app launch
Logger.shared.subsystem = Bundle.main.bundleIdentifier ?? "com.myapp"
#if DEBUG
Logger.shared.setAllowedLevels(Set(LogLevel.allCases))
#else
Logger.shared.setAllowedLevels([.error, .fault])
#endif
// Emit logs
Logger.shared.log("Loaded from disk", level: .info, tags: [.cache])
Logger.shared.log(
"Request failed",
level: .error,
tags: [.network],
metadata: [
"url": request.url,
"userID": user.id,
"retry": false
]
)Create separate logger instances for packages, modules, or features with independent configuration:
// Default global logger
Logger.shared.log("App log", level: .info)
// Named loggers with independent filtering
let packageLogger = Logger.shared(for: "com.example.package")
packageLogger.setAllowedLevels([.debug, .info])
packageLogger.log("Log from package", level: .debug)
let networkLogger = Logger.shared(for: "com.example.network")
networkLogger.setAllowedLevels([.error, .fault])
networkLogger.log("Network error", level: .error)await Logger.shared.logAsync(level: .debug, tags: [.network]) {
try await DetailedRequestDebugger.makeSummary(for: transaction)
}Metadata supports strings, integers, doubles, booleans, arrays, dictionaries, and null values:
Logger.shared.log(
"Cache miss",
level: .notice,
tags: [.cache],
metadata: [
"key": "user_42",
"reason": "Expired",
"context": [
"policy": "LRU",
"lastHit": 1_714_882_233,
"counts": [1, 3, 5]
]
]
)Output:
[Cache] Cache miss | {"key":"user_42","reason":"Expired","context":{"policy":"LRU","lastHit":1714882233,"counts":[1,3,5]}}
Trace performance-critical paths:
let id = Logger.shared.beginSignpost("Image Decode", tags: [.ui])
defer { Logger.shared.endSignpost("Image Decode", id: id) }var options = Logger.shared.currentOutputOptions
options.showMetadata = false
options.metadataFormat = .json
options.metadataKeyPolicy = .include(["scenePhase", "debugOverlays"])
Logger.shared.setOutputOptions(options)
Logger.shared.updateOutputOptions { options in
options.showMetadata = true
options.showSource = false
}Register additional sinks to observe logs in real time:
let token = Logger.shared.addEventSink { entry in
print("[\(entry.level)] \(entry.message)")
}
// Later, remove when no longer needed
Logger.shared.removeEventSink(token)Enable the SwiftUI console for QA or support builds:
import ChronicleConsole
// Enable the console store
let consoleStore = Logger.shared.enableConsole(maxEntries: 1_000)
// Wire into SwiftUI
struct RootView: View {
@State private var showConsole = false
var body: some View {
Content()
.logConsole(enabled: true)
.toolbar {
Button("Console") { showConsole = true }
}
.sheet(isPresented: $showConsole) {
if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) {
LogConsolePanel()
}
}
}
}Console capabilities: pause/resume live updates, toggle metadata/timestamp display, copy/share entries, clear buffered logs.
For server-side Swift or swift-log codebases:
import Logging
import ChronicleSwiftLogBridge
LoggingSystem.bootstrapChronicle(defaultLogLevel: .info)
let logger = Logger(label: "com.myapp.feature")
logger.notice("Task queued", metadata: ["id": .string(task.id)])Chronicle wraps Apple's Unified Logging system (os.Logger / os_log) with a Swift-friendly API. Key types:
| Type | Purpose |
|---|---|
Logger |
Main entry point with shared singleton and registry for named instances |
Tag |
Type-safe string wrapper for categorizing logs |
LogLevel |
Maps to OSLogType: debug, info, notice, warning, error, fault |
LogMetadataValue |
Recursive enum for structured JSON-like metadata |
LogOutputOptions |
Controls metadata rendering and key filtering |
All logger APIs are concurrency-safe for Swift 6. Use logAsync when you want to await expensive message builders without blocking callers.
Contributions welcome. Please feel free to submit a Pull Request. See the Contributing Guide for details.
MIT. See LICENSE for details.