Contents

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.0
  • vapor ≥ 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:

  1. 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.

  1. TraceContextMiddleware has 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