A lightweight, Sendable-safe Swift logging package for Apple platforms.
VoyagerLogger provides a small LogDestination protocol with convenience APIs for common log levels, and an AppLogger composite that fans out messages to multiple destinations.
| Destination | Purpose |
|---|---|
OSLogDestination |
Unified Logging via os.Logger |
RollingFileDestination |
Size-based rolling file logs |
NullDestination |
No-op (silences logging) |
SpyDestination |
Captures entries for tests |
LogFileExporter collects .log files and exports them as a merged text file or zip archive for sharing and support workflows.
let logger = AppLogger(destinations: [
OSLogDestination(subsystem: "com.app", category: "ui"),
RollingFileDestination(directory: logsURL)
])
logger.info("App launched")
logger.warning("Low memory")
logger.error("Something failed", meta: [.retry: true])OSLogDestination and RollingFileDestination accept a shared LogMessageFormat so you can choose which parts of the rendered log line appear and in what order.
Available components:
.timestamp— bracketed date/time, e.g.[2026-04-11 10:30:00].level— log level label, e.g.ERROR.callSite— file, function, and line combined, e.g.MyFile.doWork():42.message— the log message itself
Built-in defaults:
LogMessageFormat.osLogDefault—[.callSite, .message]LogMessageFormat.rollingFileDefault—[.timestamp, .level, .callSite, .message]
Customize OSLogDestination formatting:
let console = OSLogDestination(
subsystem: "com.app",
category: "network",
format: LogMessageFormat(
components: [.level, .message, .callSite],
separator: " | "
)
)Customize RollingFileDestination formatting:
let fileLogger = try RollingFileDestination(
configuration: .init(
directory: logsURL,
format: LogMessageFormat(
components: [.timestamp, .message, .level],
separator: " "
)
)
)LogMetadata uses type-safe, extensible keys instead of raw strings:
extension LogMetadataKey {
static let retry = LogMetadataKey(rawValue: "retry")
static let errorCode = LogMetadataKey(rawValue: "errorCode")
}
logger.error("Network timeout", meta: [.retry: true, .errorCode: 408])Conform to LogDestination to create your own:
struct MyDestination: LogDestination {
func log(
_ level: LogLevel,
message: @autoclosure () -> any Sendable,
info: LogInfo?,
meta: LogMetadata,
file: String,
function: String,
line: Int
) {
// your logging logic
}
}- Swift 6.0+
- iOS 16+ / macOS 13+ / tvOS 16+ / Mac Catalyst 16+