|
| 1 | +# Tracing |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +Vapor's tracing API is built on top of [swift-distributed-tracing](https://github.com/apple/swift-distributed-tracing), which means it is compatible with all of swift-distributed-tracing's [backend implementations](https://github.com/apple/swift-distributed-tracing/blob/main/README.md#tracing-backends). |
| 6 | + |
| 7 | +If you are unfamiliar with tracing and spans in Swift, review the [OpenTelemetry Trace documentation](https://opentelemetry.io/docs/concepts/signals/traces/) and [swift-distributed-tracing documentation](https://swiftpackageindex.com/apple/swift-distributed-tracing/main/documentation/tracing). |
| 8 | + |
| 9 | +## TracingMiddleware |
| 10 | + |
| 11 | +To automatically create a fully annotated span for each request, add the `TracingMiddleware` to your application. |
| 12 | + |
| 13 | +```swift |
| 14 | +app.middleware.use(TracingMiddleware()) |
| 15 | +``` |
| 16 | + |
| 17 | +To get accurate span measurements and ensure that tracing identifiers are passed along correctly to other services, add this middleware before other middlewares. |
| 18 | + |
| 19 | +## Adding Spans |
| 20 | + |
| 21 | +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. |
| 22 | + |
| 23 | +### Automatic Propagation |
| 24 | + |
| 25 | +Vapor has support to automatically propagate spans between middleware and route callbacks. To do so, set the `Application.traceAutoPropagation` property to true during configuration. |
| 26 | + |
| 27 | +```swift |
| 28 | +app.traceAutoPropagation = true |
| 29 | +``` |
| 30 | + |
| 31 | +!!! note |
| 32 | + 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. |
| 33 | + |
| 34 | +Then spans may be created in the route closure using the ordinary distributed tracing syntax. |
| 35 | + |
| 36 | +```swift |
| 37 | +app.get("fetchAndProcess") { req in |
| 38 | + let result = try await fetch() |
| 39 | + return try await withSpan("getNameParameter") { _ in |
| 40 | + try await process(result) |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +### Manual Propagation |
| 46 | + |
| 47 | +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. |
| 48 | + |
| 49 | +```swift |
| 50 | +app.get("fetchAndProcess") { req in |
| 51 | + let result = try await fetch() |
| 52 | + return try await withSpan("getNameParameter", context: req.serviceContext) { _ in |
| 53 | + try await process(result) |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +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. |
| 59 | + |
| 60 | +```swift |
| 61 | +app.get("fetchAndProcess") { req in |
| 62 | + try await ServiceContext.withValue(req.serviceContext) { |
| 63 | + try await fetch() |
| 64 | + return try await process(result) |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +## NIO Considerations |
| 70 | + |
| 71 | +Because `swift-distributed-tracing` uses [`TaskLocal properties`](https://developer.apple.com/documentation/swift/tasklocal) 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**. |
| 72 | + |
| 73 | +```swift |
| 74 | +app.get("fetchAndProcessNIO") { req in |
| 75 | + withSpan("fetch", context: req.serviceContext) { span in |
| 76 | + fetchSomething().map { result in |
| 77 | + withSpan("process", context: span.context) { _ in |
| 78 | + process(result) |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
0 commit comments