Everyone has context.
This is _Baggage_Context, intended specifically for carrying values over:
- asynchronous boundaries
- process boundaries
- network boundaries
This is similar to Go's stdlib Context
[1]:
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
// ...
BaggageContext
is not FrameworkContext
, as libraries already have context's:
- NIO's
ChannelHandlerContext
,
- Swift gRPC's
UnaryResponseCallContext
, - Lambda
Runtime's
Lambda.Context` - Vapor's
Request
functions effectively as a context object,
public struct BaggageContext {
public subscript<Key: BaggageContextKey>(_ key: Key.Type) -> Key.Value? // get/set
}
Unlike Go's style, we propose that nesting the baggage is fine:
// NIO
public final class BaggageContextInboundHTTPHandler: ChannelInboundHandler {
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
context.baggage
}
}
// Lambda
Lambda.run { (request: Request, context, callback: @escaping (Result<Response, Error>) -> Void) in
context.baggage.traceID // or context.traceID
}
// Vapor
request.context // baggage context
[1] https://golang.org/pkg/context/
We will try to favor simplicity and robustness rather than "magical propagation" (often seen in Java land).
This means, explcit propagation (most of the time), we worked on some rules about how to do so:
This means APIs need to take the shape of:
httpClient.get("http://...", context: context)
We strongly suggest to MAKE CONTEXT REQUIRED.
"dropped" context and traces is the single most problematic thing with tracing (!)
We may want to do additional libraries which "instrument" other async things.
let span = tracer.newSpan(operationName: "simple work", context: context)
defer { span.end() }
some()
work(context: span.context)
here(context: span.context) // may have sub-spans
E.g. an "instrumented dispatch queue":
DispatchQueue.global.instrumented(operationName: "some-work", context: context).sync {
print("hello")
}
// start span: on submission
// end span: when closure completed
or futures:
nioFuture.instrumented(operationName: "some-maps", context: context) {
$0.map().map()
}
// start span: submission time
// end span: nested future completed
// various other APIs possible!
These are OUTSIDE OF THE SCOPE for the tracing API package. :-)
In order to propagate baggage through function calls (and asynchronous-boundaries it may often be necessary to pass it explicitly (unless wrapper APIs are provided which handle the propagation automatically).
When passing baggage context explicitly we strongly suggest sticking to the following style guideline:
- Assuming the general parameter ordering of Swift function is as follows (except DSL exceptions):
- Required non-function parameters (e.g.
(url: String)
), - Defaulted non-function parameters (e.g.
(mode: Mode = .default)
), - Required function parameters, including required trailing closures (e.g.
(onNext elementHandler: (Value) -> ())
), - Defaulted function parameters, including optional trailing closures (e.g.
(onComplete completionHandler: (Reason) -> ()) = { _ in }
).
- Required non-function parameters (e.g.
- Baggage Context should be passed as: the last parameter in the required non-function parameters group in a function declaration.
This way when reading the call side, users of these APIs can learn to "ignore" or "skim over" the context parameter and the method signature remains human-readable and “Swifty”.
Examples here: https://github.com/slashmo/gsoc-swift-baggage-context
Global InstrumentationSystem
, get an instrument or a tracer from it.
If you know what specific tracer you want, you can get that.
Open questions on inter-op with swift-log.
We specifically choose to talk about "cross-cutting tools" on this layer, but perhaps it's too abstract without more examples of what we actually mean, so here's a few examples of what could be implemented as baggage instruments but is not "directly" distributed tracing:
- deadlines
- in a multi-service system, where a request has to go "through" n services from the edge, and the edge has a strict SLA of "must reply within 200ms", we may want to carry around the deadline value with the requests as they are propagated to downstream systems. ...
- resource utilization / analysis / management
in multi-tenant environments
- if may be useful to capture congestion of resources and "who is responsible for this overload".
- authentication, delegation / access control / auditing
- This is not an area I'm an expert on but does come up as another use-case of such instruments; It feels right, since usually these also mean carrying along the execution of some task some identity information. I do not encourage building ad-hoc security if anyone ever gets to this, there's plenty literature about it, our only hope is that if such system needs to carry metadata, it should be able to use the same instrumentation "points" as tracers would. 😉
- ad-hoc "propagate with this request"
Tracing types in alignment with Open Telemetry spec.
The context is "our" baggage context.
// WORK IN PROGRESS
public protocol TracingInstrument: Instrument {
var currentSpan: Span? { get }
func startSpan(named operationName: String, baggage: BaggageContext, at timestamp: DispatchTime) -> Span
}
public protocol Span {
var operationName: String { get }
var startTimestamp: DispatchTime { get }
var endTimestamp: DispatchTime? { get }
var baggage: BaggageContext { get }
mutating func addEvent(_ event: SpanEvent)
mutating func end(at timestamp: DispatchTime)
}
Tracers of interest:
- X-Ray
- Zipkin
- Jaeger
- Honeycomb
- Instruments.app during development on a mac (could be fun?)
- Any OpenTelemetry/Tracing compatible ones with Swift clients
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE,zipkin=t61rcWkgMzE,kappa=t61rcWkgMzE
- providing an agreed-upon mechanism to forward vendor-specific trace data and avoid broken traces when multiple tracing tools participate in a single transaction.
- Universal Context Propagation for Distributed System Instrumentation Jonathan Mace, Rodrigo Fonseca, large inspiration for the (Baggage)Context and layering of the APIs
- Some API inspiration how this affects streaming APIs and Futures
- https://akka-t.racing, has some convenience APIs for Futures, Streams (so, similar to Combine, Rx etc)
- Open Telemetry Tracing Spec opentelemetry-specification/.../specification/trace/api.md
- forums thread forums.swift.org/t/server-distributed-tracing
- Logging and Context #37
- Tracer API WIP gsoc-swift-tracing#62
- NIO would depend on the context lib swift-nio #1574