Imported from javiermanzo/LogBird (
AGENTS.md). Install upstream withnpx skills add javiermanzo/LogBird. Copyright stays with the author.
AGENTS.md — AI Agent Technical Specification & Reference for LogBird
This document serves as the primary technical specification for AI coding agents (LLMs, pair programmers, autonomous software agents) operating on, maintaining, or integrating the LogBird Swift library.
1. Executive Overview
LogBird is a lightweight, thread-safe, privacy-conscious Swift logging framework for Apple platforms (iOS 15+, macOS 12+, tvOS 15+, watchOS 8+). It operates with zero external dependencies and bridges in-memory log history, real-time Combine event streams, native Apple OSLog system mirroring, automatic privacy redaction, structured multi-format log exporting, and an optional SwiftUI debug viewer.
2. Directory Structure & File Map
LogBird/
├── Package.swift # SPM package manifest (Swift 6 / Swift 5.9 modes)
├── README.md # User-facing library documentation
├── CONTRIBUTING.md # Developer contribution guidelines
├── AGENTS.md # AI Agent technical specification (this file)
├── .agents/
│ └── skills/
│ ├── logbird/
│ │ └── SKILL.md # Agent skill for LogBird integration & context
│ └── logbird-migration/
│ └── SKILL.md # Agent skill for migration (v1 → v2.x)
├── Sources/
│ ├── LogBird/ # Core Logging Target (No SwiftUI dependencies)
│ │ ├── LogBird.swift # Public facade (static API & instance class)
│ │ ├── LBManager.swift # Underlying engine, thread-safety queues & OSLog
│ │ ├── LBRedactor.swift # Key normalization & value redaction engine
│ │ ├── Models/
│ │ │ ├── LBLog.swift # Main log record model (Codable, Identifiable, Sendable)
│ │ │ ├── LBLogLevel.swift # Severity enum (.debug, .info, .warning, .error, .critical)
│ │ │ ├── LBLogMessage.swift # Privacy-aware string interpolation (LBLogMessage & LBPrivacy)
│ │ │ ├── LBValue.swift # Typed metadata enum (.string, .int, .double, .bool, .url, .array, .dictionary)
│ │ │ ├── LBError.swift # Struct capturing error details & DecodingError context
│ │ │ ├── LBExtraMessage.swift # Labeled section string model
│ │ │ ├── LBLocation.swift # Call-site source location (#fileID, #function, #line)
│ │ │ ├── LBSource.swift # OSLog subsystem & category metadata
│ │ │ └── LBLogEvent.swift # Combine event enum (.recorded(LBLog), .cleared)
│ │ └── Export/
│ │ ├── LBLogExporter.swift # Encoding engine for JSON, JSONLines, & PlainText
│ │ ├── LBExportContent.swift # Scope enum (.all, .logs([LBLog]))
│ │ ├── LBExportDestination.swift # Output destination (.data, .file(URL?))
│ │ ├── LBExportFormat.swift # Format enum (.json, .jsonLines, .plainText)
│ │ └── LBExportOutput.swift # Export result (.data(Data), .file(URL, Data))
│ └── LogBirdUI/ # SwiftUI Debug UI Target (Depends on LogBird)
│ ├── LBLogsView.swift # Main SwiftUI container view with toolbar & search
│ ├── LogsViewModel.swift # @MainActor view model mirroring & filtering history
│ ├── LBLogRowView.swift # Tinted row view component
│ ├── LBLogExport.swift # Platform export presentations (macOS SavePanel, iOS ShareSheet)
│ └── LBLogLevel+Color.swift # SwiftUI Color extension mapping for LBLogLevel
└── Tests/
├── LogBirdTests/ # Comprehensive core logic unit tests (100+ tests)
└── LogBirdUITests/ # Comprehensive UI view model unit tests
3. Core Architectural Invariants
When modifying or extending LogBird, AI agents MUST preserve the following invariants:
3.1 Concurrency & Thread-Safety Model
- Dual Queue Isolation in
LBManager:dispatchQueue(com.logbird.accessQueue): Serial queue protecting state reads and mutations (logs,storedConfig). All configuration (maxLogs, isEnabled, minLogLevel, redactSensitiveFields, sensitiveKeysState, identifier) lives consolidated inside the singlestoredConfig: LBConfigvalue.publishQueue(com.logbird.publishQueue): Serial queue dedicated exclusively to asynchronous Combine event dispatch (publishQueue.async { logsSubject.send(event) }).- CRITICAL RE-ENTRANCY RULE: Never publish Combine events while holding a lock on
dispatchQueue. Subscribers may perform logging calls in response to.recordedevents, which would cause a re-entrancy deadlock ifdispatchQueuewere locked.
@unchecked SendableDecorator onLogBirdandLBManager:LogBirdandLBManagerare marked@unchecked Sendablebecause internal synchronization is handled explicitly viadispatchQueue. All internal mutations MUST remain guarded bydispatchQueue.
@MainActorIsolation in UI Layer:LBLogsViewandLogsViewModelare isolated to@MainActor. Combine subscriber events received frompublishQueueare bounced toDispatchQueue.mainbefore updating view state.
3.2 Recording Gate (isEnabled / minLogLevel)
- First thing in
LBManager.log(...): snapshotstoredConfig(read via theconfiggetter) in a singledispatchQueue.sync, thenreturnearly whenisEnabled == falseorlevel < minLogLevel. The gate MUST run before building theLBLog, forwarding to OSLog, mutating history or publishing — the disabled path performs no work. The whole entry (gate, redactor and identifier) is derived from that one snapshot. isEnabled(master switch) defaults to a compile-time constant resolved via#if DEBUG(trueunder DEBUG,falseotherwise), centralized inLBConfig.init. Because the package is compiled with the host app, the flag reflects the integrator's build configuration. It is a settableBoolso integrators can drive it from their own flags/macros or force it on at init.minLogLevel(severity floor) defaults to.debug(everything passes when enabled). RequiresLBLogLevel: Comparable, ordered by declaration severity (.debug < .info < .warning < .error < .critical) via an internalseverityRank— NOT the alphabetical raw value.- NOT gated:
clearLogs()andexport()always operate on recorded history regardless ofisEnabled/minLogLevel. Onlylog(...)recording is gated.
3.3 Privacy & Sensitive Data Redaction
- Automatic Field Redaction (
LBRedactor):- Scans keys in
additionalInfo,extraMessages, anderror.userInfo. - Keys and needles are normalized (lowercased, stripping
_,-, and whitespace). Keys added via.addor.setare normalized at insertion time. - If a normalized key contains any configured needle in
sensitiveKeys(default:"password","token","authorization","auth","secret","apikey","cookie","bearer","credentials","privatekey"), the value is swapped forLBRedactor.placeholder("<redacted>"). - Substring matching ensures variants like
access_token,refresh_token,set-cookie,x-api-key, andprivate_keyare automatically matched.
- Scans keys in
- Inline Interpolation Privacy (
LBLogMessage):- Uses Swift String Interpolation to replace
.privateinterpolations withLBRedactor.placeholderduring message assembly. - Example:
LogBird.log("User \(username, privacy: .private) logged in").
- Uses Swift String Interpolation to replace
3.4 Zero Third-Party Dependencies
LogBirdcore MUST only rely onFoundation,Combine, andOSLog.LogBirdUIMUST only rely onSwiftUIandLogBird.- Do NOT introduce SPM package dependencies.
4. Public API Reference & Type Signatures
4.1 LogBird Class
public class LogBird: @unchecked Sendable {
// Shared singleton instance
public static let shared: LogBird
// Constructor with defaults
public init(
subsystem: String = resolvedSubsystem(bundleIdentifier: Bundle.main.bundleIdentifier),
category: String? = nil,
fileID: String = #fileID,
config: LBConfig = LBConfig()
)
// Configuration Properties
public static var config: LBConfig { get set }
public var config: LBConfig { get set }
public var maxLogs: Int { get set }
public var redactSensitiveFields: Bool { get set }
public static func setDefaultSensitiveKeys(_ keys: [String])
public static var defaultSensitiveKeys: Set<String> { get }
public static var sensitiveKeys: Set<String> { get }
public static func sensitiveKeys(_ action: LBSensitiveKeysAction)
public var sensitiveKeys: Set<String> { get }
public func sensitiveKeys(_ action: LBSensitiveKeysAction)
public var identifier: String? { get set }
public var isEnabled: Bool { get set } // Recording master switch; default: true under DEBUG, false otherwise
public var minLogLevel: LBLogLevel { get set } // Minimum severity recorded; default: .debug
// Synchronous Read & Combine Stream
public var logs: [LBLog] { get }
public var logsPublisher: AnyPublisher<LBLogEvent, Never> { get }
// Logging Methods
public func log(_ message: String? = nil, extraMessages: [LBExtraMessage]? = nil, additionalInfo: [String: LBValue]? = nil, error: Error? = nil, level: LBLogLevel = .debug, file: String = #fileID, function: String = #function, line: Int = #line)
public func log(_ message: LBLogMessage, extraMessages: [LBExtraMessage]? = nil, additionalInfo: [String: LBValue]? = nil, error: Error? = nil, level: LBLogLevel = .debug, file: String = #fileID, function: String = #function, line: Int = #line)
// Clear & Export
public func clearLogs()
@discardableResult
public func export(_ content: LBExportContent = .all, format: LBExportFormat = .json, destination: LBExportDestination = .data) throws -> LBExportOutput
}
5. Key Data Models
| Model | Conformances | Description |
|---|---|---|
LBLog |
Codable, Identifiable, Hashable, Sendable |
Core record: id, level, message, extraMessages, additionalInfo, error, createdAt, location, source. |
LBConfig |
Hashable, Sendable |
Centralized config: maxLogs, isEnabled, minLogLevel, redactSensitiveFields, sensitiveKeys, identifier. |
LBLogLevel |
String, Codable, CaseIterable, Comparable, Sendable |
Severities: .debug, .info, .warning, .error, .critical. Ordered by severity (not alphabetically) and maps to OSLogType & emojis. |
LBValue |
Codable, Hashable, CustomStringConvertible, Sendable |
Typed metadata: .string, .int, .double, .bool, .url, .array, .dictionary. Expressible by literals. |
LBLogMessage |
ExpressibleByStringInterpolation, Hashable, Sendable |
Custom interpolation wrapper supporting \(value, privacy: .private). |
LBError |
Codable, Hashable, Sendable |
Captures domain, code, type, localizedDescription, and stringified userInfo (merged with DecodingError / EncodingError context). |
LBSensitiveKeysAction |
Hashable, Sendable |
Action enum for instance redaction configuration: .add([String]), .set([String]), .reset, .clear. |
LBExportFormat |
String, Codable, CaseIterable, Sendable |
Encoding formats: .json, .jsonLines (NDJSON), .plainText. |
LBExportOutput |
Sendable, Equatable |
Result enum: .data(Data) or .file(URL, data: Data). |
6. Rules for AI Agents Modifying Code
- Test Execution: Always run
swift test(usingBypassSandbox: trueif sandboxed) to verify clean execution. - Complete SwiftDoc Comments: Every new or modified piece of code (types, structs, enums, properties, methods, parameter lists) MUST include clean, complete SwiftDoc comments (
///or/** ... */). - No Unsafe State Access: Do not access
logsarray or internal variables inLBManageroutsidedispatchQueue.syncordispatchQueue.async. - Swift 6 Compatibility: Keep all types conformant to
Sendablewhere appropriate. - Synchronized Documentation & Skill Maintenance: Whenever code, public APIs, configurations, or features are added or modified, AI agents MUST update all corresponding documentation files:
README.md(user-facing library documentation and usage examples)AGENTS.md(this file, updating API reference signatures, invariants, and data models).agents/skills/logbird/SKILL.md(agent skill & context loader).agents/skills/logbird-migration/SKILL.md(unified migration guide across breaking releases)
7. Integration Recipe for Agents Integrating LogBird
When an AI agent is instructed to integrate LogBird into a host iOS/macOS app:
- Add
LogBirddependency inPackage.swiftor Xcode target. - Replace print statements or un-structured logs with
LogBird.log(...). - Use
privacy: .privatefor user credentials, tokens, or PII. - Pass
errorparameter incatchblocks to automatically capture error context. - In debug settings views, embed
LBLogsView().