Skip to content

SwiftLog

ObservabilityLoggingSwift

What it is

SwiftLog is Apple's logging API package, defining a common interface that libraries log against and applications route to a backend of their choice.

Create a Logger with a label, log at a level with structured metadata, and bootstrap a backend once at startup.

Installation

.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0")

Getting started

The smallest useful thing you can do with it, and what each part means.

Logging with metadata
import Logging

// Once, at startup, before any Logger is created.
LoggingSystem.bootstrap { label in
    var handler = StreamLogHandler.standardOutput(label: label)
    handler.logLevel = .info
    return handler
}

let logger = Logger(label: "com.example.books")

logger.info("request completed", metadata: [
    "path": "/books",
    "status": "200",
    "duration_ms": "42",
])

// Autoclosure: the message is not built when the level is disabled.
logger.debug("cache state: \(expensiveDescription())")

logger.error("sync failed", metadata: ["error": "\(error)"])
bootstrap must run before any Logger is constructed and can only be called once — calling it later, or twice, throws or silently has no effect.

Advanced usage

Where the library earns its place over a simpler alternative.

Scoped metadata and multiple backends
// A logger that stamps request context on every subsequent line.
var requestLogger = logger
requestLogger[metadataKey: "request_id"] = "\(requestID)"
requestLogger[metadataKey: "user_id"] = "\(userID)"

requestLogger.info("validating")   // carries both keys

// Fan out to several destinations.
LoggingSystem.bootstrap { label in
    MultiplexLogHandler([
        StreamLogHandler.standardOutput(label: label),
        CrashReportingLogHandler(label: label, minimumLevel: .warning),
    ])
}
A per-request logger copy is the idiomatic way to correlate lines. Because Logger is a struct, copying it is cheap and the metadata does not leak between requests.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Log level changes have no effect
bootstrap ran after loggers were created, or was called twice. Move it to the first line of main.
No output at all on Linux
The default handler writes to stdout; check that the level is not filtering everything, and that output is not buffered by the container.

Best practices

  • Call LoggingSystem.bootstrap once, at the very start, before creating any Logger.
  • Use metadata rather than string interpolation so a log platform can index the fields.
  • Copy the logger and attach request metadata rather than passing context to every call.
  • Libraries should depend on SwiftLog and never bootstrap — that is the application's decision.

Background

Why it exists, and what it was reacting to.

SwiftLog solves the same problem SLF4J solves on the JVM: a library should not dictate where logs go. It is an API with pluggable backends, not a logging implementation.