Skip to content

Commit f1ffc1c

Browse files
committed
Add documentation on new APIs
1 parent e5a71f9 commit f1ffc1c

File tree

3 files changed

+144
-41
lines changed

3 files changed

+144
-41
lines changed

Sources/InMemoryTracing/InMemorySpanContext.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414

1515
import ServiceContextModule
1616

17+
/// Encapsulates the `traceID`, `spanID` and `parentSpanID` of an `InMemorySpan`.
18+
/// Generally used through the `ServiceContext/inMemorySpanContext` task local value.
1719
public struct InMemorySpanContext: Sendable, Hashable {
20+
/// Idenfifier of top-level trace of which this span is a part of.
1821
public let traceID: String
22+
23+
/// Identifier of this specific span.
1924
public let spanID: String
25+
26+
// Identifier of the parent of this span, if any.
2027
public let parentSpanID: String?
2128

2229
public init(traceID: String, spanID: String, parentSpanID: String?) {
@@ -27,7 +34,8 @@ public struct InMemorySpanContext: Sendable, Hashable {
2734
}
2835

2936
extension ServiceContext {
30-
var inMemorySpanContext: InMemorySpanContext? {
37+
/// Task-local value representing the current tracing ``Span`` as set by the ``InMemoryTracer``.
38+
public var inMemorySpanContext: InMemorySpanContext? {
3139
get {
3240
self[InMemorySpanContextKey.self]
3341
}

Sources/InMemoryTracing/InMemoryTracer.swift

Lines changed: 119 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,50 @@
1515
@_spi(Locking) import Instrumentation
1616
import Tracing
1717

18+
/// An in-memory implementation of the ``Tracer`` protocol which can be used either in testing,
19+
/// or in manual collecting and interrogating traces within a process, and acting on them programatically.
20+
///
21+
/// ### Span lifecycle
22+
/// This tracer does _not_ automatically remove spans once they end.
23+
/// Finished spans are retained and available for inspection using the `finishedSpans` property.
24+
/// Spans which have been started but have not yet been called `Span/end()` on are also available
25+
/// for inspection using the ``activeSpans`` property.
26+
///
27+
/// Spans are retained by the `InMemoryTracer` until they are explicitly removed, e.g. by using
28+
/// ``popFinishedSpans()`` or any of the `clear...` methods (e.g. ``clearFinishedSpans()``)
1829
public struct InMemoryTracer: Tracer {
1930

20-
public static let traceIDKey = "test-trace-id"
21-
public static let spanIDKey = "test-span-id"
22-
2331
public let idGenerator: IDGenerator
2432

33+
public let recordInjections: Bool
34+
public let recordExtractions: Bool
35+
2536
private let _activeSpans = LockedValueBox<[InMemorySpanContext: InMemorySpan]>([:])
2637
private let _finishedSpans = LockedValueBox<[FinishedInMemorySpan]>([])
2738
private let _numberOfForceFlushes = LockedValueBox<Int>(0)
2839

29-
public init(idGenerator: IDGenerator = .incrementing) {
40+
private let _injections = LockedValueBox<[Injection]>([])
41+
private let _extractions = LockedValueBox<[Extraction]>([])
42+
43+
/// Create a new ``InMemoryTracer``.
44+
///
45+
/// - Parameters:
46+
/// - Parameter idGenerator: strategy for generating trace and span identifiers
47+
/// - Parameter idGenerator: strategy for generating trace and span identifiers
48+
public init(
49+
idGenerator: IDGenerator = .incrementing,
50+
recordInjections: Bool = true,
51+
recordExtractions: Bool = true
52+
) {
3053
self.idGenerator = idGenerator
54+
self.recordInjections = recordInjections
55+
self.recordExtractions = recordExtractions
3156
}
57+
}
3258

33-
// MARK: - Tracer
59+
// MARK: - Tracer
60+
61+
extension InMemoryTracer {
3462

3563
public func startSpan<Instant>(
3664
_ operationName: String,
@@ -77,24 +105,66 @@ public struct InMemoryTracer: Tracer {
77105
return span
78106
}
79107

108+
public func forceFlush() {
109+
_numberOfForceFlushes.withValue { $0 += 1 }
110+
}
111+
}
112+
113+
// MARK: - InMemoryTracer querying
114+
115+
extension InMemoryTracer {
116+
117+
/// Array of active spans, i.e. spans which have been started by have not yet finished (by calling `Span/end()`).
118+
public var activeSpans: [InMemorySpan] {
119+
_activeSpans.withValue { active in Array(active.values) }
120+
}
121+
122+
/// Retrives a specific _active_ span, identified by the specific span, trace, and parent ID's
123+
/// stored in the `inMemorySpanContext`
80124
public func activeSpan(identifiedBy context: ServiceContext) -> InMemorySpan? {
81125
guard let spanContext = context.inMemorySpanContext else { return nil }
82126
return _activeSpans.withValue { $0[spanContext] }
83127
}
84128

85-
public func forceFlush() {
86-
_numberOfForceFlushes.withValue { $0 += 1 }
87-
}
88-
129+
/// Count of the number of times ``Tracer/forceFlush()`` was called on this tracer.
89130
public var numberOfForceFlushes: Int {
90131
_numberOfForceFlushes.withValue { $0 }
91132
}
92133

134+
/// Gets, without removing, all the finished spans recorded by this tracer.
135+
///
136+
/// - SeeAlso: `popFinishedSpans()`
93137
public var finishedSpans: [FinishedInMemorySpan] {
94138
_finishedSpans.withValue { $0 }
95139
}
140+
141+
/// Returns, and removes, all finished spans recorded by this tracer.
142+
public func popFinishedSpans() -> [FinishedInMemorySpan] {
143+
_finishedSpans.withValue { spans in
144+
defer { spans = [] }
145+
return spans
146+
}
147+
}
96148

97-
// MARK: - Instrument
149+
/// Atomically clears any stored finished spans in this tracer.
150+
public func clearFinishedSpans() {
151+
_finishedSpans.withValue { $0 = [] }
152+
}
153+
154+
/// Clears all registered finished spans, as well as injections/extractions performed by this tracer.
155+
public func clearAll() {
156+
_finishedSpans.withValue { $0 = [] }
157+
_injections.withValue { $0 = [] }
158+
_extractions.withValue { $0 = [] }
159+
}
160+
}
161+
162+
// MARK: - Instrument
163+
164+
extension InMemoryTracer {
165+
166+
public static let traceIDKey = "in-memory-trace-id"
167+
public static let spanIDKey = "in-memory-span-id"
98168

99169
public func inject<Carrier, Inject: Injector>(
100170
_ context: ServiceContext,
@@ -110,22 +180,44 @@ public struct InMemoryTracer: Tracer {
110180
values[Self.spanIDKey] = spanContext.spanID
111181
}
112182

113-
let injection = Injection(context: context, values: values)
114-
_injections.withValue { $0.append(injection) }
183+
if recordInjections {
184+
let injection = Injection(context: context, values: values)
185+
_injections.withValue { $0.append(injection) }
186+
}
115187
}
116188

117-
public var injections: [Injection] {
189+
/// Lists all recorded calls to this tracer's ``Instrument/inject(_:into:using:)`` method.
190+
/// This may be used to inspect what span identifiers are being propagated by this tracer.
191+
public var performedContextInjections: [Injection] {
118192
_injections.withValue { $0 }
119193
}
120194

195+
/// Clear the list of recorded context injections (calls to ``Instrument/inject(_:into:using:)``).
196+
public func clearPerformedContextInjections() {
197+
_injections.withValue { $0 = [] }
198+
}
199+
200+
/// Represents a recorded call to the InMemoryTracer's ``Instrument/inject(_:into:using:)`` method.
201+
public struct Injection: Sendable {
202+
/// The context from which values were being injected.
203+
public let context: ServiceContext
204+
/// The injected values, these will be specifically the trace and span identifiers of the propagated span.
205+
public let values: [String: String]
206+
}
207+
}
208+
209+
extension InMemoryTracer {
210+
121211
public func extract<Carrier, Extract: Extractor>(
122212
_ carrier: Carrier,
123213
into context: inout ServiceContext,
124214
using extractor: Extract
125215
) where Carrier == Extract.Carrier {
126216
defer {
127-
let extraction = Extraction(carrier: carrier, context: context)
128-
_extractions.withValue { $0.append(extraction) }
217+
if self.recordExtractions {
218+
let extraction = Extraction(carrier: carrier, context: context)
219+
_extractions.withValue { $0.append(extraction) }
220+
}
129221
}
130222

131223
guard let traceID = extractor.extract(key: Self.traceIDKey, from: carrier),
@@ -137,27 +229,30 @@ public struct InMemoryTracer: Tracer {
137229
context.inMemorySpanContext = InMemorySpanContext(traceID: traceID, spanID: spanID, parentSpanID: nil)
138230
}
139231

140-
public var extractions: [Extraction] {
232+
/// Lists all recorded calls to this tracer's ``Instrument/extract(_:into:using:)`` method.
233+
/// This may be used to inspect what span identifiers were extracted from an incoming carrier object into ``ServiceContext``.
234+
public var performedContextExtractions: [Extraction] {
141235
_extractions.withValue { $0 }
142236
}
143237

144-
public struct Injection: Sendable {
145-
public let context: ServiceContext
146-
public let values: [String: String]
147-
}
148-
238+
239+
/// Represents a recorded call to the InMemoryTracer's ``Instrument/extract(_:into:using:)`` method.
149240
public struct Extraction: Sendable {
241+
/// The carrier object from which the context values were extracted from,
242+
/// e.g. this frequently is an HTTP request or similar.
150243
public let carrier: any Sendable
244+
/// The constructed service context, containing the extracted ``ServiceContext/inMemoryTraceContext``.
151245
public let context: ServiceContext
152246
}
153-
154-
private let _injections = LockedValueBox<[Injection]>([])
155-
private let _extractions = LockedValueBox<[Extraction]>([])
156247
}
157248

158249
// MARK: - ID Generator
159250

160251
extension InMemoryTracer {
252+
253+
/// Can be used to customize how trace and span IDs are generated by the ``InMemoryTracer``.
254+
///
255+
/// Defaults to a simple sequential numeric scheme (`span-1`, `span-2`, `trace-1`, `trace-2` etc).
161256
public struct IDGenerator: Sendable {
162257
public let nextTraceID: @Sendable () -> String
163258
public let nextSpanID: @Sendable () -> String

Tests/InMemoryTracingTests/InMemoryTracerTests.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ struct InMemoryTracerTests {
106106
spanID: "stub",
107107
parentSpanID: "stub"
108108
)
109-
context.testSpanContext = spanContext
109+
context.inMemorySpanContext = spanContext
110110

111111
var values = [String: String]()
112112
tracer.inject(context, into: &values, using: DictionaryInjector())
113113

114114
#expect(values == [InMemoryTracer.traceIDKey: "stub", InMemoryTracer.spanIDKey: "stub"])
115115

116-
let injection = try #require(tracer.injections.first)
117-
#expect(injection.context.testSpanContext == spanContext)
116+
let injection = try #require(tracer.performedContextInjections.first)
117+
#expect(injection.context.inMemorySpanContext == spanContext)
118118
#expect(injection.values == values)
119119
}
120120

@@ -128,8 +128,8 @@ struct InMemoryTracerTests {
128128

129129
#expect(values.isEmpty)
130130

131-
let injection = try #require(tracer.injections.first)
132-
#expect(injection.context.testSpanContext == nil)
131+
let injection = try #require(tracer.performedContextInjections.first)
132+
#expect(injection.context.inMemorySpanContext == nil)
133133
#expect(injection.values.isEmpty)
134134
}
135135

@@ -141,13 +141,13 @@ struct InMemoryTracerTests {
141141
let values = [InMemoryTracer.traceIDKey: "stub", InMemoryTracer.spanIDKey: "stub"]
142142
tracer.extract(values, into: &context, using: DictionaryExtractor())
143143

144-
let spanContext = try #require(context.testSpanContext)
144+
let spanContext = try #require(context.inMemorySpanContext)
145145

146146
#expect(spanContext == InMemorySpanContext(traceID: "stub", spanID: "stub", parentSpanID: nil))
147147

148-
let extraction = try #require(tracer.extractions.first)
148+
let extraction = try #require(tracer.performedContextExtractions.first)
149149
#expect(extraction.carrier as? [String: String] == values)
150-
#expect(extraction.context.testSpanContext == spanContext)
150+
#expect(extraction.context.inMemorySpanContext == spanContext)
151151
}
152152

153153
@Test("Does not extract span context without values but records extraction")
@@ -158,11 +158,11 @@ struct InMemoryTracerTests {
158158
let values = ["foo": "bar"]
159159
tracer.extract(values, into: &context, using: DictionaryExtractor())
160160

161-
#expect(context.testSpanContext == nil)
161+
#expect(context.inMemorySpanContext == nil)
162162

163-
let extraction = try #require(tracer.extractions.first)
163+
let extraction = try #require(tracer.performedContextExtractions.first)
164164
#expect(extraction.carrier as? [String: String] == values)
165-
#expect(extraction.context.testSpanContext == nil)
165+
#expect(extraction.context.inMemorySpanContext == nil)
166166
}
167167
}
168168

@@ -212,18 +212,18 @@ struct InMemoryTracerTests {
212212

213213
let spanContext1 = InMemorySpanContext(traceID: "1", spanID: "1", parentSpanID: nil)
214214
var context1 = ServiceContext.topLevel
215-
context1.testSpanContext = spanContext1
215+
context1.inMemorySpanContext = spanContext1
216216
span.addLink(SpanLink(context: context1, attributes: ["foo": "1"]))
217217
let link1 = try #require(span.links.first)
218-
#expect(link1.context.testSpanContext == spanContext1)
218+
#expect(link1.context.inMemorySpanContext == spanContext1)
219219
#expect(link1.attributes == ["foo": "1"])
220220

221221
let spanContext2 = InMemorySpanContext(traceID: "2", spanID: "2", parentSpanID: nil)
222222
var context2 = ServiceContext.topLevel
223-
context2.testSpanContext = spanContext2
223+
context2.inMemorySpanContext = spanContext2
224224
span.addLink(SpanLink(context: context2, attributes: ["foo": "2"]))
225225
let link2 = try #require(span.links.last)
226-
#expect(link2.context.testSpanContext == spanContext2)
226+
#expect(link2.context.inMemorySpanContext == spanContext2)
227227
#expect(link2.attributes == ["foo": "2"])
228228
}
229229

@@ -282,7 +282,7 @@ struct InMemoryTracerTests {
282282
span.addEvent("foo")
283283
let otherSpanContext = InMemorySpanContext(traceID: "other", spanID: "other", parentSpanID: nil)
284284
var otherContext = ServiceContext.topLevel
285-
otherContext.testSpanContext = otherSpanContext
285+
otherContext.inMemorySpanContext = otherSpanContext
286286
span.addLink(SpanLink(context: otherContext, attributes: [:]))
287287
struct TestError: Error {}
288288
span.recordError(TestError())

0 commit comments

Comments
 (0)