Skip to content

associated type Span, Tracer as short-hand, and *Protocol types #93

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 15, 2023
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "swift-distributed-tracing",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(name: "Instrumentation", targets: ["Instrumentation"]),
.library(name: "Tracing", targets: ["Tracing"]),
Expand Down
6 changes: 6 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "swift-distributed-tracing",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(name: "Instrumentation", targets: ["Instrumentation"]),
.library(name: "Tracing", targets: ["Tracing"]),
Expand Down
6 changes: 6 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "swift-distributed-tracing",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(name: "Instrumentation", targets: ["Instrumentation"]),
.library(name: "Tracing", targets: ["Tracing"]),
Expand Down
6 changes: 6 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "swift-distributed-tracing",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(name: "Instrumentation", targets: ["Instrumentation"]),
.library(name: "Tracing", targets: ["Tracing"]),
Expand Down
6 changes: 6 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import PackageDescription

let package = Package(
name: "swift-distributed-tracing",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(name: "Instrumentation", targets: ["Instrumentation"]),
.library(name: "Tracing", targets: ["Tracing"]),
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ This project uses the context progagation type defined independently in:
+ [Instrumenting your software](#library-framework-developers--instrumenting-your-software)
+ [Extracting & injecting Baggage](#extracting--injecting-baggage)
+ [Tracing your library](#tracing-your-library)
* In-Depth Guide for: **Instrument developers**
+ [Creating an `Instrument`](#instrument-developers--creating-an-instrument)
* In-Depth Guide for: **InstrumentProtocol developers**
+ [Creating an `InstrumentProtocol`](#instrument-developers--creating-an-instrument)
+ [Creating a `Tracer`](#creating-a--tracer-)
* [Contributing](#contributing)

Expand Down Expand Up @@ -119,7 +119,7 @@ To your main target, add a dependency on `Tracing` library and the instrument yo
),
```

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:
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 `InstrumentProtocol` and can be offered used to globally bootstrap the tracing system, like this:


```swift
Expand Down Expand Up @@ -261,7 +261,7 @@ When instrumenting server applications there are typically three parties involve

1. [Application developers](#application-developers-setting-up-instruments) creating server-side applications
2. [Library/Framework developers](#libraryframework-developers-instrumenting-your-software) providing building blocks to create these applications
3. [Instrument developers](#instrument-developers-creating-an-instrument) providing tools to collect distributed metadata about your application
3. [InstrumentProtocol developers](#instrument-developers-creating-an-instrument) providing tools to collect distributed metadata about your application
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, a batch rename mistake?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks yeah will clean it up


For applications to be instrumented correctly these three parts have to play along nicely.

Expand Down Expand Up @@ -295,7 +295,7 @@ To your main target, add a dependency on the `Instrumentation library` and the i

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`:
`InstrumentProtocol`:

```swift
InstrumentationSystem.bootstrap(FancyInstrument())
Expand All @@ -316,7 +316,7 @@ This is because tracing systems may attempt to emit metrics about their status e

#### Bootstrapping multiple instruments using MultiplexInstrument

It is important to note that `InstrumentationSystem.bootstrap(_: Instrument)` must only be called once. In case you
It is important to note that `InstrumentationSystem.bootstrap(_: InstrumentProtocol)` 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:

Expand Down Expand Up @@ -444,7 +444,7 @@ Spans form hierarchies with their parent spans, and end up being visualized usin
The above trace is achieved by starting and ending spans in all the mentioned functions, for example, like this:

```swift
let tracer: Tracer
let tracer: any TracerProtocol

func makeDinner(context: LoggingContext) async throws -> Meal {
tracer.withSpan(operationName: "makeDinner", context) {
Expand Down Expand Up @@ -481,7 +481,7 @@ func get(url: String, context: LoggingContext) {
}
```

On the receiving side, an HTTP server should use the following `Instrument` API to extract the HTTP headers of the given
On the receiving side, an HTTP server should use the following `InstrumentProtocol` API to extract the HTTP headers of the given
`HTTPRequest` into:

```swift
Expand Down Expand Up @@ -536,12 +536,12 @@ func get(url: String, context: LoggingContext) {
> In the above example we used the semantic `http.method` attribute that gets exposed via the
`TracingOpenTelemetrySupport` library.

## Instrument developers: Creating an instrument
## InstrumentProtocol 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.
Creating an instrument means adopting the `InstrumentProtocol` protocol (or `Tracer` in case you develop a tracer).
`InstrumentProtocol` is part of the `Instrumentation` library & `Tracing` contains the `Tracer` protocol.

`Instrument` has two requirements:
`InstrumentProtocol` 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`
Expand Down
4 changes: 2 additions & 2 deletions Sources/Instrumentation/Instrument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public protocol Injector: _SwiftInstrumentationSendable {

/// 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: _SwiftInstrumentationSendable {
public protocol InstrumentProtocol: _SwiftInstrumentationSendable {
/// 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`.
/// It's quite common for `InstrumentProtocol`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.
Expand Down
26 changes: 14 additions & 12 deletions Sources/Instrumentation/InstrumentationSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@
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 ``InstrumentProtocol`` implementation.
///
/// # Bootstrap multiple Instruments
/// If you need to use more that one cross-cutting tool you can do so by using ``MultiplexInstrument``.
///
/// # Access the Instrument
/// ``instrument``: Returns whatever you passed to ``bootstrap(_:)`` as an ``Instrument``.
/// # Access the InstrumentProtocol
/// ``instrument``: Returns whatever you passed to ``bootstrap(_:)`` as an ``InstrumentProtocol``.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage
public enum InstrumentationSystem {
private static let lock = ReadWriteLock()
private static var _instrument: Instrument = NoOpInstrument()
private static var _instrument: InstrumentProtocol = NoOpInstrument()
private static var isInitialized = false

/// Globally select the desired ``Instrument`` implementation.
/// Globally select the desired ``InstrumentProtocol`` implementation.
///
/// - Parameter instrument: The ``Instrument`` you want to share globally within your system.
/// - Parameter instrument: The ``InstrumentProtocol`` 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) {
public static func bootstrap(_ instrument: InstrumentProtocol) {
self.lock.withWriterLock {
precondition(
!self.isInitialized, """
Expand All @@ -47,23 +48,24 @@ public enum InstrumentationSystem {
/// 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.
internal static func bootstrapInternal(_ instrument: Instrument?) {
internal static func bootstrapInternal(_ instrument: InstrumentProtocol?) {
self.lock.withWriterLock {
self._instrument = instrument ?? NoOpInstrument()
}
}

/// Returns the globally configured ``Instrument``.
/// Returns the globally configured ``InstrumentProtocol``.
///
/// Defaults to a no-op ``Instrument`` if ``bootstrap(_:)`` wasn't called before.
public static var instrument: Instrument {
/// Defaults to a no-op ``InstrumentProtocol`` if ``bootstrap(_:)`` wasn't called before.
public static var instrument: InstrumentProtocol {
self.lock.withReaderLock { self._instrument }
}
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal Baggage
extension InstrumentationSystem {
/// :nodoc: INTERNAL API: Do Not Use
public static func _findInstrument(where predicate: (Instrument) -> Bool) -> Instrument? {
public static func _findInstrument(where predicate: (InstrumentProtocol) -> Bool) -> InstrumentProtocol? {
self.lock.withReaderLock {
if let multiplex = self._instrument as? MultiplexInstrument {
return multiplex.firstInstrument(where: predicate)
Expand Down
12 changes: 6 additions & 6 deletions Sources/Instrumentation/MultiplexInstrument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,27 @@

import InstrumentationBaggage

/// A pseudo-``Instrument`` that may be used to instrument using multiple other ``Instrument``s across a
/// A pseudo-``InstrumentProtocol`` that may be used to instrument using multiple other ``InstrumentProtocol``s across a
/// common `Baggage`.
public struct MultiplexInstrument {
private var instruments: [Instrument]
private var instruments: [InstrumentProtocol]

/// Create a ``MultiplexInstrument``.
///
/// - Parameter instruments: An array of ``Instrument``s, each of which will be used to ``Instrument/inject(_:into:using:)`` or ``Instrument/extract(_:into:using:)``
/// - Parameter instruments: An array of ``InstrumentProtocol``s, each of which will be used to ``InstrumentProtocol/inject(_:into:using:)`` or ``InstrumentProtocol/extract(_:into:using:)``
/// through the same `Baggage`.
public init(_ instruments: [Instrument]) {
public init(_ instruments: [InstrumentProtocol]) {
self.instruments = instruments
}
}

extension MultiplexInstrument {
func firstInstrument(where predicate: (Instrument) -> Bool) -> Instrument? {
func firstInstrument(where predicate: (InstrumentProtocol) -> Bool) -> InstrumentProtocol? {
self.instruments.first(where: predicate)
}
}

extension MultiplexInstrument: Instrument {
extension MultiplexInstrument: InstrumentProtocol {
public func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject)
where Inject: Injector, Carrier == Inject.Carrier
{
Expand Down
4 changes: 2 additions & 2 deletions Sources/Instrumentation/NoOpInstrument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

import InstrumentationBaggage

/// A "no op" implementation of an ``Instrument``.
public struct NoOpInstrument: Instrument {
/// A "no op" implementation of an ``InstrumentProtocol``.
public struct NoOpInstrument: InstrumentProtocol {
public init() {}

public func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject)
Expand Down
18 changes: 9 additions & 9 deletions Sources/Tracing/Docs.docc/InDepthGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ When instrumenting server applications there are typically three parties involve

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
3. **InstrumentProtocol developers** provide tools to collect distributed metadata about your application

For applications to be instrumented correctly these three parts have to play along nicely.

Expand Down Expand Up @@ -42,7 +42,7 @@ To your main target, add a dependency on the `Instrumentation library` and the i

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`:
`InstrumentProtocol`:

```swift
InstrumentationSystem.bootstrap(FancyInstrument())
Expand All @@ -63,7 +63,7 @@ This is because tracing systems may attempt to emit metrics about their status e

#### Bootstrapping multiple instruments using MultiplexInstrument

It is important to note that `InstrumentationSystem.bootstrap(_: Instrument)` must only be called once. In case you
It is important to note that `InstrumentationSystem.bootstrap(_: InstrumentProtocol)` 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:

Expand Down Expand Up @@ -188,7 +188,7 @@ Spans form hierarchies with their parent spans, and end up being visualized usin
The above trace is achieved by starting and ending spans in all the mentioned functions, for example, like this:

```swift
let tracer: Tracer
let tracer: any TracerProtocol

func makeDinner(context: LoggingContext) async throws -> Meal {
tracer.withSpan(operationName: "makeDinner", context) {
Expand Down Expand Up @@ -225,7 +225,7 @@ func get(url: String, context: LoggingContext) {
}
```

On the receiving side, an HTTP server should use the following `Instrument` API to extract the HTTP headers of the given
On the receiving side, an HTTP server should use the following `InstrumentProtocol` API to extract the HTTP headers of the given
`HTTPRequest` into:

```swift
Expand Down Expand Up @@ -280,12 +280,12 @@ func get(url: String, context: LoggingContext) {
> In the above example we used the semantic `http.method` attribute that gets exposed via the
`TracingOpenTelemetrySupport` library.

## Instrument developers: Creating an instrument
## InstrumentProtocol 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.
Creating an instrument means adopting the `InstrumentProtocol` protocol (or ``Tracer`` in case you develop a tracer).
`InstrumentProtocol` is part of the `Instrumentation` library & `Tracing` contains the ``Tracer`` protocol.

`Instrument` has two requirements:
`InstrumentProtocol` 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`
Expand Down
2 changes: 1 addition & 1 deletion Sources/Tracing/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ To your main target, add a dependency on the `Tracing` library and the instrumen
),
```

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:
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 `InstrumentProtocol` and can be offered used to globally bootstrap the tracing system, like this:


```swift
Expand Down
20 changes: 18 additions & 2 deletions Sources/Tracing/InstrumentationSystem+Tracing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,29 @@
@_exported import Instrumentation

extension InstrumentationSystem {
#if swift(>=5.7.0)
/// 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.
///
/// - 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()
public static var tracer: any TracerProtocol {
let found: (any TracerProtocol)? =
(self._findInstrument(where: { $0 is (any TracerProtocol) }) as? (any TracerProtocol))
return found ?? NoOpTracer()
}
#endif

/// 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.
///
/// - Returns: A ``Tracer`` if the system was bootstrapped with one, and ``NoOpTracer`` otherwise.
public static var legacyTracer: any LegacyTracerProtocol {
let found: (any LegacyTracerProtocol)? =
(self._findInstrument(where: { $0 is (any LegacyTracerProtocol) }) as? (any LegacyTracerProtocol))
return found ?? NoOpTracer()
}
}
Loading