Skip to content

Commit 9aedf76

Browse files
committed
change the API to withTracer
1 parent 2394919 commit 9aedf76

7 files changed

Lines changed: 241 additions & 365 deletions

File tree

Benchmarks/Benchmarks/TracingBenchmarks/AtrributeDSLBenchmark.swift

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ let benchmarks: @Sendable () -> Void = {
4949
}
5050

5151
Benchmark(
52-
"NoopTracing.startSpan_endSpan.withInstrumentScope",
52+
"NoopTracing.startSpan_endSpan.withTracerScope",
5353
configuration: configurationWithMallocAndInstructions
5454
) { benchmark in
55-
withInstrument(NoOpInstrument()) {
55+
withTracer(NoOpTracer()) {
5656
benchmark.startMeasurement()
5757
let span = startSpan("name")
5858
blackHole(span)
@@ -62,10 +62,10 @@ let benchmarks: @Sendable () -> Void = {
6262
}
6363

6464
Benchmark(
65-
"NoopTracing.withSpan.withInstrumentScope",
65+
"NoopTracing.withSpan.withTracerScope",
6666
configuration: configurationWithMallocAndInstructions
6767
) { benchmark in
68-
withInstrument(NoOpInstrument()) {
68+
withTracer(NoOpTracer()) {
6969
benchmark.startMeasurement()
7070
withSpan("name") { span in
7171
blackHole(span)
@@ -74,19 +74,6 @@ let benchmarks: @Sendable () -> Void = {
7474
}
7575
}
7676

77-
Benchmark(
78-
"NoopTracing.startSpan_endSpan.withMultiplexScope",
79-
configuration: configurationWithMallocAndInstructions
80-
) { benchmark in
81-
withInstrument(MultiplexInstrument([NoOpInstrument(), NoOpInstrument()])) {
82-
benchmark.startMeasurement()
83-
let span = startSpan("name")
84-
blackHole(span)
85-
span.end()
86-
benchmark.stopMeasurement()
87-
}
88-
}
89-
9077
Benchmark(
9178
"NoopTracing.attribute. set, span.attributes['http.status_code'] = 200",
9279
configuration: configurationWithMalloc

Sources/Instrumentation/InstrumentationSystem.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import ServiceContextModule
2222
/// If you need to use more that one cross-cutting tool you can do so by using ``MultiplexInstrument``.
2323
///
2424
/// To override the active instrument for a scope — a test, a subsystem — without touching the process-wide
25-
/// bootstrap, use ``withInstrument(_:_:)``. It binds a task-local instrument that ``instrument`` and discovery
26-
/// resolve ahead of the bootstrapped one, for the duration of a closure.
25+
/// bootstrap, the Tracing module provides `withTracer(_:_:)`, which binds a task-local override that
26+
/// ``instrument`` and discovery resolve ahead of the bootstrapped one, for the duration of a closure.
2727
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal ServiceContext
2828
public enum InstrumentationSystem {
2929
/// Marked as @unchecked Sendable due to the synchronization being
@@ -66,17 +66,18 @@ public enum InstrumentationSystem {
6666

6767
private static let shared = Storage()
6868

69-
/// Task-local instrument override set by ``withInstrument(_:_:)``.
69+
/// Task-local instrument override set by `withTracer(_:_:)` (Tracing module).
7070
///
7171
/// Resolved ahead of the bootstrapped instrument by ``instrument`` and ``_findInstrument(where:)`` for the
72-
/// duration of a scope. Internal storage — callers set it by calling ``withInstrument(_:_:)``.
72+
/// duration of a scope.
7373
@TaskLocal
7474
@usableFromInline
7575
internal static var _taskLocalInstrument: (any Instrument)?
7676

77-
/// Runs `operation` with `instrument` bound to the task-local override. Backs ``withInstrument(_:_:)``.
77+
/// Runs `operation` with `instrument` bound to the task-local override. Backs `withTracer(_:_:)` in the
78+
/// Tracing module — `package`, not `internal`, so that module can call it.
7879
@usableFromInline
79-
static func withTaskLocalInstrument<Result>(
80+
package static func withTaskLocalInstrument<Result>(
8081
_ instrument: any Instrument,
8182
operation: () throws -> Result
8283
) rethrows -> Result {
@@ -86,7 +87,7 @@ public enum InstrumentationSystem {
8687
#if compiler(>=6.2)
8788
/// Async variant of ``withTaskLocalInstrument(_:operation:)``.
8889
@usableFromInline
89-
nonisolated(nonsending) static func withTaskLocalInstrument<Result>(
90+
package nonisolated(nonsending) static func withTaskLocalInstrument<Result>(
9091
_ instrument: any Instrument,
9192
operation: nonisolated(nonsending) () async throws -> Result
9293
) async rethrows -> Result {
@@ -95,7 +96,7 @@ public enum InstrumentationSystem {
9596
#else
9697
/// Async variant of ``withTaskLocalInstrument(_:operation:)``.
9798
@usableFromInline
98-
static func withTaskLocalInstrument<Result>(
99+
package static func withTaskLocalInstrument<Result>(
99100
_ instrument: any Instrument,
100101
isolation: isolated (any Actor)? = #isolation,
101102
operation: () async throws -> Result
@@ -122,7 +123,7 @@ public enum InstrumentationSystem {
122123

123124
/// The currently active ``Instrument``.
124125
///
125-
/// This is the instrument bound by the innermost enclosing ``withInstrument(_:_:)`` scope, if any,
126+
/// This is the instrument bound by the innermost enclosing `withTracer(_:_:)` scope, if any,
126127
/// otherwise the one set with ``bootstrap(_:)`` — and a ``NoOpInstrument`` if neither was set.
127128
public static var instrument: Instrument {
128129
Self._taskLocalInstrument ?? self.shared.instrument
@@ -134,7 +135,7 @@ extension InstrumentationSystem {
134135
/// INTERNAL API: Do Not Use
135136
///
136137
/// Finds the first instrument matching `predicate` in the currently active instrument — the
137-
/// ``withInstrument(_:_:)`` override if one is in scope, otherwise the bootstrapped instrument. If the
138+
/// `withTracer(_:_:)` override if one is in scope, otherwise the bootstrapped instrument. If the
138139
/// active instrument is a ``MultiplexInstrument``, its direct members are checked in order.
139140
public static func _findInstrument(where predicate: (Instrument) -> Bool) -> Instrument? {
140141
if let scoped = Self._taskLocalInstrument {

Sources/Tracing/Docs.docc/Guides/InstrumentYourLibrary.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,14 +379,14 @@ On the other hand, it may be valuable for testing scenarios to be able to set a
379379

380380
#### Honoring a caller's scope
381381

382-
A library observes a caller's ``withInstrument(_:_:)`` scope only if it resolves the instrument *per call*
382+
A library observes a caller's ``withTracer(_:_:)`` scope only if it resolves the instrument *per call*
383383
through the free-function `withSpan` / `startSpan` or ``InstrumentationSystem/instrument`` at the emission
384384
site. A library that captures a `Tracer` once at construction ignores any scope entered later, so resolve per
385385
call unless you deliberately want to pin one instrument to an instance.
386386

387387
#### Testing your library's instrumentation
388388

389-
``withInstrument(_:_:)`` is the recommended way to test span emission and context propagation from a library.
389+
``withTracer(_:_:)`` is the recommended way to test span emission and context propagation from a library.
390390
Each test sets its own in-memory tracer as the active instrument for the duration of a closure. Because the
391391
binding is task-local, tests run in parallel without serialization or global-state cleanup.
392392

@@ -397,7 +397,7 @@ import InMemoryTracing
397397

398398
@Test func emitsExpectedSpan() async throws {
399399
let tracer = InMemoryTracer()
400-
await withInstrument(tracer) {
400+
await withTracer(tracer) {
401401
await MyLibrary().doWork()
402402
}
403403
#expect(tracer.finishedSpans.map(\.operationName) == ["my-library.do-work"])
@@ -406,4 +406,4 @@ import InMemoryTracing
406406

407407
The active tracer is the one the test set, so it captures emission directly. For propagation tests, call
408408
`InstrumentationSystem.instrument.inject(...)` / `.extract(...)` inside the closure and assert on the carrier
409-
or context. See <doc:TraceYourApplication#Scoping-an-instrument-with-withInstrument> for the full semantics.
409+
or context. See <doc:TraceYourApplication#Scoping-a-tracer-with-withTracer> for the full semantics.

Sources/Tracing/Docs.docc/Guides/TraceYourApplication.md

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ There are two ways to set up instrumentation:
1919

2020
- ``InstrumentationSystem/bootstrap(_:)`` — process-wide, called once at startup. The classic path. Simplest
2121
when your app uses a single tracer for its whole lifetime and never needs to override it.
22-
- ``withInstrument(_:_:)`` — makes an instrument active for the current task while a closure runs, resolved
22+
- ``withTracer(_:_:)`` — makes a tracer active for the current task while a closure runs, resolved
2323
ahead of the bootstrapped instrument and falling back to it outside the scope. Useful for parallel-safe tests
24-
with per-test tracers, or to override the instrument for a specific subsystem. See
25-
[Scoping an instrument with withInstrument](#Scoping-an-instrument-with-withInstrument)
24+
with per-test tracers, or to override the tracer for a specific subsystem. See
25+
[Scoping a tracer with withTracer](#Scoping-a-tracer-with-withTracer)
2626
later in this guide.
2727

2828
> Note: Since instrumenting an **application** in practice will always need to pull in an existing tracer implementation,
@@ -159,8 +159,8 @@ InstrumentationSystem.bootstrap(MultiplexInstrument([
159159
`MultiplexInstrument` will then call out to each instrument it has been initialized with.
160160

161161
> Note: For scoped alternatives to plain `bootstrap` — for example, binding a tracer inside a test or
162-
> overriding it for a subsystem — use ``withInstrument(_:_:)`` instead and see
163-
> [Scoping an instrument with withInstrument](#Scoping-an-instrument-with-withInstrument)
162+
> overriding it for a subsystem — use ``withTracer(_:_:)`` instead and see
163+
> [Scoping a tracer with withTracer](#Scoping-a-tracer-with-withTracer)
164164
> later in this guide, after the span introduction.
165165
166166
### Introducing Trace Spans
@@ -459,11 +459,11 @@ Events usually show up in a trace view as points on the timeline (note that some
459459
460460
Events cannot be "failed" or "successful", that is a property of a ``Span``, and they do not have anything that would be equivalent to a log level. When a trace span is recorded and collected, so will all events related to it. In that sense, events are different from log statements, because one can easily change a logger to include the "debug level" log statements, but technically no such concept exists for events (although you could simulate it with attributes).
461461
462-
### Scoping an instrument with withInstrument
462+
### Scoping a tracer with withTracer
463463
464-
``withInstrument(_:_:)`` makes an instrument active for the current task while a closure runs. Inside the
465-
closure it is resolved ahead of whatever ``InstrumentationSystem/bootstrap(_:)`` set. Outside the closure — and
466-
in tasks that do not inherit the binding, such as `Task.detached` — resolution falls back to the bootstrapped
464+
``withTracer(_:_:)`` makes a ``Tracer`` active for the current task while a closure runs. Inside the closure
465+
it is resolved ahead of whatever ``InstrumentationSystem/bootstrap(_:)`` set. Outside the closure — and in
466+
tasks that do not inherit the binding, such as `Task.detached` — resolution falls back to the bootstrapped
467467
instrument. The binding is task-local, so it flows into the structured child tasks the closure spawns, as well
468468
as an unstructured `Task { }`.
469469
@@ -474,58 +474,49 @@ Its two intended uses are **parallel-safe testing** and **per-subsystem override
474474
// Parallel-safe: the binding is task-local, so concurrent tests don't interfere.
475475
@Test func spansAreCaptured() async {
476476
let tracer = InMemoryTracer()
477-
await withInstrument(tracer) {
477+
await withTracer(tracer) {
478478
await withSpan("op") { _ in }
479479
}
480480
#expect(tracer.finishedSpans.count == 1)
481481
}
482482
```
483483
484-
> Important: ``withInstrument(_:_:)`` chooses the active *instrument* (the backend). It does **not** propagate
484+
> Important: ``withTracer(_:_:)`` chooses the active *instrument* (the backend). It does **not** propagate
485485
> trace *context* — that is ``ServiceContext``'s job, carried on its own task-local via
486486
> `ServiceContext.withValue` and, across process boundaries, `inject` / `extract`. The two are independent
487487
> task-locals: at any span-creation or propagation site you need both the intended instrument and the right
488488
> ``ServiceContext`` in scope. Neither crosses a `Task.detached` or manual (callback / `EventLoopFuture`)
489489
> boundary — re-establish both on the other side. See <doc:InstrumentYourLibrary> for context propagation.
490490
491-
The instrument **replaces** the active instrument for the scope — it does not merge with it. A nested
492-
``withInstrument(_:_:)`` fully replaces the enclosing one, and the previous instrument is restored when the
491+
The tracer **replaces** the active instrument for the scope — it does not merge with it. A nested
492+
``withTracer(_:_:)`` fully replaces the enclosing one, and the previous instrument is restored when the
493493
closure returns. Discovery (``InstrumentationSystem/tracer``, free-function `withSpan` / `startSpan`) and
494494
propagation (`inject` / `extract`) resolve it for the duration of the scope, and fall back to the bootstrap
495-
outside it.
495+
outside it — a ``Tracer`` is also an ``Instrument``, so `inject` / `extract` observe the scope too, not just
496+
span creation.
496497
497-
To run several instruments at once, pass a ``MultiplexInstrument`` naming all of them — exactly as you would
498-
build one for `bootstrap`. Propagation (`inject` / `extract`) runs every member, while span creation uses the
499-
**first** ``Tracer`` in the multiplex:
498+
``withTracer(_:_:)`` only accepts a ``Tracer``, so it can't be handed a ``MultiplexInstrument`` directly. If a
499+
scope needs several tracers active at once (or a whole-process combination that never scopes), install the
500+
``MultiplexInstrument`` naming all of them once, at ``InstrumentationSystem/bootstrap(_:)``:
500501
501502
```swift
502-
try await withInstrument(MultiplexInstrument([OTelTracer(configuration: config), myPropagator])) {
503-
try await subsystem.run()
504-
}
503+
InstrumentationSystem.bootstrap(MultiplexInstrument([OTelTracer(configuration: config), myPropagator]))
505504
```
506505
507-
Because a scope replaces rather than merges, binding a **non-tracer** instrument on its own turns tracing off
508-
for the scope — it shadows the tracer, so spans created inside reach no tracer. Include a tracer in the
509-
``MultiplexInstrument`` when you want both. To *augment* whatever is already active rather than replace it,
510-
build the ``MultiplexInstrument`` from ``InstrumentationSystem/instrument``, which returns the concrete active
511-
instrument:
512-
513-
```swift
514-
try await withInstrument(MultiplexInstrument([InstrumentationSystem.instrument, myPropagator])) {
515-
try await subsystem.run()
516-
}
517-
```
506+
There is currently no supported way to task-locally combine a tracer with extra propagators, or several
507+
tracers, in a single scope — `withTracer(_:_:)` only accepts a `Tracer`, and `MultiplexInstrument` isn't one.
508+
Install the combination once, at `bootstrap`, if you need it process-wide.
518509
519-
> Important: ``withInstrument(_:_:)`` overrides the active instrument for its scope, like
510+
> Important: ``withTracer(_:_:)`` overrides the active instrument for its scope, like
520511
> ``InstrumentationSystem/bootstrap(_:)`` but scoped rather than process-wide, and resolution falls back to the
521512
> bootstrapped one outside it. ``InstrumentationSystem/instrument`` and `withSpan` / `startSpan` already
522513
> observe whichever is active.
523514
524515
Prefer ``InstrumentationSystem/bootstrap(_:)`` for the application's **process-wide** tracer, and reach for
525-
``withInstrument(_:_:)`` to override it for a **bounded scope** — a test, or a subsystem. Scoping the whole
516+
``withTracer(_:_:)`` to override it for a **bounded scope** — a test, or a subsystem. Scoping the whole
526517
application is possible but rarely what you want for tracing: work that escapes the closure's task tree (a
527518
`Task.detached`, an `EventLoopFuture` callback, a long-lived background task) does not inherit the scope and
528-
falls back to the bootstrapped instrument, so a whole-application `withInstrument` can silently drop spans and
519+
falls back to the bootstrapped instrument, so a whole-application `withTracer` can silently drop spans and
529520
context at exactly those boundaries.
530521
531522
### Integrations

0 commit comments

Comments
 (0)