dean151/swift-server-gcp-vapor
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.0vapor≥ 4.100.0
Installation
dependencies: [
.package(
url: "https://github.com/Dean151/swift-server-gcp-vapor.git",
from: "0.1.0"
),
],
targets: [
.executableTarget(
name: "MyServer",
dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(
name: "GoogleCloudPlatformVapor",
package: "swift-server-gcp-vapor"
),
// 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 Vapor's built-in RouteLoggingMiddleware or any custom request-logging middleware), so the trace metadata is already on the logger by the time those entries are written.
import Foundation
import GoogleCloudPlatform
import GoogleCloudPlatformVapor
import Logging
import Vapor
// 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"]
)
}
let app = try await Application.make(.detect())
// 2. Register `TraceContextMiddleware` first so every later middleware
// (and every route handler) sees a logger that already carries
// trace.id / trace.span_id / trace.sampled.
app.middleware.use(TraceContextMiddleware())
app.middleware.use(RouteLoggingMiddleware(logLevel: .info))
app.get { req -> String in
// No manual metadata plumbing — req.logger already has the trace
// ids if the incoming request carried them.
req.logger.info("hello")
return "ok"
}
try await app.execute()
try await app.asyncShutdown()When a request arrives with either header set, every log line the request produces — the per-request access log from RouteLoggingMiddleware, 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": "codes.vapor.request",
"logging.googleapis.com/trace": "projects/my-project/traces/0af7651916cd43dd8448eb211c80319c",
"logging.googleapis.com/spanId": "b7ad6b7169203331",
"logging.googleapis.com/trace_sampled": true,
"request-id": "…",
"method": "GET",
"path": "/"
}When neither trace header is present, the middleware is a no-op and Vapor's framework-assigned request-id remains the only correlation key.
Notes
- 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-vapor
Default branch: main
README: README.md