15
15
@_spi ( Locking) import Instrumentation
16
16
import Tracing
17
17
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()``)
18
29
public struct InMemoryTracer : Tracer {
19
30
20
- public static let traceIDKey = " test-trace-id "
21
- public static let spanIDKey = " test-span-id "
22
-
23
31
public let idGenerator : IDGenerator
24
32
33
+ public let recordInjections : Bool
34
+ public let recordExtractions : Bool
35
+
25
36
private let _activeSpans = LockedValueBox < [ InMemorySpanContext : InMemorySpan ] > ( [ : ] )
26
37
private let _finishedSpans = LockedValueBox < [ FinishedInMemorySpan ] > ( [ ] )
27
38
private let _numberOfForceFlushes = LockedValueBox < Int > ( 0 )
28
39
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
+ ) {
30
53
self . idGenerator = idGenerator
54
+ self . recordInjections = recordInjections
55
+ self . recordExtractions = recordExtractions
31
56
}
57
+ }
32
58
33
- // MARK: - Tracer
59
+ // MARK: - Tracer
60
+
61
+ extension InMemoryTracer {
34
62
35
63
public func startSpan< Instant> (
36
64
_ operationName: String ,
@@ -77,24 +105,66 @@ public struct InMemoryTracer: Tracer {
77
105
return span
78
106
}
79
107
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`
80
124
public func activeSpan( identifiedBy context: ServiceContext ) -> InMemorySpan ? {
81
125
guard let spanContext = context. inMemorySpanContext else { return nil }
82
126
return _activeSpans. withValue { $0 [ spanContext] }
83
127
}
84
128
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.
89
130
public var numberOfForceFlushes : Int {
90
131
_numberOfForceFlushes. withValue { $0 }
91
132
}
92
133
134
+ /// Gets, without removing, all the finished spans recorded by this tracer.
135
+ ///
136
+ /// - SeeAlso: `popFinishedSpans()`
93
137
public var finishedSpans : [ FinishedInMemorySpan ] {
94
138
_finishedSpans. withValue { $0 }
95
139
}
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
+ }
96
148
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 "
98
168
99
169
public func inject< Carrier, Inject: Injector > (
100
170
_ context: ServiceContext ,
@@ -110,22 +180,44 @@ public struct InMemoryTracer: Tracer {
110
180
values [ Self . spanIDKey] = spanContext. spanID
111
181
}
112
182
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
+ }
115
187
}
116
188
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 ] {
118
192
_injections. withValue { $0 }
119
193
}
120
194
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
+
121
211
public func extract< Carrier, Extract: Extractor > (
122
212
_ carrier: Carrier ,
123
213
into context: inout ServiceContext ,
124
214
using extractor: Extract
125
215
) where Carrier == Extract . Carrier {
126
216
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
+ }
129
221
}
130
222
131
223
guard let traceID = extractor. extract ( key: Self . traceIDKey, from: carrier) ,
@@ -137,27 +229,30 @@ public struct InMemoryTracer: Tracer {
137
229
context. inMemorySpanContext = InMemorySpanContext ( traceID: traceID, spanID: spanID, parentSpanID: nil )
138
230
}
139
231
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 ] {
141
235
_extractions. withValue { $0 }
142
236
}
143
237
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.
149
240
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.
150
243
public let carrier : any Sendable
244
+ /// The constructed service context, containing the extracted ``ServiceContext/inMemoryTraceContext``.
151
245
public let context : ServiceContext
152
246
}
153
-
154
- private let _injections = LockedValueBox < [ Injection ] > ( [ ] )
155
- private let _extractions = LockedValueBox < [ Extraction ] > ( [ ] )
156
247
}
157
248
158
249
// MARK: - ID Generator
159
250
160
251
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).
161
256
public struct IDGenerator : Sendable {
162
257
public let nextTraceID : @Sendable ( ) -> String
163
258
public let nextSpanID : @Sendable ( ) -> String
0 commit comments