Skip to content

berardino95/DebugKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DebugKit

In-app logging + diagnostics primitives for iOS / macOS apps. UI-free by design — you bring the SwiftUI views, DebugKit gives you the observable ring buffers and the structured log facade behind them.

What's in the box

Type Role
AppLogger Process-wide facade. Calls land in os.Logger (Console.app) and optionally fan out to a sink closure.
LogEntry Structured payload for one log line (level, category, message, metadata, source location).
LogLevel Closed enum: trace / debug / info / notice / warning / error / critical.
LogCategory, LogSubcategory String-backed wrappers. Extend with your own cases per app.
DiagnosticEntry, DiagnosticsCollector @Observable ring buffer of warning/error events for the in-app inspector.
LogStreamCollector @Observable ring buffer of every log entry — full chronological stream.

UI is intentionally not included. Each consuming app builds its own SwiftUI inspector against the observables.

Install

.package(url: "https://github.com/berardino95/DebugKit.git", from: "0.1.0")

Then import:

import DebugKit

Define your app's categories

import DebugKit

extension LogCategory {
    static let routing: LogCategory = "routing"
    static let coreData: LogCategory = "coreData"
    static let ui: LogCategory = "ui"
}

The package ships .general and .lifeCycle; everything else is your vocabulary.

Logging

import DebugKit

// Use the global `appLogger`, or instantiate `AppLogger(subsystem:)`
// for advanced setups.
appLogger.info("App launched", category: .lifeCycle)

do {
    try await save(itinerary)
} catch {
    appLogger.error(
        "Save failed",
        category: .coreData,
        subcategory: .save,
        metadata: [
            "id": itinerary.id.uuidString,
            "error": error.localizedDescription,
        ]
    )
}

Wire the inspectors

@MainActor
final class AppDelegate: NSObject, UIApplicationDelegate {
    let diagnostics = DiagnosticsCollector()
    let stream = LogStreamCollector()

    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        #if DEBUG
        appLogger.sink = { [diagnostics, stream] entry in
            Task { @MainActor in
                stream.record(entry)
                guard entry.level >= .warning else { return }
                diagnostics.record(.init(
                    source: entry.category?.rawValue ?? "App",
                    severity: entry.level == .warning ? .warning : .error,
                    debugMessage: "\(entry.fileName):\(entry.line) \(entry.function)"
                ))
            }
        }
        #endif
        return true
    }
}

Boundary-logging discipline

DebugKit doesn't enforce a convention but rewards one. The pattern that plays well with the structured metadata field:

do {
    try await someCriticalOperation()
} catch {
    appLogger.error(
        "Short human description",
        category: .coreData,
        subcategory: .save,
        metadata: [
            "id": "...",
            "error": error.localizedDescription,  // Apple-localized
        ]
    )
    throw error
}

Log at the boundary where the error stops propagating; let intermediate layers re-throw.

Export logs to a file

Both collectors can serialize their current buffer. DebugKit does no disk I/O — you get a String or Data and decide where it goes (ShareLink, .fileExporter, upload, sandbox URL, …).

// Plain-text, one line per entry. Good for .log / .txt attachments.
let text = stream.exportedText()

// JSON array of LogEntry — stable, machine-readable, pretty-printed.
let data = try stream.exportedJSON()

DiagnosticsCollector exposes the same two methods over DiagnosticEntry. Text lines look like:

2026-05-22T10:12:33.421Z [warning] coreData/save MyView.swift:42 save(_:) — Save failed {error=..., id=42}

SwiftUI example:

ShareLink(
    item: stream.exportedText(),
    preview: SharePreview("debug.log")
)

LogEntry, DiagnosticEntry, LogLevel, LogCategory and LogSubcategory are all Codable, so you can plug them into your own encoder if the bundled JSON shape doesn't fit.

Platforms

iOS 17 / macOS 14 / tvOS 17 / watchOS 10 / visionOS 1. Requires Swift 6.

License

MIT.

About

In-app logging + diagnostics primitives for iOS / macOS. Bring your own UI.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages