dean151/swift-server-gcp-hummingbird
Glue between swift-server-gcp
Requirements
- Swift 6.0+ (the package opts in to Swift 6 language mode)
- macOS 13+ or Linux
swift-server-gcp≥ 0.1.0hummingbird≥ 2.0.0
Installation
dependencies: [
.package(
url: "https://github.com/Dean151/swift-server-gcp-hummingbird.git",
from: "0.1.0"
),
],
targets: [
.executableTarget(
name: "MyServer",
dependencies: [
.product(name: "Hummingbird", package: "hummingbird"),
.product(
name: "GoogleCloudPlatformHummingbird",
package: "swift-server-gcp-hummingbird"
),
// GcpJsonLogHandler lives in the upstream package; pull it in
// alongside if you want trace-correlated structured logs.
.product(name: "GoogleCloudPlatform", package: "swift-server-gcp"),
]
),
]Wiring it up
Two things have to line up for trace correlation to work end-to-end:
- The log handler has to be the GCP JSON handler — it's the piece that
rewrites the generic trace.* metadata keys into the logging.googleapis.com/{trace,spanId,trace_sampled} fields Cloud Logging promotes.
TraceContextMiddlewarehas to run before anything that emits a
log line for the request (most importantly LogRequestsMiddleware), so the trace metadata is already on the logger by the time those entries are written.
import Foundation
import GoogleCloudPlatform
import GoogleCloudPlatformHummingbird
import Hummingbird
import Logging
// 1. Bootstrap swift-log with the GCP handler. Do this once, before any
// logger is constructed — the upstream README has the details and the
// full severity-mapping table.
LoggingSystem.bootstrap { label in
GcpJsonLogHandler(
label: label,
googleCloudProject: ProcessInfo.processInfo.environment["GOOGLE_CLOUD_PROJECT"]
)
}
// 2. Build the router. `TraceContextMiddleware` is registered first so
// every later middleware (and every route handler) sees a logger that
// already carries trace.id / trace.span_id / trace.sampled.
let router = Router()
router.add(middleware: TraceContextMiddleware())
router.add(middleware: LogRequestsMiddleware(.info))
router.get("/") { _, context -> String in
// No manual metadata plumbing — context.logger already has the trace
// ids if the incoming request carried them.
context.logger.info("hello")
return "ok"
}
let app = Application(
router: router,
configuration: .init(address: .hostname("0.0.0.0", port: 8080))
)
try await app.runService()When a request arrives with either header set, every log line the request produces — the per-request access log from LogRequestsMiddleware, anything your handlers log, anything middlewares further down the chain log — comes out shaped like this:
{
"severity": "INFO",
"message": "Request",
"time": "2026-05-25T10:40:00.123Z",
"logger": "hb",
"logging.googleapis.com/trace": "projects/my-project/traces/0af7651916cd43dd8448eb211c80319c",
"logging.googleapis.com/spanId": "b7ad6b7169203331",
"logging.googleapis.com/trace_sampled": true,
"hb.request.id": "…",
"hb.request.method": "GET",
"hb.request.path": "/"
}When neither trace header is present, the middleware is a no-op and Hummingbird's framework-assigned hb.request.id remains the only correlation key.
Notes
- The middleware is generic over
Context: RequestContext, so it works
with the default BasicRequestContext and any custom context type.
- The metadata keys it writes (
trace.id,trace.span_id,
trace.sampled) are deployment-agnostic — defined in TraceContext.MetadataKey upstream. You can pair the middleware with a non-GCP log handler and emit the generic keys as-is.
- Propagating the trace to downstream HTTP calls is handled by
TraceContext.forwardingHeaders from the upstream package — extract a TraceContext from the inbound request in your handler and copy those headers onto your outbound request.
License
MIT — see LICENSE.
Package Metadata
Repository: dean151/swift-server-gcp-hummingbird
Default branch: main
README: README.md