Skip to content

Commit e02ed13

Browse files
committed
associated type Span, Tracer as short-hand, and *Protocol types
**Motivation:** This is a revival of #84 where we try to KEEP compatibility with versions below 5.7 with a compatibility "legacy" tracer type, but otherwise move towards requiring 5.7 for all the "nice" apis that use associated types and `any TracerProtocol` and friends **Modifications:** - `Tracer` -> `TracerProtocol` - `Tracer` is now a namespace in order to `Tracer.withSpan {}` easily - `Span` -> `SpanProtocol` - Introduce `LegacyTracerProtocol` which does not make use of associated type Span, and can be used in 5.6 libraries; they can deprecate and move away form it ASAP as they start requiring 5.7 **Result:** Offer the APIs we want in 5.7 but remain compatible with 5.6 until we drop it as soon as 5.9 is released as stable - this allows us to adopt eagerly in libraries without having to wait for 5.9 to drop.
1 parent d633b17 commit e02ed13

21 files changed

+373
-172
lines changed

Diff for: README.md

+12-12
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ This project uses the context progagation type defined independently in:
3939
+ [Instrumenting your software](#library-framework-developers--instrumenting-your-software)
4040
+ [Extracting & injecting Baggage](#extracting--injecting-baggage)
4141
+ [Tracing your library](#tracing-your-library)
42-
* In-Depth Guide for: **Instrument developers**
43-
+ [Creating an `Instrument`](#instrument-developers--creating-an-instrument)
42+
* In-Depth Guide for: **InstrumentProtocol developers**
43+
+ [Creating an `InstrumentProtocol`](#instrument-developers--creating-an-instrument)
4444
+ [Creating a `Tracer`](#creating-a--tracer-)
4545
* [Contributing](#contributing)
4646

@@ -119,7 +119,7 @@ To your main target, add a dependency on `Tracing` library and the instrument yo
119119
),
120120
```
121121

122-
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:
122+
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:
123123

124124

125125
```swift
@@ -261,7 +261,7 @@ When instrumenting server applications there are typically three parties involve
261261

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

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

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

296296
Instead of providing each instrumented library with a specific instrument explicitly, you *bootstrap* the
297297
`InstrumentationSystem` which acts as a singleton that libraries/frameworks access when calling out to the configured
298-
`Instrument`:
298+
`InstrumentProtocol`:
299299

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

317317
#### Bootstrapping multiple instruments using MultiplexInstrument
318318

319-
It is important to note that `InstrumentationSystem.bootstrap(_: Instrument)` must only be called once. In case you
319+
It is important to note that `InstrumentationSystem.bootstrap(_: InstrumentProtocol)` must only be called once. In case you
320320
want to bootstrap the system to use multiple instruments, you group them in a `MultiplexInstrument` first, which you
321321
then pass along to the `bootstrap` method like this:
322322

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

446446
```swift
447-
let tracer: Tracer
447+
let tracer: any TracerProtocol
448448

449449
func makeDinner(context: LoggingContext) async throws -> Meal {
450450
tracer.withSpan(operationName: "makeDinner", context) {
@@ -481,7 +481,7 @@ func get(url: String, context: LoggingContext) {
481481
}
482482
```
483483

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

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

539-
## Instrument developers: Creating an instrument
539+
## InstrumentProtocol developers: Creating an instrument
540540

541-
Creating an instrument means adopting the `Instrument` protocol (or `Tracer` in case you develop a tracer).
542-
`Instrument` is part of the `Instrumentation` library & `Tracing` contains the `Tracer` protocol.
541+
Creating an instrument means adopting the `InstrumentProtocol` protocol (or `Tracer` in case you develop a tracer).
542+
`InstrumentProtocol` is part of the `Instrumentation` library & `Tracing` contains the `Tracer` protocol.
543543

544-
`Instrument` has two requirements:
544+
`InstrumentProtocol` has two requirements:
545545

546546
1. A method to inject values inside a `LoggingContext` into a generic carrier (e.g. HTTP headers)
547547
2. A method to extract values from a generic carrier (e.g. HTTP headers) and store them in a `LoggingContext`

Diff for: Sources/Instrumentation/Instrument.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ public protocol Injector: _SwiftInstrumentationSendable {
5050

5151
/// Conforming types are usually cross-cutting tools like tracers. They are agnostic of what specific `Carrier` is used
5252
/// to propagate metadata across boundaries, but instead just specify what values to use for which keys.
53-
public protocol Instrument: _SwiftInstrumentationSendable {
53+
public protocol InstrumentProtocol: _SwiftInstrumentationSendable {
5454
/// Extract values from a `Carrier` by using the given extractor and inject them into the given `Baggage`.
55-
/// It's quite common for `Instrument`s to come up with new values if they weren't passed along in the given `Carrier`.
55+
/// It's quite common for `InstrumentProtocol`s to come up with new values if they weren't passed along in the given `Carrier`.
5656
///
5757
/// - Parameters:
5858
/// - carrier: The `Carrier` that was used to propagate values across boundaries.

Diff for: Sources/Instrumentation/InstrumentationSystem.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@
1515
import InstrumentationBaggage
1616

1717
/// `InstrumentationSystem` is a global facility where the default cross-cutting tool can be configured.
18-
/// It is set up just once in a given program to select the desired ``Instrument`` implementation.
18+
/// It is set up just once in a given program to select the desired ``InstrumentProtocol`` implementation.
1919
///
2020
/// # Bootstrap multiple Instruments
2121
/// If you need to use more that one cross-cutting tool you can do so by using ``MultiplexInstrument``.
2222
///
23-
/// # Access the Instrument
24-
/// ``instrument``: Returns whatever you passed to ``bootstrap(_:)`` as an ``Instrument``.
23+
/// # Access the InstrumentProtocol
24+
/// ``instrument``: Returns whatever you passed to ``bootstrap(_:)`` as an ``InstrumentProtocol``.
2525
public enum InstrumentationSystem {
2626
private static let lock = ReadWriteLock()
27-
private static var _instrument: Instrument = NoOpInstrument()
27+
private static var _instrument: InstrumentProtocol = NoOpInstrument()
2828
private static var isInitialized = false
2929

30-
/// Globally select the desired ``Instrument`` implementation.
30+
/// Globally select the desired ``InstrumentProtocol`` implementation.
3131
///
32-
/// - Parameter instrument: The ``Instrument`` you want to share globally within your system.
32+
/// - Parameter instrument: The ``InstrumentProtocol`` you want to share globally within your system.
3333
/// - Warning: Do not call this method more than once. This will lead to a crash.
34-
public static func bootstrap(_ instrument: Instrument) {
34+
public static func bootstrap(_ instrument: InstrumentProtocol) {
3535
self.lock.withWriterLock {
3636
precondition(
3737
!self.isInitialized, """
@@ -47,23 +47,23 @@ public enum InstrumentationSystem {
4747
/// For testing scenarios one may want to set instruments multiple times, rather than the set-once semantics enforced by ``bootstrap(_:)``.
4848
///
4949
/// - Parameter instrument: the instrument to boostrap the system with, if `nil` the ``NoOpInstrument`` is bootstrapped.
50-
internal static func bootstrapInternal(_ instrument: Instrument?) {
50+
internal static func bootstrapInternal(_ instrument: InstrumentProtocol?) {
5151
self.lock.withWriterLock {
5252
self._instrument = instrument ?? NoOpInstrument()
5353
}
5454
}
5555

56-
/// Returns the globally configured ``Instrument``.
56+
/// Returns the globally configured ``InstrumentProtocol``.
5757
///
58-
/// Defaults to a no-op ``Instrument`` if ``bootstrap(_:)`` wasn't called before.
59-
public static var instrument: Instrument {
58+
/// Defaults to a no-op ``InstrumentProtocol`` if ``bootstrap(_:)`` wasn't called before.
59+
public static var instrument: any InstrumentProtocol {
6060
self.lock.withReaderLock { self._instrument }
6161
}
6262
}
6363

6464
extension InstrumentationSystem {
6565
/// :nodoc: INTERNAL API: Do Not Use
66-
public static func _findInstrument(where predicate: (Instrument) -> Bool) -> Instrument? {
66+
public static func _findInstrument(where predicate: (InstrumentProtocol) -> Bool) -> InstrumentProtocol? {
6767
self.lock.withReaderLock {
6868
if let multiplex = self._instrument as? MultiplexInstrument {
6969
return multiplex.firstInstrument(where: predicate)

Diff for: Sources/Instrumentation/MultiplexInstrument.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,27 @@
1414

1515
import InstrumentationBaggage
1616

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

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

3131
extension MultiplexInstrument {
32-
func firstInstrument(where predicate: (Instrument) -> Bool) -> Instrument? {
32+
func firstInstrument(where predicate: (InstrumentProtocol) -> Bool) -> InstrumentProtocol? {
3333
self.instruments.first(where: predicate)
3434
}
3535
}
3636

37-
extension MultiplexInstrument: Instrument {
37+
extension MultiplexInstrument: InstrumentProtocol {
3838
public func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject)
3939
where Inject: Injector, Carrier == Inject.Carrier
4040
{

Diff for: Sources/Instrumentation/NoOpInstrument.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
import InstrumentationBaggage
1616

17-
/// A "no op" implementation of an ``Instrument``.
18-
public struct NoOpInstrument: Instrument {
17+
/// A "no op" implementation of an ``InstrumentProtocol``.
18+
public struct NoOpInstrument: InstrumentProtocol {
1919
public init() {}
2020

2121
public func inject<Carrier, Inject>(_ baggage: Baggage, into carrier: inout Carrier, using injector: Inject)

Diff for: Sources/Tracing/Docs.docc/InDepthGuide.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ When instrumenting server applications there are typically three parties involve
88

99
1. **Application developers** create server-side applications
1010
2. **Library/Framework developers** provide building blocks to create these applications
11-
3. **Instrument developers** provide tools to collect distributed metadata about your application
11+
3. **InstrumentProtocol developers** provide tools to collect distributed metadata about your application
1212

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

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

4343
Instead of providing each instrumented library with a specific instrument explicitly, you *bootstrap* the
4444
`InstrumentationSystem` which acts as a singleton that libraries/frameworks access when calling out to the configured
45-
`Instrument`:
45+
`InstrumentProtocol`:
4646

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

6464
#### Bootstrapping multiple instruments using MultiplexInstrument
6565

66-
It is important to note that `InstrumentationSystem.bootstrap(_: Instrument)` must only be called once. In case you
66+
It is important to note that `InstrumentationSystem.bootstrap(_: InstrumentProtocol)` must only be called once. In case you
6767
want to bootstrap the system to use multiple instruments, you group them in a `MultiplexInstrument` first, which you
6868
then pass along to the `bootstrap` method like this:
6969

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

190190
```swift
191-
let tracer: Tracer
191+
let tracer: any TracerProtocol
192192

193193
func makeDinner(context: LoggingContext) async throws -> Meal {
194194
tracer.withSpan(operationName: "makeDinner", context) {
@@ -225,7 +225,7 @@ func get(url: String, context: LoggingContext) {
225225
}
226226
```
227227

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

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

283-
## Instrument developers: Creating an instrument
283+
## InstrumentProtocol developers: Creating an instrument
284284

285-
Creating an instrument means adopting the `Instrument` protocol (or ``Tracer`` in case you develop a tracer).
286-
`Instrument` is part of the `Instrumentation` library & `Tracing` contains the ``Tracer`` protocol.
285+
Creating an instrument means adopting the `InstrumentProtocol` protocol (or ``Tracer`` in case you develop a tracer).
286+
`InstrumentProtocol` is part of the `Instrumentation` library & `Tracing` contains the ``Tracer`` protocol.
287287

288-
`Instrument` has two requirements:
288+
`InstrumentProtocol` has two requirements:
289289

290290
1. A method to inject values inside a `LoggingContext` into a generic carrier (e.g. HTTP headers)
291291
2. A method to extract values from a generic carrier (e.g. HTTP headers) and store them in a `LoggingContext`

Diff for: Sources/Tracing/Docs.docc/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ To your main target, add a dependency on the `Tracing` library and the instrumen
6262
),
6363
```
6464

65-
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:
65+
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:
6666

6767

6868
```swift

Diff for: Sources/Tracing/InstrumentationSystem+Tracing.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ extension InstrumentationSystem {
2121
/// tracing instrument as passed to the multiplex instrument. If none is found, a ``NoOpTracer`` is returned.
2222
///
2323
/// - Returns: A ``Tracer`` if the system was bootstrapped with one, and ``NoOpTracer`` otherwise.
24-
public static var tracer: Tracer {
25-
(self._findInstrument(where: { $0 is Tracer }) as? Tracer) ?? NoOpTracer()
24+
public static var tracer: any TracerProtocol {
25+
let found: (any TracerProtocol)? =
26+
(self._findInstrument(where: { $0 is (any TracerProtocol) }) as? (any TracerProtocol))
27+
return found ?? NoOpTracer()
2628
}
2729
}

Diff for: Sources/Tracing/NoOpTracer.swift

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import Dispatch
1616
@_exported import Instrumentation
1717
@_exported import InstrumentationBaggage
1818

19-
/// No operation ``Tracer``, used when no tracing is required.
20-
public struct NoOpTracer: Tracer {
19+
/// Tracer that ignores all operations, used when no tracing is required.
20+
public struct NoOpTracer: TracerProtocol {
21+
public typealias Span = NoOpSpan
22+
2123
public init() {}
2224

2325
public func startSpan(
@@ -28,7 +30,7 @@ public struct NoOpTracer: Tracer {
2830
function: String,
2931
file fileID: String,
3032
line: UInt
31-
) -> Span {
33+
) -> NoOpSpan {
3234
NoOpSpan(operationName: operationName, baggage: baggage)
3335
}
3436

@@ -46,7 +48,7 @@ public struct NoOpTracer: Tracer {
4648
// no-op
4749
}
4850

49-
public final class NoOpSpan: Span {
51+
public final class NoOpSpan: SpanProtocol {
5052
public let baggage: Baggage
5153
public let isRecording = false
5254

Diff for: Sources/Tracing/Span.swift renamed to Sources/Tracing/SpanProtocol.swift

+9-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import struct Dispatch.DispatchWallTime
2626
/// Creating a `Span` is delegated to a ``Tracer`` and end users should never create them directly.
2727
///
2828
/// - 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.
29-
public protocol Span: AnyObject, _SwiftTracingSendableSpan {
29+
public protocol SpanProtocol: AnyObject, _SwiftTracingSendableSpan {
3030
/// The read-only `Baggage` of this `Span`, set when starting this `Span`.
3131
var baggage: Baggage { get }
3232

@@ -85,10 +85,15 @@ public protocol Span: AnyObject, _SwiftTracingSendableSpan {
8585
/// - Parameter time: The `DispatchWallTime` at which the span ended.
8686
///
8787
/// - SeeAlso: `Span.end()` which automatically uses the "current" time.
88+
@available(*, deprecated, message: "Use Clock based `end(at:)` instead")
8889
func end(at time: DispatchWallTime)
90+
91+
// #if swift(>=5.7.0)
92+
// func end(at time: Clock.Instant)
93+
// #endif
8994
}
9095

91-
extension Span {
96+
extension SpanProtocol {
9297
/// End this `Span` at the current time.
9398
///
9499
/// ### Rules about ending Spans
@@ -110,12 +115,12 @@ extension Span {
110115
/// Adds a ``SpanLink`` between this `Span` and the given `Span`.
111116
/// - Parameter other: The `Span` to link to.
112117
/// - Parameter attributes: The ``SpanAttributes`` describing this link. Defaults to no attributes.
113-
public func addLink(_ other: Span, attributes: SpanAttributes = [:]) {
118+
public func addLink(_ other: SpanProtocol, attributes: SpanAttributes = [:]) {
114119
self.addLink(SpanLink(baggage: other.baggage, attributes: attributes))
115120
}
116121
}
117122

118-
extension Span {
123+
extension SpanProtocol {
119124
/// Record a failure described by the given error.
120125
///
121126
/// - Parameters:

0 commit comments

Comments
 (0)