From d8631094393e4391c984b013c81d0e26f6345298 Mon Sep 17 00:00:00 2001 From: Yim Lee Date: Tue, 9 Aug 2022 23:23:57 -0700 Subject: [PATCH 1/5] DocC Setup --- Package.swift | 8 +- Package@swift-5.2.swift | 62 ++++ Package@swift-5.3.swift | 62 ++++ Package@swift-5.4.swift | 62 ++++ Package@swift-5.5.swift | 62 ++++ README.md | 4 +- Sources/Instrumentation/Instrument.swift | 10 +- .../InstrumentationSystem.swift | 20 +- .../Instrumentation/MultiplexInstrument.swift | 12 +- Sources/Instrumentation/NoOpInstrument.swift | 4 +- Sources/Tracing/Docs.docc/InDepthGuide.md | 332 ++++++++++++++++++ .../Resources}/single_http_span_jaeger.png | Bin .../Docs.docc/Resources}/zipkin_trace.png | Bin Sources/Tracing/Docs.docc/index.md | 204 +++++++++++ .../InstrumentationSystem+Tracing.swift | 8 +- Sources/Tracing/NoOpTracer.swift | 4 +- Sources/Tracing/Span.swift | 47 +-- Sources/Tracing/Tracer.swift | 32 +- scripts/preview_docc.sh | 30 ++ scripts/validate_license_headers.sh | 3 +- 20 files changed, 892 insertions(+), 74 deletions(-) create mode 100644 Package@swift-5.2.swift create mode 100644 Package@swift-5.3.swift create mode 100644 Package@swift-5.4.swift create mode 100644 Package@swift-5.5.swift create mode 100644 Sources/Tracing/Docs.docc/InDepthGuide.md rename {images => Sources/Tracing/Docs.docc/Resources}/single_http_span_jaeger.png (100%) rename {images => Sources/Tracing/Docs.docc/Resources}/zipkin_trace.png (100%) create mode 100644 Sources/Tracing/Docs.docc/index.md create mode 100755 scripts/preview_docc.sh diff --git a/Package.swift b/Package.swift index 954727a..e3ceb7c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.6 import PackageDescription let package = Package( @@ -9,6 +9,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-distributed-tracing-baggage.git", .upToNextMinor(from: "0.3.0")), + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ // ==== -------------------------------------------------------------------------------------------------------- @@ -46,7 +47,7 @@ let package = Package( // ==== -------------------------------------------------------------------------------------------------------- // MARK: Performance / Benchmarks - .target( + .executableTarget( name: "_TracingBenchmarks", dependencies: [ .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), @@ -56,7 +57,8 @@ let package = Package( ), .target( name: "_TracingBenchmarkTools", - dependencies: [] + dependencies: [], + exclude: ["README_SWIFT.md"] ), ] ) diff --git a/Package@swift-5.2.swift b/Package@swift-5.2.swift new file mode 100644 index 0000000..954727a --- /dev/null +++ b/Package@swift-5.2.swift @@ -0,0 +1,62 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "swift-distributed-tracing", + products: [ + .library(name: "Instrumentation", targets: ["Instrumentation"]), + .library(name: "Tracing", targets: ["Tracing"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-distributed-tracing-baggage.git", .upToNextMinor(from: "0.3.0")), + ], + targets: [ + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Instrumentation + + .target( + name: "Instrumentation", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + ] + ), + .testTarget( + name: "InstrumentationTests", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Tracing + + .target( + name: "Tracing", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + .testTarget( + name: "TracingTests", + dependencies: [ + .target(name: "Tracing"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Performance / Benchmarks + + .target( + name: "_TracingBenchmarks", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + .target(name: "Tracing"), + .target(name: "_TracingBenchmarkTools"), + ] + ), + .target( + name: "_TracingBenchmarkTools", + dependencies: [] + ), + ] +) diff --git a/Package@swift-5.3.swift b/Package@swift-5.3.swift new file mode 100644 index 0000000..954727a --- /dev/null +++ b/Package@swift-5.3.swift @@ -0,0 +1,62 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "swift-distributed-tracing", + products: [ + .library(name: "Instrumentation", targets: ["Instrumentation"]), + .library(name: "Tracing", targets: ["Tracing"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-distributed-tracing-baggage.git", .upToNextMinor(from: "0.3.0")), + ], + targets: [ + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Instrumentation + + .target( + name: "Instrumentation", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + ] + ), + .testTarget( + name: "InstrumentationTests", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Tracing + + .target( + name: "Tracing", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + .testTarget( + name: "TracingTests", + dependencies: [ + .target(name: "Tracing"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Performance / Benchmarks + + .target( + name: "_TracingBenchmarks", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + .target(name: "Tracing"), + .target(name: "_TracingBenchmarkTools"), + ] + ), + .target( + name: "_TracingBenchmarkTools", + dependencies: [] + ), + ] +) diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift new file mode 100644 index 0000000..954727a --- /dev/null +++ b/Package@swift-5.4.swift @@ -0,0 +1,62 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "swift-distributed-tracing", + products: [ + .library(name: "Instrumentation", targets: ["Instrumentation"]), + .library(name: "Tracing", targets: ["Tracing"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-distributed-tracing-baggage.git", .upToNextMinor(from: "0.3.0")), + ], + targets: [ + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Instrumentation + + .target( + name: "Instrumentation", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + ] + ), + .testTarget( + name: "InstrumentationTests", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Tracing + + .target( + name: "Tracing", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + .testTarget( + name: "TracingTests", + dependencies: [ + .target(name: "Tracing"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Performance / Benchmarks + + .target( + name: "_TracingBenchmarks", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + .target(name: "Tracing"), + .target(name: "_TracingBenchmarkTools"), + ] + ), + .target( + name: "_TracingBenchmarkTools", + dependencies: [] + ), + ] +) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift new file mode 100644 index 0000000..954727a --- /dev/null +++ b/Package@swift-5.5.swift @@ -0,0 +1,62 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "swift-distributed-tracing", + products: [ + .library(name: "Instrumentation", targets: ["Instrumentation"]), + .library(name: "Tracing", targets: ["Tracing"]), + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-distributed-tracing-baggage.git", .upToNextMinor(from: "0.3.0")), + ], + targets: [ + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Instrumentation + + .target( + name: "Instrumentation", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + ] + ), + .testTarget( + name: "InstrumentationTests", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Tracing + + .target( + name: "Tracing", + dependencies: [ + .target(name: "Instrumentation"), + ] + ), + .testTarget( + name: "TracingTests", + dependencies: [ + .target(name: "Tracing"), + ] + ), + + // ==== -------------------------------------------------------------------------------------------------------- + // MARK: Performance / Benchmarks + + .target( + name: "_TracingBenchmarks", + dependencies: [ + .product(name: "InstrumentationBaggage", package: "swift-distributed-tracing-baggage"), + .target(name: "Tracing"), + .target(name: "_TracingBenchmarkTools"), + ] + ), + .target( + name: "_TracingBenchmarkTools", + dependencies: [] + ), + ] +) diff --git a/README.md b/README.md index 3b7a06d..faf042e 100644 --- a/README.md +++ b/README.md @@ -226,9 +226,9 @@ While this is verbose, this is only the low-level building blocks that this libr > Eventually convenience wrappers will be provided, automatically wrapping future types etc. We welcome such contributions, but likely they should live in `swift-distributed-tracing-extras`. -Once a system, or multiple systems have been instrumented, a Tracer been selected and your application runs and emits some trace information, you will be able to inspect how your application is behaving by looking at one of the various trace UIs, such as e.g. Zipkin: +Once a system, or multiple systems have been instrumented, a Tracer has been selected and your application runs and emits some trace information, you will be able to inspect how your application is behaving by looking at one of the various trace UIs, such as e.g. Zipkin: -![Simple example trace in Zipkin Web UI](images/zipkin_trace.png) +![Simple example trace in Zipkin Web UI](Sources/Tracing/Docs.docc/Resources/zipkin_trace.png) ### More examples diff --git a/Sources/Instrumentation/Instrument.swift b/Sources/Instrumentation/Instrument.swift index a7385be..924f004 100644 --- a/Sources/Instrumentation/Instrument.swift +++ b/Sources/Instrumentation/Instrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -44,22 +44,22 @@ public protocol Injector { /// Conforming types are usually cross-cutting tools like tracers. They are agnostic of what specific `Carrier` is used /// to propagate metadata across boundaries, but instead just specify what values to use for which keys. public protocol Instrument { - /// Extract values from a `Carrier` by using the given extractor and inject them into the given `BaggageContext`. + /// Extract values from a `Carrier` by using the given extractor and inject them into the given `Baggage`. /// It's quite common for `Instrument`s to come up with new values if they weren't passed along in the given `Carrier`. /// /// - Parameters: /// - carrier: The `Carrier` that was used to propagate values across boundaries. /// - baggage: The `Baggage` into which these values should be injected. - /// - extractor: The `Extractor` that extracts values from the given `Carrier`. + /// - extractor: The ``Extractor`` that extracts values from the given `Carrier`. func extract(_ carrier: Carrier, into baggage: inout Baggage, using extractor: Extract) where Extract: Extractor, Extract.Carrier == Carrier - /// Inject values from a `BaggageContext` and inject them into the given `Carrier` using the given `Injector`. + /// Extract values from a `Baggage` and inject them into the given `Carrier` using the given ``Injector``. /// /// - Parameters: /// - baggage: The `Baggage` from which relevant information will be extracted. /// - carrier: The `Carrier` into which this information will be injected. - /// - injector: The `Injector` used to inject extracted `BaggageContext` into the given `Carrier`. + /// - injector: The ``Injector`` used to inject extracted `Baggage` into the given `Carrier`. func inject(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject) where Inject: Injector, Inject.Carrier == Carrier } diff --git a/Sources/Instrumentation/InstrumentationSystem.swift b/Sources/Instrumentation/InstrumentationSystem.swift index 203936d..9c512a8 100644 --- a/Sources/Instrumentation/InstrumentationSystem.swift +++ b/Sources/Instrumentation/InstrumentationSystem.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -15,21 +15,21 @@ import InstrumentationBaggage /// `InstrumentationSystem` is a global facility where the default cross-cutting tool can be configured. -/// It is set up just once in a given program to select the desired `Instrument` implementation. +/// It is set up just once in a given program to select the desired ``Instrument`` implementation. /// /// # Bootstrap multiple Instruments -/// If you need to use more that one cross-cutting tool you can do so by using `MultiplexInstrument`. +/// If you need to use more that one cross-cutting tool you can do so by using ``MultiplexInstrument``. /// /// # Access the Instrument -/// `InstrumentationSystem.instrument`: Returns whatever you passed to `.bootstrap` as an `Instrument`. +/// ``instrument``: Returns whatever you passed to ``bootstrap(_:)`` as an ``Instrument``. public enum InstrumentationSystem { private static let lock = ReadWriteLock() private static var _instrument: Instrument = NoOpInstrument() private static var isInitialized = false - /// Globally select the desired `Instrument` implementation. + /// Globally select the desired ``Instrument`` implementation. /// - /// - Parameter instrument: The `Instrument` you want to share globally within your system. + /// - Parameter instrument: The ``Instrument`` you want to share globally within your system. /// - Warning: Do not call this method more than once. This will lead to a crash. public static func bootstrap(_ instrument: Instrument) { self.lock.withWriterLock { @@ -44,18 +44,18 @@ public enum InstrumentationSystem { } } - /// For testing scenarios one may want to set instruments multiple times, rather than the set-once semantics enforced by `bootstrap()`. + /// For testing scenarios one may want to set instruments multiple times, rather than the set-once semantics enforced by ``bootstrap(_:)``. /// - /// - Parameter instrument: the instrument to boostrap the system with, if `nil` the `NoOpInstrument` is bootstrapped. + /// - Parameter instrument: the instrument to boostrap the system with, if `nil` the ``NoOpInstrument`` is bootstrapped. internal static func bootstrapInternal(_ instrument: Instrument?) { self.lock.withWriterLock { self._instrument = instrument ?? NoOpInstrument() } } - /// Returns the globally configured `Instrument`. + /// Returns the globally configured ``Instrument``. /// - /// Defaults to a no-op `Instrument` if `boostrap` wasn't called before. + /// Defaults to a no-op ``Instrument`` if ``bootstrap(_:)`` wasn't called before. public static var instrument: Instrument { self.lock.withReaderLock { self._instrument } } diff --git a/Sources/Instrumentation/MultiplexInstrument.swift b/Sources/Instrumentation/MultiplexInstrument.swift index ec2bbda..6679357 100644 --- a/Sources/Instrumentation/MultiplexInstrument.swift +++ b/Sources/Instrumentation/MultiplexInstrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -14,15 +14,15 @@ import InstrumentationBaggage -/// A pseudo-`Instrument` that may be used to instrument using multiple other `Instrument`s across a -/// common `BaggageContext`. +/// A pseudo-``Instrument`` that may be used to instrument using multiple other ``Instrument``s across a +/// common `Baggage`. public struct MultiplexInstrument { private var instruments: [Instrument] - /// Create a `MultiplexInstrument`. + /// Create a ``MultiplexInstrument``. /// - /// - Parameter instruments: An array of `Instrument`s, each of which will be used to `inject`/`extract` - /// through the same `BaggageContext`. + /// - Parameter instruments: An array of ``Instrument``s, each of which will be used to ``Instrument/inject(_:into:using:)`` or ``Instrument/extract(_:into:using:)`` + /// through the same `Baggage`. public init(_ instruments: [Instrument]) { self.instruments = instruments } diff --git a/Sources/Instrumentation/NoOpInstrument.swift b/Sources/Instrumentation/NoOpInstrument.swift index 849b439..437b31b 100644 --- a/Sources/Instrumentation/NoOpInstrument.swift +++ b/Sources/Instrumentation/NoOpInstrument.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -14,7 +14,7 @@ import InstrumentationBaggage -/// A "no op" implementation of an `Instrument`. +/// A "no op" implementation of an ``Instrument``. public struct NoOpInstrument: Instrument { public init() {} diff --git a/Sources/Tracing/Docs.docc/InDepthGuide.md b/Sources/Tracing/Docs.docc/InDepthGuide.md new file mode 100644 index 0000000..c85ce54 --- /dev/null +++ b/Sources/Tracing/Docs.docc/InDepthGuide.md @@ -0,0 +1,332 @@ +# In-Depth Guide + +An in-depth guide on using the Distributed Tracing API. + +## Overview + +When instrumenting server applications there are typically three parties involved: + +1. **Application developers** create server-side applications +2. **Library/Framework developers** provide building blocks to create these applications +3. **Instrument developers** provide tools to collect distributed metadata about your application + +For applications to be instrumented correctly these three parts have to play along nicely. + +## Application Developers + +### Setting up instruments & tracers + +As an end-user building server applications you get to choose what instruments to use to instrument your system. Here are +all the steps you need to take to get up and running: + +Add a package dependency for this repository in your `Package.swift` file, and one for the specific instrument you want +to use, in this case `FancyInstrument`: + +```swift +.package(url: "https://github.com/apple/swift-distributed-tracing.git", .branch("main")), +.package(url: "", from: "<4.2.0>"), +``` + +To your main target, add a dependency on the `Instrumentation library` and the instrument you want to use: + +```swift +.target( + name: "MyApplication", + dependencies: [ + "FancyInstrument" + ] +), +``` + +### Bootstrapping the `InstrumentationSystem` + +Instead of providing each instrumented library with a specific instrument explicitly, you *bootstrap* the +`InstrumentationSystem` which acts as a singleton that libraries/frameworks access when calling out to the configured +`Instrument`: + +```swift +InstrumentationSystem.bootstrap(FancyInstrument()) +``` + +#### Recommended bootstrap order + +Swift offers developers a suite of observability libraries: logging, metrics and tracing. Each of those systems offers a `bootstrap` function. It is useful to stick to a recommended boot order in order to achieve predictable initialization of applications and sub-systems. + +Specifically, it is recommended to bootstrap systems in the following order: + +1. [Swift Log](https://github.com/apple/swift-log#default-logger-behavior)'s `LoggingSystem` +2. [Swift Metrics](https://github.com/apple/swift-metrics#selecting-a-metrics-backend-implementation-applications-only)' `MetricsSystem` +3. Swift Tracing's `InstrumentationSystem` +4. Finally, any other parts of your application + +This is because tracing systems may attempt to emit metrics about their status etc. + +#### Bootstrapping multiple instruments using MultiplexInstrument + +It is important to note that `InstrumentationSystem.bootstrap(_: Instrument)` must only be called once. In case you +want to bootstrap the system to use multiple instruments, you group them in a `MultiplexInstrument` first, which you +then pass along to the `bootstrap` method like this: + +```swift +InstrumentationSystem.bootstrap(MultiplexInstrument([FancyInstrument(), OtherFancyInstrument()])) +``` + +`MultiplexInstrument` will then call out to each instrument it has been initialized with. + +### Context propagation, by explicit `LoggingContext` passing + +> `LoggingContext` naming has been carefully selected and it reflects the type's purpose and utility: It binds a [Swift Log `Logger`](https://github.com/apple/swift-log) with an associated distributed tracing [Baggage](https://github.com/apple/swift-distributed-tracing-baggage). +> +> It _also_ is used for tracing, by tracers reaching in to read or modify the carried baggage. + +For instrumentation and tracing to work, certain pieces of metadata (usually in the form of identifiers), must be +carried throughout the entire system–including across process and service boundaries. Because of that, it's essential +for a context object to be passed around your application and the libraries/frameworks you depend on, but also carried +over asynchronous boundaries like an HTTP call to another service of your app. + +`LoggingContext` should always be passed around explicitly. + +Libraries which support tracing are expected to accept a `LoggingContext` parameter, which can be passed through the entire application. Make sure to always pass along the context that's previously handed to you. E.g., when making an HTTP request using `AsyncHTTPClient` in a `NIO` handler, you can use the `ChannelHandlerContext`s `baggage` property to access the `LoggingContext`. + +#### Context argument naming/positioning + +> 💡 This general style recommendation has been ironed out together with the Swift standard library, core team, the SSWG as well as members of the community. Please respect these recommendations when designing APIs such that all APIs are able to "feel the same" yielding a great user experience for our end users ❤️ +> +> It is possible that the ongoing Swift Concurrency efforts, and "Task Local" values will resolve this explicit context passing problem, however until these arrive in the language, please adopt the "context is the last parameter" style as outlined here. + +Propagating baggage context through your system is to be done explicitly, meaning as a parameter in function calls, following the "flow" of execution. + +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): + 1. Required non-function parameters (e.g. `(url: String)`), + 2. Defaulted non-function parameters (e.g. `(mode: Mode = .default)`), + 3. Required function parameters, including required trailing closures (e.g. `(onNext elementHandler: (Value) -> ())`), + 4. Defaulted function parameters, including optional trailing closures (e.g. `(onComplete completionHandler: (Reason) -> ()) = { _ in }`). +- Logging 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: + +- `func request(_ url: URL,` **`context: LoggingContext`** `)`, which may be called as `httpClient.request(url, context: context)` +- `func handle(_ request: RequestObject,` **`context: LoggingContext`**`)` + - if a "framework context" exists and _carries_ the baggage context already, it is permitted to pass that context + together with the baggage; + - it is _strongly recommended_ to store the baggage context as `baggage` property of `FrameworkContext`, and conform `FrameworkContext` to `LoggingContext` in such cases, in order to avoid the confusing spelling of `context.context`, and favoring the self-explanatory `context.baggage` spelling when the baggage is contained in a framework context object. +- `func receiveMessage(_ message: Message, context: FrameworkContext)` +- `func handle(element: Element,` **`context: LoggingContext`** `, settings: Settings? = nil)` + - before any defaulted non-function parameters +- `func handle(element: Element,` **`context: LoggingContext`** `, settings: Settings? = nil, onComplete: () -> ())` + - before defaulted parameters, which themselfes are before required function parameters +- `func handle(element: Element,` **`context: LoggingContext`** `, onError: (Error) -> (), onComplete: (() -> ())? = nil)` + +In case there are _multiple_ "framework-ish" parameters, such as passing a NIO `EventLoop` or similar, we suggest: + +- `func perform(_ work: Work, for user: User,` _`frameworkThing: Thing, eventLoop: NIO.EventLoop,`_ **`context: LoggingContext`**`)` + - pass the baggage as **last** of such non-domain specific parameters as it will be _by far more_ omnipresent than any + specific framework parameter - as it is expected that any framework should be accepting a context if it can do so. + While not all libraries are necessarily going to be implemented using the same frameworks. + +We feel it is important to preserve Swift's human-readable nature of function definitions. In other words, we intend to +keep the read-out-loud phrasing of methods to remain _"request that URL (ignore reading out loud the context parameter)"_ +rather than _"request (ignore this context parameter when reading) that URL"_. + +#### When to use what context type? + +Generally libraries should favor accepting the general `LoggingContext` type, and **not** attempt to wrap it, as it will result in difficult to compose APIs between multiple libraries. Because end users are likely going to be combining various libraries in a single application, it is important that they can "just pass along" the same context object through all APIs, regardless which other library they are calling into. + +Frameworks may need to be more opinionated here, and e.g. already have some form of "per request context" contextual object which they will conform to `LoggingContext`. _Within_ such framework it is fine and expected to accept and pass the explicit `SomeFrameworkContext`, however when designing APIs which may be called _by_ other libraries, such framework should be able to accept a generic `LoggingContext` rather than its own specific type. + +#### Existing context argument + +When adapting an existing library/framework to support `LoggingContext` and it already has a "framework context" which is expected to be passed through "everywhere", we suggest to follow these guidelines for adopting LoggingContext: + +1. Add a `Baggage` as a property called `baggage` to your own `context` type, so that the call side for your + users becomes `context.baggage` (rather than the confusing `context.context`) +2. If you cannot or it would not make sense to carry baggage inside your framework's context object, pass (and accept (!)) the `LoggingContext` in your framework functions like follows: +- if they take no framework context, accept a `context: LoggingContext` which is the same guideline as for all other cases +- if they already _must_ take a context object and you are out of words (or your API already accepts your framework context as "context"), pass the baggage as **last** parameter (see above) yet call the parameter `baggage` to disambiguate your `context` object from the `baggage` context object. + +Examples: + +- `Lamda.Context` may contain `baggage` and a `logger` and should be able to conform to `LoggingContext` + - passing context to a `Lambda.Context` unaware library becomes: `http.request(url: "...", context: context)`. +- `ChannelHandlerContext` offers a way to set/get baggage on the underlying channel via `context.baggage = ...` + - this context is not passed outside a handler, but within it may be passed as is, and the baggage may be accessed on it directly through it. + - Example: [https://github.com/apple/swift-nio/pull/1574](https://github.com/apple/swift-nio/pull/1574) + +### Creating context objects (and when not to do so) + +Generally application developers _should not_ create new context objects, but rather keep passing on a context value that they were given by e.g. the web framework invoking the their code. + +If really necessary, or for the purposes of testing, one can create a baggage or context using one of the two factory functions: + +- [`DefaultLoggingContext.topLevel(logger:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L232-L259) or [`Baggage.topLevel`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L79-L103) - which creates an empty context/baggage, without any values. It should _not_ be used too frequently, and as the name implies in applications it only should be used on the "top level" of the application, or at the beginning of a contextless (e.g. timer triggered) event processing. +- [`DefaultLoggingContext.TODO(logger:reason:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L262-L292) or [`Baggage.TODO`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L107-L136) - which should be used to mark a parameter where "before this code goes into production, a real context should be passed instead." An application can be run with `-DBAGGAGE_CRASH_TODOS` to cause the application to crash whenever a TODO context is still in use somewhere, making it easy to diagnose and avoid breaking context propagation by accidentally leaving in a `TODO` context in production. + +Please refer to the respective functions documentation for details. + +If using a framework which itself has a "`...Context`" object you may want to inspect it for similar factory functions, as `LoggingContext` is a protocol, that may be conformed to by frameworks to provide a smoother user experience. + +### Working with `Span`s + +The primary purpose of this API is to start and end so-called ``Span`` types. + +Spans form hierarchies with their parent spans, and end up being visualized using various tools, usually in a format similar to gant charts. So for example, if we had multiple operations that compose making dinner, they would be modelled as child spans of a main `makeDinner` span. Any sub tasks are again modelled as child spans of any given operation, and so on, resulting in a trace view similar to: + +``` +>-o-o-o----- makeDinner ----------------o---------------x [15s] + \-|-|- chopVegetables--------x | [2s] + | | \- chop -x | | [1s] + | | \--- chop -x | [1s] + \-|- marinateMeat -----------x | [3s] + \- preheatOven -----------------x | [10s] + \--cook---------x [5s] +``` + +The above trace is achieved by starting and ending spans in all the mentioned functions, for example, like this: + +```swift +let tracer: Tracer + +func makeDinner(context: LoggingContext) async throws -> Meal { + tracer.withSpan(operationName: "makeDinner", context) { + let veggiesFuture = try chopVegetables(context: span.context) + let meatFuture = marinateMeat(context: span.context) + let ovenFuture = try preheatOven(temperature: 350, context: span.context) + ... + return cook(veggies, meat, oven) + } +} +``` + +> ❗️ It is tremendously important to **always `end()` a started ``Span``**! make sure to end any started span on _every_ code path, including error paths +> +> Failing to do so is an error, and a tracer *may* decide to either crash the application or log warnings when an not-ended span is deinitialized. + + +## Library/Framework developers: Instrumenting your software + +### Extracting & injecting Baggage + +When hitting boundaries like an outgoing HTTP request you call out to the configured instrument(s) (see ): + +An HTTP client e.g. should inject the given `LoggingContext` into the HTTP headers of its outbound request: + +```swift +func get(url: String, context: LoggingContext) { + var request = HTTPRequest(url: url) + InstrumentationSystem.instrument.inject( + context.baggage, + into: &request.headers, + using: HTTPHeadersInjector() + ) +} +``` + +On the receiving side, an HTTP server should use the following `Instrument` API to extract the HTTP headers of the given +`HTTPRequest` into: + +```swift +func handler(request: HTTPRequest, context: LoggingContext) { + InstrumentationSystem.instrument.extract( + request.headers, + into: &context.baggage, + using: HTTPHeadersExtractor() + ) + // ... +} +``` + +> In case your library makes use of the `NIOHTTP1.HTTPHeaders` type we already have an `HTTPHeadersInjector` & +`HTTPHeadersExtractor` available as part of the `NIOInstrumentation` library. + +For your library/framework to be able to carry `LoggingContext` across asynchronous boundaries, it's crucial that you carry the context throughout your entire call chain in order to avoid dropping metadata. + +### Tracing your library + +When your library/framework can benefit from tracing, you should make use of it by integrating the `Tracing` library. + +In order to work with the tracer configured by the end-user (see ), it adds a property to `InstrumentationSystem` that gives you back a ``Tracer``. You can then use that tracer to start ``Span``s. In an HTTP client you e.g. +should start a ``Span`` when sending the outgoing HTTP request: + +```swift +func get(url: String, context: LoggingContext) { + var request = HTTPRequest(url: url) + + // inject the request headers into the baggage as explained above + + // start a span for the outgoing request + let tracer = InstrumentationSystem.tracer + var span = tracer.startSpan(named: "HTTP GET", context: context, ofKind: .client) + + // set attributes on the span + span.attributes.http.method = "GET" + // ... + + self.execute(request).always { _ in + // set some more attributes & potentially record an error + + // end the span + span.end() + } +} +``` + +> ⚠️ Make sure to ALWAYS end spans. Ensure that all paths taken by the code will result in ending the span. +> Make sure that error cases also set the error attribute and end the span. + +> In the above example we used the semantic `http.method` attribute that gets exposed via the +`TracingOpenTelemetrySupport` library. + +## Instrument developers: Creating an instrument + +Creating an instrument means adopting the `Instrument` protocol (or ``Tracer`` in case you develop a tracer). +`Instrument` is part of the `Instrumentation` library & `Tracing` contains the ``Tracer`` protocol. + +`Instrument` has two requirements: + +1. A method to inject values inside a `LoggingContext` into a generic carrier (e.g. HTTP headers) +2. A method to extract values from a generic carrier (e.g. HTTP headers) and store them in a `LoggingContext` + +The two methods will be called by instrumented libraries/frameworks at asynchronous boundaries, giving you a chance to +act on the provided information or to add additional information to be carried across these boundaries. + +> Check out the [`Baggage` documentation](https://github.com/apple/swift-distributed-tracing-baggage) for more information on +how to retrieve values from the `LoggingContext` and how to set values on it. + +### Creating a `Tracer` + +When creating a tracer you need to create two types: + +1. Your tracer conforming to ``Tracer`` +2. A span class conforming to ``Span`` + +> ``Span`` conforms to the standard rules defined in [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-specification/blob/v0.7.0/specification/trace/api.md#span), so if unsure about usage patterns, you can refer to this specification and examples referring to it. + +### Defining, injecting and extracting Baggage + +```swift +import Tracing + +private enum TraceIDKey: BaggageKey { + typealias Value = String +} + +extension Baggage { + var traceID: String? { + get { + return self[TraceIDKey.self] + } + set { + self[TraceIDKey.self] = newValue + } + } +} + +var context = DefaultLoggingContext.topLevel(logger: ...) +context.baggage.traceID = "4bf92f3577b34da6a3ce929d0e0e4736" +print(context.baggage.traceID ?? "new trace id") +``` + diff --git a/images/single_http_span_jaeger.png b/Sources/Tracing/Docs.docc/Resources/single_http_span_jaeger.png similarity index 100% rename from images/single_http_span_jaeger.png rename to Sources/Tracing/Docs.docc/Resources/single_http_span_jaeger.png diff --git a/images/zipkin_trace.png b/Sources/Tracing/Docs.docc/Resources/zipkin_trace.png similarity index 100% rename from images/zipkin_trace.png rename to Sources/Tracing/Docs.docc/Resources/zipkin_trace.png diff --git a/Sources/Tracing/Docs.docc/index.md b/Sources/Tracing/Docs.docc/index.md new file mode 100644 index 0000000..f541c06 --- /dev/null +++ b/Sources/Tracing/Docs.docc/index.md @@ -0,0 +1,204 @@ +# ``Tracing`` + +A Distributed Tracing API for Swift. + +## Overview + +This is a collection of Swift libraries enabling the instrumentation of server side applications using tools such as tracers. Our goal is to provide a common foundation that allows to freely choose how to instrument systems with minimal changes to your actual code. + +While Swift Distributed Tracing allows building all kinds of _instruments_, which can co-exist in applications transparently, its primary use is instrumenting multi-threaded and distributed systems with Distributed Traces. + + +--- + +This project uses the context progagation type defined independently in: + +- 🧳 [swift-distributed-tracing-baggage](https://github.com/apple/swift-distributed-tracing-baggage) -- [`Baggage`](https://apple.github.io/swift-distributed-tracing-baggage/docs/current/InstrumentationBaggage/Structs/Baggage.html) (zero dependencies) + +## Compatibility + +This project is designed in a very open and extensible manner, such that various instrumentation and tracing systems can be built on top of it. + +The purpose of the tracing package is to serve as common API for all tracer and instrumentation implementations. Thanks to this, libraries may only need to be instrumented once, and then be used with any tracer which conforms to this API. + +### Tracing Backends + +Compatible `Tracer` implementations: + +| Library | Status | Description | +| ------- | ------ | ----------- | +| [@slashmo](https://github.com/slashmo) / [**OpenTelemetry** Swift](https://github.com/slashmo/opentelemetry-swift) | Complete | Exports spans to OpenTelemetry Collector; **X-Ray** & **Jaeger** propagation available via extensions. | +| [@pokrywka](https://github.com/pokryfka) / [AWS **xRay** SDK Swift](https://github.com/pokryfka/aws-xray-sdk-swift) | Complete (?) | ... | + +## Getting Started + +In this short getting started example, we'll go through bootstrapping, immediately benefiting from tracing, and instrumenting our own synchronous and asynchronous APIs. The explain all the pieces of the API in more depth. When in doubt, you may want to refer to the [OpenTelemetry](https://opentelemetry.io), [Zipkin](https://zipkin.io), or [Jaeger](https://www.jaegertracing.io) documentations because all the concepts for different tracers are quite similar. + +### Dependencies & Tracer backend + +In order to use tracing you will need to bootstrap a tracing backend (). + +When developing an *application* locate the specific tracer library you would like to use and add it as an dependency directly: + +```swift +.package(url: "", // the specific tracer + ] +), +``` + +Then (in an application, libraries should _never_ invoke `bootstrap`), you will want to bootstrap the specific tracer you want to use in your application. A ``Tracer`` is a type of `Instrument` and can be offered used to globally bootstrap the tracing system, like this: + + +```swift +import Tracing // the tracing API +import AwesomeTracing // the specific tracer + +InstrumentationSystem.bootstrap(AwesomeTracing()) +``` + +If you don't bootstrap (or other instrument) the default no-op tracer is used, which will result in no trace data being collected. + +### Benefiting from instrumented libraries/frameworks + +**Automatically reported spans**: When using an already instrumented library, e.g. an HTTP Server which automatically emits spans internally, this is all you have to do to enable tracing. It should now automatically record and emit spans using your configured backend. + +**Using baggage and logging context**: The primary transport type for tracing metadata is called `Baggage`, and the primary type used to pass around baggage context and loggers is `LoggingContext`. Logging context combines baggage context values with a smart `Logger` that automatically includes any baggage values ("trace metadata") when it is used for logging. For example, when using an instrumented HTTP server, the API could look like this: + +```swift +SomeHTTPLibrary.handle { (request, context) in + context.logger.info("Wow, tracing!") // automatically includes tracing metadata such as "trace-id" + return try doSomething(request context: context) +} +``` + +In this snippet, we use the context logger to log a very useful message. However it is even more useful than it seems at first sight: if a tracer was installed and extracted tracing information from the incoming request, it would automatically log our message _with_ the trace information, allowing us to co-relate all log statements made during handling of this specific request: + +``` +05:46:38 example-trace-id=1111-23-1234556 info: Wow tracing! +05:46:38 example-trace-id=9999-22-9879797 info: Wow tracing! +05:46:38 example-trace-id=9999-22-9879797 user=Alice info: doSomething() for user Alice +05:46:38 example-trace-id=1111-23-1234556 user=Charlie info: doSomething() for user Charlie +05:46:38 example-trace-id=1111-23-1234556 user=Charlie error: doSomething() could not complete request! +05:46:38 example-trace-id=9999-22-9879797 user=alice info: doSomething() completed +``` + +Thanks to tracing, and trace identifiers, even if not using tracing visualization libraries, we can immediately co-relate log statements and know that the request `1111-23-1234556` has failed. Since our application can also _add_ values to the context, we can quickly notice that the error seems to occur for the user `Charlie` and not for user `Alice`. Perhaps the user Charlie has exceeded some quotas, does not have permissions or we have a bug in parsing names that include the letter `h`? We don't know _yet_, but thanks to tracing we can much quicker begin our investigation. + +**Passing context to client libraries**: When using client libraries that support distributed tracing, they will accept a `Baggage.LoggingContext` type as their _last_ parameter in many calls. + +When using client libraries that support distributed tracing, they will accept a `Baggage.LoggingContext` type as their _last_ parameter in many calls. Please refer to the section of the to learn more about how to properly pass context values around. + +### Instrumenting your code + +Adding a span to synchronous functions can be achieved like this: + +```swift +func handleRequest(_ op: String, context: LoggingContext) -> String { + let tracer = InstrumentationSystem.tracer + let span = tracer.startSpan(operationName: "handleRequest(\(name))", context: context) + defer { span.end() } + + return "done:\(op)" +} +``` + +Throwing can be handled by either recording errors manually into a span by calling ``Span/recordError(_:)``, or by wrapping a potentially throwing operation using the `withSpan(operation:context:body:)` function, which automatically records any thrown error and ends the span at the end of the body closure scope: + +```swift +func handleRequest(_ op: String, context: LoggingContext) -> String { + return try InstrumentationSystem.tracer + .withSpan(operationName: "handleRequest(\(name))", context: context) { + return try dangerousOperation() + } +} +``` + +If this function were asynchronous, and returning a [Swift NIO](https://github.com/apple/swift-nio) `EventLoopFuture`, +we need to end the span when the future completes. We can do so in its `onComplete`: + +```swift +func handleRequest(_ op: String, context: LoggingContext) -> EventLoopFuture { + let tracer = InstrumentationSystem.tracer + let span = tracer.startSpan(operationName: "handleRequest(\(name))", context: context) + + let future: EventLoopFuture = someOperation(op) + future.whenComplete { _ in + span.end() // oh no, ignored errors! + } + + return future +} +``` + +This is better, however we ignored the possibility that the future perhaps has failed. If this happens, we would like to report the span as _errored_ because then it will show up as such in tracing backends and we can then easily search for failed operations etc. + +To do this within the future we could manually invoke the ``Span/recordError(_:)`` API before ending the span like this: + +```swift +func handleRequest(_ op: String, context: LoggingContext) -> EventLoopFuture { + let tracer = InstrumentationSystem.tracer + let span = tracer.startSpan(operationName: "handleRequest(\(name))", context: context) + + let future: EventLoopFuture = someOperation(op) + future.whenComplete { result in + switch result { + case .failure(let error): span.recordError(error) + case .success(let value): // ... record additional *attributes* into the span + } + span.end() + } + + return future +} +``` + +While this is verbose, this is only the low-level building blocks that this library provides, higher level helper utilities can be + +> Eventually convenience wrappers will be provided, automatically wrapping future types etc. We welcome such contributions, but likely they should live in `swift-distributed-tracing-extras`. + +Once a system, or multiple systems have been instrumented, a ``Tracer`` has been selected and your application runs and emits some trace information, you will be able to inspect how your application is behaving by looking at one of the various trace UIs, such as e.g. Zipkin: + +![Simple example trace in Zipkin Web UI](zipkin_trace.png) + +### More examples + +It sometimes is easier to grasp the usage of tracing by looking at a "real" application - which is why we have implemented an example application, spanning multiple nodes and using various databases - tracing through all of them. You can view the example application here: [slashmo/swift-tracing-examples](https://github.com/slashmo/swift-tracing-examples/tree/main/hotrod). + +### Future work: Tracing asynchronous functions + +> ⚠️ This section refers to in-development upcoming Swift Concurrency features and can be tried out using nightly snapshots of the Swift toolchain. + +With Swift's ongoing work towards asynchronous functions, actors, and tasks, tracing in Swift will become more pleasant than it is today. + +Firstly, a lot of the callback heavy code will be folded into normal control flow, which is easy and correct to integrate with tracing like this: + +```swift +func perform(context: LoggingContext) async -> String { + let span = InstrumentationSystem.tracer.startSpan(operationName: #function, context: context) + defer { span.end() } + + return await someWork() +} +``` + +## Topics + +### Articles + +- diff --git a/Sources/Tracing/InstrumentationSystem+Tracing.swift b/Sources/Tracing/InstrumentationSystem+Tracing.swift index 1052c1d..1be4949 100644 --- a/Sources/Tracing/InstrumentationSystem+Tracing.swift +++ b/Sources/Tracing/InstrumentationSystem+Tracing.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -15,12 +15,12 @@ @_exported import Instrumentation extension InstrumentationSystem { - /// Returns the `Tracer` bootstrapped as part of the `InstrumentationSystem`. + /// Returns the ``Tracer`` bootstrapped as part of the `InstrumentationSystem`. /// /// If the system was bootstrapped with a `MultiplexInstrument` this function attempts to locate the _first_ - /// tracing instrument as passed to the multiplex instrument. If none is found, a `NoOpTracer` is returned. + /// tracing instrument as passed to the multiplex instrument. If none is found, a ``NoOpTracer`` is returned. /// - /// - Returns: A `Tracer` if the system was bootstrapped with one, and `NoOpTracer` otherwise. + /// - Returns: A ``Tracer`` if the system was bootstrapped with one, and ``NoOpTracer`` otherwise. public static var tracer: Tracer { (self._findInstrument(where: { $0 is Tracer }) as? Tracer) ?? NoOpTracer() } diff --git a/Sources/Tracing/NoOpTracer.swift b/Sources/Tracing/NoOpTracer.swift index f5bd716..a851094 100644 --- a/Sources/Tracing/NoOpTracer.swift +++ b/Sources/Tracing/NoOpTracer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -16,7 +16,7 @@ import Dispatch @_exported import Instrumentation @_exported import InstrumentationBaggage -/// No operation Tracer, used when no tracing is required. +/// No operation ``Tracer``, used when no tracing is required. public struct NoOpTracer: Tracer { public init() {} diff --git a/Sources/Tracing/Span.swift b/Sources/Tracing/Span.swift index d636fc4..5316b22 100644 --- a/Sources/Tracing/Span.swift +++ b/Sources/Tracing/Span.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -19,7 +19,7 @@ import Dispatch /// with it. A `Span` can be created from a `Baggage` or `LoggingContext` which MAY contain existing span identifiers, /// in which case this span should be considered as "child" of the previous span. /// -/// Creating a `Span` is delegated to a `Tracer` and end users should never create them directly. +/// Creating a `Span` is delegated to a ``Tracer`` and end users should never create them directly. /// /// - SeeAlso: For more details refer to the [OpenTelemetry Specification: Span](https://github.com/open-telemetry/opentelemetry-specification/blob/v0.7.0/specification/trace/api.md#span) which this type is compatible with. public protocol Span: AnyObject { @@ -30,8 +30,8 @@ public protocol Span: AnyObject { /// - Parameter status: The status of this `Span`. func setStatus(_ status: SpanStatus) - /// Add a `SpanEvent` in place. - /// - Parameter event: The `SpanEvent` to add to this `Span`. + /// Add a ``SpanEvent`` in place. + /// - Parameter event: The ``SpanEvent`` to add to this `Span`. func addEvent(_ event: SpanEvent) /// Record an error of the given type described by the the given message. @@ -46,7 +46,7 @@ public protocol Span: AnyObject { /// Returns true if this `Span` is recording information like events, attributes, status, etc. var isRecording: Bool { get } - /// Add a `SpanLink` in place. + /// Add a ``SpanLink`` in place. /// - Parameter link: The `SpanLink` to add to this `Span`. func addLink(_ link: SpanLink) @@ -79,15 +79,15 @@ extension Span { /// /// - Parameter time: The `DispatchWallTime` at which the span ended. /// - /// - SeeAlso: `Span.end(at:)` which allows passing in a specific time, e.g. if the operation was ended and recorded somewhere and we need to post-factum record it. - /// Generally though prefer using the `end()` version of this API in user code and structure your system such that it can be called in the right place and time. + /// - SeeAlso: ``end(at:)`` which allows passing in a specific time, e.g. if the operation was ended and recorded somewhere and we need to post-factum record it. + /// Generally though prefer using the ``end()`` version of this API in user code and structure your system such that it can be called in the right place and time. public func end() { self.end(at: .now()) } - /// Adds a `SpanLink` between this `Span` and the given `Span`. + /// Adds a ``SpanLink`` between this `Span` and the given `Span`. /// - Parameter other: The `Span` to link to. - /// - Parameter attributes: The `SpanAttributes` describing this link. Defaults to no attributes. + /// - Parameter attributes: The ``SpanAttributes`` describing this link. Defaults to no attributes. public func addLink(_ other: Span, attributes: SpanAttributes = [:]) { self.addLink(SpanLink(baggage: other.baggage, attributes: attributes)) } @@ -96,12 +96,12 @@ extension Span { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Span Event -/// An event that occurred during a `Span`. +/// An event that occurred during a ``Span``. public struct SpanEvent: Equatable { /// The human-readable name of this `SpanEvent`. public let name: String - /// One or more `SpanAttribute`s with the same restrictions as defined for `Span` attributes. + /// One or more ``SpanAttribute``s with the same restrictions as defined for ``Span`` attributes. public var attributes: SpanAttributes /// The `DispatchWallTime` at which this event occurred. @@ -213,7 +213,7 @@ extension SpanAttributeNamespace { } #endif -/// The value of an attribute used to describe a `Span` or `SpanEvent`. +/// The value of an attribute used to describe a ``Span`` or ``SpanEvent``. /// /// Arrays are allowed but are enforced to be homogenous. /// @@ -491,7 +491,7 @@ extension SpanAttribute: ExpressibleByBooleanLiteral { // MARK: SpanAttributes: Namespaces #if swift(>=5.2) -/// A container of `SpanAttribute`s. +/// A container of ``SpanAttribute``s. @dynamicMemberLookup public struct SpanAttributes: Equatable { private var _attributes = [String: SpanAttribute]() @@ -505,12 +505,13 @@ public struct SpanAttributes: Equatable { extension SpanAttributes { /// Create a set of attributes by wrapping the given dictionary. + /// /// - Parameter attributes: The attributes dictionary to wrap. public init(_ attributes: [String: SpanAttribute]) { self._attributes = attributes } - /// Accesses the `SpanAttribute` with the given name for reading and writing. + /// Accesses the ``SpanAttribute`` with the given name for reading and writing. /// /// Please be cautious to not abuse this APIs power to read attributes to "smuggle" values between calls. /// Only `Baggage` is intended to carry information in a readable fashion between functions / processes / nodes. @@ -595,14 +596,14 @@ public struct SpanStatus: Equatable { /// Create a new `SpanStatus`. /// /// - Parameters: - /// - code: The `Code` of this `SpanStatus`. + /// - code: The ``SpanStatus/Code-swift.enum`` of this `SpanStatus`. /// - message: The optional descriptive message of this `SpanStatus`. Defaults to nil. public init(code: Code, message: String? = nil) { self.code = code self.message = message } - /// A code representing the status of a `Span`. + /// A code representing the status of a ``Span``. /// /// - SeeAlso: For the semantics of status codes see [OpenTelemetry Specification: setStatus](https://github.com/open-telemetry/opentelemetry-specification/blob/v0.7.0/specification/trace/api.md#set-status) public enum Code { @@ -616,7 +617,7 @@ public struct SpanStatus: Equatable { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Span Kind -/// Describes the relationship between the Span, its parents, and its children in a Trace. +/// Describes the relationship between the ``Span``, its parents, and its children in a Trace. public enum SpanKind { /// Indicates that the span covers server-side handling of a synchronous RPC or other remote request. /// This span is the child of a remote `.client` span that was expected to wait for a response. @@ -637,20 +638,20 @@ public enum SpanKind { // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Span Link -/// A link to another `Span`. -/// The other `Span`s information is stored in `context` and `attributes` may be used to +/// A link to another ``Span``. +/// The other ``Span``s information is stored in `context` and `attributes` may be used to /// further describe the link. public struct SpanLink { - /// A `Baggage` containing identifying information about the link target `Span`. + /// A `Baggage` containing identifying information about the link target ``Span``. public let baggage: Baggage - /// `SpanAttributes` further describing the connection between the `Span`s. + /// ``SpanAttributes`` further describing the connection between the ``Span``s. public let attributes: SpanAttributes /// Create a new `SpanLink`. /// - Parameters: - /// - context: The `Baggage` identifying the targeted `Span`. - /// - attributes: `SpanAttributes` that further describe the link. Defaults to no attributes. + /// - context: The `Baggage` identifying the targeted ``Span``. + /// - attributes: ``SpanAttributes`` that further describe the link. Defaults to no attributes. public init(baggage: Baggage, attributes: SpanAttributes = [:]) { self.baggage = baggage self.attributes = attributes diff --git a/Sources/Tracing/Tracer.swift b/Sources/Tracing/Tracer.swift index a4bfbe0..241dda1 100644 --- a/Sources/Tracing/Tracer.swift +++ b/Sources/Tracing/Tracer.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift Distributed Tracing open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +// Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project // authors // Licensed under Apache License v2.0 // @@ -16,10 +16,10 @@ import Dispatch @_exported import Instrumentation @_exported import InstrumentationBaggage -/// An `Instrument` with added functionality for distributed tracing. Is uses the span-based tracing model and is +/// An `Instrument` with added functionality for distributed tracing. It uses the span-based tracing model and is /// based on the OpenTracing/OpenTelemetry spec. public protocol Tracer: Instrument { - /// Start a new `Span` with the given `Baggage` at a given time. + /// Start a new ``Span`` with the given `Baggage` at a given time. /// /// - Note: Prefer to use `withSpan` to start a span as it automatically takes care of ending the span, /// and recording errors when thrown. Use `startSpan` iff you need to pass the span manually to a different @@ -27,9 +27,9 @@ public protocol Tracer: Instrument { /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... - /// - baggage: The `Baggage` providing information on where to start the new `Span`. - /// - kind: The `SpanKind` of the new `Span`. - /// - time: The `DispatchTime` at which to start the new `Span`. + /// - baggage: The `Baggage` providing information on where to start the new ``Span``. + /// - kind: The ``SpanKind`` of the new ``Span``. + /// - time: The `DispatchTime` at which to start the new ``Span``. func startSpan( _ operationName: String, baggage: Baggage, @@ -47,12 +47,12 @@ public protocol Tracer: Instrument { } extension Tracer { - /// Start a new `Span` with the given `Baggage` starting at `DispatchWallTime.now()`. + /// Start a new ``Span`` with the given `Baggage` starting at `DispatchWallTime.now()`. /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... - /// - baggage: Baggage potentially containing trace identifiers of a parent `Span`. - /// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`. + /// - baggage: Baggage potentially containing trace identifiers of a parent ``Span``. + /// - kind: The ``SpanKind`` of the ``Span`` to be created. Defaults to ``SpanKind/internal``. public func startSpan( _ operationName: String, baggage: Baggage, @@ -66,14 +66,14 @@ extension Tracer { // MARK: Starting spans: `withSpan` extension Tracer { - /// Execute a specific task within a newly created `Span`. + /// Execute a specific task within a newly created ``Span``. /// /// DO NOT `end()` the passed in span manually. It will be ended automatically when the `operation` returns. /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... - /// - baggage: Baggage potentially containing trace identifiers of a parent `Span`. - /// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`. + /// - baggage: Baggage potentially containing trace identifiers of a parent ``Span``. + /// - kind: The ``SpanKind`` of the ``Span`` to be created. Defaults to ``SpanKind/internal``. /// - operation: operation to wrap in a span start/end and execute immediately /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) @@ -100,14 +100,14 @@ extension Tracer { #if swift(>=5.5) && canImport(_Concurrency) @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Tracer { - /// Execute the given operation within a newly created `Span`, + /// Execute the given operation within a newly created ``Span``, /// started as a child of the currently stored task local `Baggage.current` or as a root span if `nil`. /// /// DO NOT `end()` the passed in span manually. It will be ended automatically when the `operation` returns. /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... - /// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`. + /// - kind: The ``SpanKind`` of the ``Span`` to be created. Defaults to ``SpanKind/internal``. /// - operation: operation to wrap in a span start/end and execute immediately /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) @@ -123,14 +123,14 @@ extension Tracer { } } - /// Execute the given async operation within a newly created `Span`, + /// Execute the given async operation within a newly created ``Span``, /// started as a child of the currently stored task local `Baggage.current` or as a root span if `nil`. /// /// DO NOT `end()` the passed in span manually. It will be ended automatically when the `operation` returns. /// /// - Parameters: /// - operationName: The name of the operation being traced. This may be a handler function, database call, ... - /// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`. + /// - kind: The ``SpanKind`` of the ``Span`` to be created. Defaults to ``SpanKind/internal``. /// - operation: operation to wrap in a span start/end and execute immediately /// - Returns: the value returned by `operation` /// - Throws: the error the `operation` has thrown (if any) diff --git a/scripts/preview_docc.sh b/scripts/preview_docc.sh new file mode 100755 index 0000000..eca74c6 --- /dev/null +++ b/scripts/preview_docc.sh @@ -0,0 +1,30 @@ +#!/bin/bash +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift Distributed Tracing open source project +## +## Copyright (c) 2022 Apple Inc. and the Swift Distributed Tracing project +## authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift Distributed Actors open source project +## +## Copyright (c) 2018-2019 Apple Inc. and the Swift Distributed Actors project authors +## Licensed under Apache License v2.0 +## +## See LICENSE.txt for license information +## See CONTRIBUTORS.md for the list of Swift Distributed Actors project authors +## +## SPDX-License-Identifier: Apache-2.0 +## +##===----------------------------------------------------------------------===## + +swift package --disable-sandbox preview-documentation --target $1 diff --git a/scripts/validate_license_headers.sh b/scripts/validate_license_headers.sh index 4d7f031..a75e249 100755 --- a/scripts/validate_license_headers.sh +++ b/scripts/validate_license_headers.sh @@ -33,6 +33,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function replace_acceptable_years() { # this needs to replace all acceptable forms with 'YEARS' sed -e 's/2020-2021/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/' + sed -e 's/202[01]-202[12]/YEARS/' -e 's/202[012]/YEARS/' } printf "=> Checking license headers\n" @@ -46,7 +47,7 @@ for language in swift-or-c bash dtrace; do matching_files=( -name '*' ) case "$language" in swift-or-c) - exceptions=( -name c_nio_http_parser.c -o -name c_nio_http_parser.h -o -name cpp_magic.h -o -name Package.swift -o -name CNIOSHA1.h -o -name c_nio_sha1.c -o -name ifaddrs-android.c -o -name ifaddrs-android.h) + exceptions=( -name Package.swift -o -name 'Package@*.swift' ) matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) cat > "$tmp" <<"EOF" //===----------------------------------------------------------------------===// From db3a1ee6187977eb2e2365ef002373834961931a Mon Sep 17 00:00:00 2001 From: Yim Lee Date: Tue, 9 Aug 2022 23:33:51 -0700 Subject: [PATCH 2/5] Fix soundness script --- scripts/validate_license_headers.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/validate_license_headers.sh b/scripts/validate_license_headers.sh index a75e249..399902f 100755 --- a/scripts/validate_license_headers.sh +++ b/scripts/validate_license_headers.sh @@ -3,7 +3,7 @@ ## ## This source file is part of the Swift Distributed Tracing open source project ## -## Copyright (c) 2020-2021 Apple Inc. and the Swift Distributed Tracing project +## Copyright (c) 2020-2022 Apple Inc. and the Swift Distributed Tracing project ## authors ## Licensed under Apache License v2.0 ## @@ -32,7 +32,6 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" function replace_acceptable_years() { # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/2020-2021/YEARS/' -e 's/2020/YEARS/' -e 's/2021/YEARS/' sed -e 's/202[01]-202[12]/YEARS/' -e 's/202[012]/YEARS/' } From 503899f6427697e44346bad01b537e3dbbd5c03f Mon Sep 17 00:00:00 2001 From: Yim Lee Date: Tue, 9 Aug 2022 23:37:05 -0700 Subject: [PATCH 3/5] Workaround LinuxMain error in newer Swift versions --- docker/docker-compose.2004.56.yaml | 4 ++-- docker/docker-compose.2004.57.yaml | 4 ++-- docker/docker-compose.2004.main.yaml | 4 ++-- docker/docker-compose.yaml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/docker-compose.2004.56.yaml b/docker/docker-compose.2004.56.yaml index eac45f1..e9322cb 100644 --- a/docker/docker-compose.2004.56.yaml +++ b/docker/docker-compose.2004.56.yaml @@ -11,8 +11,8 @@ services: test: image: swift-distributed-tracing:20.04-5.6 - environment: [] - #- SANITIZER_ARG=--sanitize=thread + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery shell: image: swift-distributed-tracing:20.04-5.6 diff --git a/docker/docker-compose.2004.57.yaml b/docker/docker-compose.2004.57.yaml index 26bea45..dcd1f85 100644 --- a/docker/docker-compose.2004.57.yaml +++ b/docker/docker-compose.2004.57.yaml @@ -10,8 +10,8 @@ services: test: image: swift-distributed-tracing:20.04-5.7 - environment: [] - #- SANITIZER_ARG=--sanitize=thread + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery shell: image: swift-distributed-tracing:20.04-5.7 diff --git a/docker/docker-compose.2004.main.yaml b/docker/docker-compose.2004.main.yaml index e85334f..4ba3202 100644 --- a/docker/docker-compose.2004.main.yaml +++ b/docker/docker-compose.2004.main.yaml @@ -10,8 +10,8 @@ services: test: image: swift-distributed-tracing:20.04-main - environment: [] - #- SANITIZER_ARG=--sanitize=thread + environment: + - FORCE_TEST_DISCOVERY=--enable-test-discovery shell: image: swift-distributed-tracing:20.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 2a090c3..d9d26ad 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -28,7 +28,7 @@ services: test: <<: *common - command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" + command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors $${FORCE_TEST_DISCOVERY-} $${SANITIZER_ARG-}" # util From 5cf30fda4d4dcf00964ed84cd48d11df3e37445e Mon Sep 17 00:00:00 2001 From: Yim Lee Date: Wed, 10 Aug 2022 12:31:48 -0700 Subject: [PATCH 4/5] Update Sources/Tracing/Docs.docc/InDepthGuide.md Co-authored-by: YR Chen --- Sources/Tracing/Docs.docc/InDepthGuide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Tracing/Docs.docc/InDepthGuide.md b/Sources/Tracing/Docs.docc/InDepthGuide.md index c85ce54..e78d559 100644 --- a/Sources/Tracing/Docs.docc/InDepthGuide.md +++ b/Sources/Tracing/Docs.docc/InDepthGuide.md @@ -150,7 +150,7 @@ When adapting an existing library/framework to support `LoggingContext` and it a Examples: -- `Lamda.Context` may contain `baggage` and a `logger` and should be able to conform to `LoggingContext` +- `Lambda.Context` may contain `baggage` and a `logger` and should be able to conform to `LoggingContext` - passing context to a `Lambda.Context` unaware library becomes: `http.request(url: "...", context: context)`. - `ChannelHandlerContext` offers a way to set/get baggage on the underlying channel via `context.baggage = ...` - this context is not passed outside a handler, but within it may be passed as is, and the baggage may be accessed on it directly through it. From 141eca77713dc540ebaeb1a9dea795b20724dc3e Mon Sep 17 00:00:00 2001 From: Yim Lee Date: Wed, 10 Aug 2022 13:39:35 -0700 Subject: [PATCH 5/5] Don't link to specific lines of code --- README.md | 6 +++--- Sources/Tracing/Docs.docc/InDepthGuide.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index faf042e..af37f76 100644 --- a/README.md +++ b/README.md @@ -402,7 +402,7 @@ When adapting an existing library/framework to support `LoggingContext` and it a Examples: -- `Lamda.Context` may contain `baggage` and a `logger` and should be able to conform to `LoggingContext` +- `Lambda.Context` may contain `baggage` and a `logger` and should be able to conform to `LoggingContext` - passing context to a `Lambda.Context` unaware library becomes: `http.request(url: "...", context: context)`. - `ChannelHandlerContext` offers a way to set/get baggage on the underlying channel via `context.baggage = ...` - this context is not passed outside a handler, but within it may be passed as is, and the baggage may be accessed on it directly through it. @@ -414,8 +414,8 @@ Generally application developers _should not_ create new context objects, but ra If really necessary, or for the purposes of testing, one can create a baggage or context using one of the two factory functions: -- [`DefaultLoggingContext.topLevel(logger:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L232-L259) or [`Baggage.topLevel`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L79-L103) - which creates an empty context/baggage, without any values. It should _not_ be used too frequently, and as the name implies in applications it only should be used on the "top level" of the application, or at the beginning of a contextless (e.g. timer triggered) event processing. -- [`DefaultLoggingContext.TODO(logger:reason:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L262-L292) or [`Baggage.TODO`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L107-L136) - which should be used to mark a parameter where "before this code goes into production, a real context should be passed instead." An application can be run with `-DBAGGAGE_CRASH_TODOS` to cause the application to crash whenever a TODO context is still in use somewhere, making it easy to diagnose and avoid breaking context propagation by accidentally leaving in a `TODO` context in production. +- [`DefaultLoggingContext.topLevel(logger:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift) or [`Baggage.topLevel`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift) - which creates an empty context/baggage, without any values. It should _not_ be used too frequently, and as the name implies in applications it only should be used on the "top level" of the application, or at the beginning of a contextless (e.g. timer triggered) event processing. +- [`DefaultLoggingContext.TODO(logger:reason:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift) or [`Baggage.TODO`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift) - which should be used to mark a parameter where "before this code goes into production, a real context should be passed instead." An application can be run with `-DBAGGAGE_CRASH_TODOS` to cause the application to crash whenever a TODO context is still in use somewhere, making it easy to diagnose and avoid breaking context propagation by accidentally leaving in a `TODO` context in production. Please refer to the respective functions documentation for details. diff --git a/Sources/Tracing/Docs.docc/InDepthGuide.md b/Sources/Tracing/Docs.docc/InDepthGuide.md index e78d559..052f9f2 100644 --- a/Sources/Tracing/Docs.docc/InDepthGuide.md +++ b/Sources/Tracing/Docs.docc/InDepthGuide.md @@ -162,8 +162,8 @@ Generally application developers _should not_ create new context objects, but ra If really necessary, or for the purposes of testing, one can create a baggage or context using one of the two factory functions: -- [`DefaultLoggingContext.topLevel(logger:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L232-L259) or [`Baggage.topLevel`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L79-L103) - which creates an empty context/baggage, without any values. It should _not_ be used too frequently, and as the name implies in applications it only should be used on the "top level" of the application, or at the beginning of a contextless (e.g. timer triggered) event processing. -- [`DefaultLoggingContext.TODO(logger:reason:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift#L262-L292) or [`Baggage.TODO`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift#L107-L136) - which should be used to mark a parameter where "before this code goes into production, a real context should be passed instead." An application can be run with `-DBAGGAGE_CRASH_TODOS` to cause the application to crash whenever a TODO context is still in use somewhere, making it easy to diagnose and avoid breaking context propagation by accidentally leaving in a `TODO` context in production. +- [`DefaultLoggingContext.topLevel(logger:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift) or [`Baggage.topLevel`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift) - which creates an empty context/baggage, without any values. It should _not_ be used too frequently, and as the name implies in applications it only should be used on the "top level" of the application, or at the beginning of a contextless (e.g. timer triggered) event processing. +- [`DefaultLoggingContext.TODO(logger:reason:)`](https://github.com/apple/swift-distributed-tracing-baggage/blob/main/Sources/Baggage/LoggingContext.swift) or [`Baggage.TODO`](https://github.com/apple/swift-distributed-tracing-baggage-core/blob/main/Sources/CoreBaggage/Baggage.swift) - which should be used to mark a parameter where "before this code goes into production, a real context should be passed instead." An application can be run with `-DBAGGAGE_CRASH_TODOS` to cause the application to crash whenever a TODO context is still in use somewhere, making it easy to diagnose and avoid breaking context propagation by accidentally leaving in a `TODO` context in production. Please refer to the respective functions documentation for details.