跳转至

Tracing

Tracing is a powerful tool for monitoring and debugging distributed systems. Vapor's tracing API allows developers to easily track request lifecycles, propagate metadata, and integrate with popular backends like OpenTelemetry.

Vapor's tracing API is built on top of swift-distributed-tracing, which means it is compatible with all of swift-distributed-tracing's backend implementations.

If you are unfamiliar with tracing and spans in Swift, review the OpenTelemetry Trace documentation and swift-distributed-tracing documentation.

TracingMiddleware

To automatically create a fully annotated span for each request, add the TracingMiddleware to your application.

app.middleware.use(TracingMiddleware())

To get accurate span measurements and ensure that tracing identifiers are passed along correctly to other services, add this middleware before other middlewares.

Adding Spans

When adding spans to route handlers, it's ideal for them to be associated with the top-level request span. This is referred to as "span propagation" and can be handled in two different ways: automatic or manual.

Automatic Propagation

Vapor has support to automatically propagate spans between middleware and route callbacks. To do so, set the Application.traceAutoPropagation property to true during configuration.

app.traceAutoPropagation = true

Note

Enabling auto-propagation may degrade performance on high-throughput APIs with minimal tracing needs, since request span metadata must be restored for every route handler regardless of whether spans are created.

Then spans may be created in the route closure using the ordinary distributed tracing syntax.

app.get("fetchAndProcess") { req in
    let result = try await fetch()
    return try await withSpan("getNameParameter") { _ in
        try await process(result)
    }
}

Manual Propagation

To avoid the performance implications of automatic propagation, you may manually restore span metadata where necessary. TracingMiddleware automatically sets a Request.serviceContext property which may be used directly in withSpan's context parameter.

app.get("fetchAndProcess") { req in
    let result = try await fetch()
    return try await withSpan("getNameParameter", context: req.serviceContext) { _ in
        try await process(result)
    }
}

To restore the span metadata without creating a span, use ServiceContext.withValue. This is valuable if you know that downstream async libraries emit their own tracing spans, and those should be nested underneath the parent request span.

app.get("fetchAndProcess") { req in
    try await ServiceContext.withValue(req.serviceContext) {
        try await fetch()
        return try await process(result)
    }
}

NIO Considerations

Because swift-distributed-tracing uses TaskLocal properties to propagate, you must manually re-restore the context whenever you cross NIO EventLoopFuture boundaries to ensure spans are linked correctly. This is necessary regardless of whether automatic propagation is enabled.

app.get("fetchAndProcessNIO") { req in
    withSpan("fetch", context: req.serviceContext) { span in
        fetchSomething().map { result in
            withSpan("process", context: span.context) { _ in
                process(result)
            }
        }
    }
}