Skip to content

Commit 6600888

Browse files
ktososlashmo
andauthored
Pushing InMemoryTracer (#180) over the finish line (#183)
Co-authored-by: Moritz Lang <[email protected]>
1 parent 149fa4c commit 6600888

File tree

8 files changed

+1031
-8
lines changed

8 files changed

+1031
-8
lines changed

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ let package = Package(
66
products: [
77
.library(name: "Instrumentation", targets: ["Instrumentation"]),
88
.library(name: "Tracing", targets: ["Tracing"]),
9+
.library(name: "InMemoryTracing", targets: ["InMemoryTracing"]),
910
],
1011
dependencies: [
1112
.package(url: "https://github.com/apple/swift-service-context.git", from: "1.1.0")
@@ -44,6 +45,18 @@ let package = Package(
4445
.target(name: "Tracing")
4546
]
4647
),
48+
.target(
49+
name: "InMemoryTracing",
50+
dependencies: [
51+
.target(name: "Tracing")
52+
]
53+
),
54+
.testTarget(
55+
name: "InMemoryTracingTests",
56+
dependencies: [
57+
.target(name: "InMemoryTracing")
58+
]
59+
),
4760

4861
// ==== --------------------------------------------------------------------------------------------------------
4962
// MARK: Wasm Support
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@_spi(Locking) import Instrumentation
16+
import Tracing
17+
18+
/// A ``Span`` created by the ``InMemoryTracer`` that will be retained in memory when ended.
19+
/// See ``InMemoryTracer/
20+
public struct InMemorySpan: Span {
21+
22+
public let context: ServiceContext
23+
public var spanContext: InMemorySpanContext {
24+
context.inMemorySpanContext!
25+
}
26+
27+
/// The ID of the overall trace this span belongs to.
28+
public var traceID: String {
29+
spanContext.spanID
30+
}
31+
/// The ID of this concrete span.
32+
public var spanID: String {
33+
spanContext.spanID
34+
}
35+
/// The ID of the parent span of this span, if there was any.
36+
/// When this is `nil` it means this is the top-level span of this trace.
37+
public var parentSpanID: String? {
38+
spanContext.parentSpanID
39+
}
40+
41+
public let kind: SpanKind
42+
public let startInstant: any TracerInstant
43+
44+
private let _operationName: LockedValueBox<String>
45+
private let _attributes = LockedValueBox<SpanAttributes>([:])
46+
private let _events = LockedValueBox<[SpanEvent]>([])
47+
private let _links = LockedValueBox<[SpanLink]>([])
48+
private let _errors = LockedValueBox<[RecordedError]>([])
49+
private let _status = LockedValueBox<SpanStatus?>(nil)
50+
private let _isRecording = LockedValueBox<Bool>(true)
51+
private let onEnd: @Sendable (FinishedInMemorySpan) -> Void
52+
53+
public init(
54+
operationName: String,
55+
context: ServiceContext,
56+
spanContext: InMemorySpanContext,
57+
kind: SpanKind,
58+
startInstant: any TracerInstant,
59+
onEnd: @escaping @Sendable (FinishedInMemorySpan) -> Void
60+
) {
61+
self._operationName = LockedValueBox(operationName)
62+
var context = context
63+
context.inMemorySpanContext = spanContext
64+
self.context = context
65+
self.kind = kind
66+
self.startInstant = startInstant
67+
self.onEnd = onEnd
68+
}
69+
70+
/// The in memory span stops recording (storing mutations performed on the span) when it is ended.
71+
/// In other words, a finished span no longer is mutable and will ignore all subsequent attempts to mutate.
72+
public var isRecording: Bool {
73+
_isRecording.withValue { $0 }
74+
}
75+
76+
public var operationName: String {
77+
get {
78+
_operationName.withValue { $0 }
79+
}
80+
nonmutating set {
81+
guard isRecording else { return }
82+
_operationName.withValue { $0 = newValue }
83+
}
84+
}
85+
86+
public var attributes: SpanAttributes {
87+
get {
88+
_attributes.withValue { $0 }
89+
}
90+
nonmutating set {
91+
guard isRecording else { return }
92+
_attributes.withValue { $0 = newValue }
93+
}
94+
}
95+
96+
public var events: [SpanEvent] {
97+
_events.withValue { $0 }
98+
}
99+
100+
public func addEvent(_ event: SpanEvent) {
101+
guard isRecording else { return }
102+
_events.withValue { $0.append(event) }
103+
}
104+
105+
public var links: [SpanLink] {
106+
_links.withValue { $0 }
107+
}
108+
109+
public func addLink(_ link: SpanLink) {
110+
guard isRecording else { return }
111+
_links.withValue { $0.append(link) }
112+
}
113+
114+
public var errors: [RecordedError] {
115+
_errors.withValue { $0 }
116+
}
117+
118+
public func recordError(
119+
_ error: any Error,
120+
attributes: SpanAttributes,
121+
at instant: @autoclosure () -> some TracerInstant
122+
) {
123+
guard isRecording else { return }
124+
_errors.withValue {
125+
$0.append(RecordedError(error: error, attributes: attributes, instant: instant()))
126+
}
127+
}
128+
129+
public var status: SpanStatus? {
130+
_status.withValue { $0 }
131+
}
132+
133+
public func setStatus(_ status: SpanStatus) {
134+
guard isRecording else { return }
135+
_status.withValue { $0 = status }
136+
}
137+
138+
public func end(at instant: @autoclosure () -> some TracerInstant) {
139+
let shouldRecord = _isRecording.withValue {
140+
let value = $0
141+
$0 = false // from here on after, stop recording
142+
return value
143+
}
144+
guard shouldRecord else { return }
145+
146+
let finishedSpan = FinishedInMemorySpan(
147+
operationName: operationName,
148+
context: context,
149+
kind: kind,
150+
startInstant: startInstant,
151+
endInstant: instant(),
152+
attributes: attributes,
153+
events: events,
154+
links: links,
155+
errors: errors,
156+
status: status
157+
)
158+
onEnd(finishedSpan)
159+
}
160+
161+
public struct RecordedError: Sendable {
162+
public let error: Error
163+
public let attributes: SpanAttributes
164+
public let instant: any TracerInstant
165+
}
166+
}
167+
168+
/// Represents a finished span (a ``Span`` that `end()` was called on)
169+
/// that was recorded by the ``InMemoryTracer``.
170+
public struct FinishedInMemorySpan: Sendable {
171+
public var operationName: String
172+
173+
public var context: ServiceContext
174+
public var spanContext: InMemorySpanContext {
175+
get {
176+
context.inMemorySpanContext!
177+
}
178+
set {
179+
context.inMemorySpanContext = newValue
180+
}
181+
}
182+
183+
/// The ID of the overall trace this span belongs to.
184+
public var traceID: String {
185+
get {
186+
spanContext.spanID
187+
}
188+
set {
189+
spanContext.spanID = newValue
190+
}
191+
}
192+
/// The ID of this concrete span.
193+
public var spanID: String {
194+
get {
195+
spanContext.spanID
196+
}
197+
set {
198+
spanContext.spanID = newValue
199+
}
200+
}
201+
/// The ID of the parent span of this span, if there was any.
202+
/// When this is `nil` it means this is the top-level span of this trace.
203+
public var parentSpanID: String? {
204+
get {
205+
spanContext.parentSpanID
206+
}
207+
set {
208+
spanContext.parentSpanID = newValue
209+
}
210+
}
211+
212+
public var kind: SpanKind
213+
public var startInstant: any TracerInstant
214+
public var endInstant: any TracerInstant
215+
public var attributes: SpanAttributes
216+
public var events: [SpanEvent]
217+
public var links: [SpanLink]
218+
public var errors: [InMemorySpan.RecordedError]
219+
public var status: SpanStatus?
220+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Distributed Tracing open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import ServiceContextModule
16+
17+
/// Encapsulates the `traceID`, `spanID` and `parentSpanID` of an `InMemorySpan`.
18+
/// Generally used through the `ServiceContext/inMemorySpanContext` task local value.
19+
public struct InMemorySpanContext: Sendable, Hashable {
20+
/// Idenfifier of top-level trace of which this span is a part of.
21+
public var traceID: String
22+
23+
/// Identifier of this specific span.
24+
public var spanID: String
25+
26+
// Identifier of the parent of this span, if any.
27+
public var parentSpanID: String?
28+
29+
public init(traceID: String, spanID: String, parentSpanID: String?) {
30+
self.traceID = traceID
31+
self.spanID = spanID
32+
self.parentSpanID = parentSpanID
33+
}
34+
}
35+
36+
extension ServiceContext {
37+
/// Task-local value representing the current tracing ``Span`` as set by the ``InMemoryTracer``.
38+
public var inMemorySpanContext: InMemorySpanContext? {
39+
get {
40+
self[InMemorySpanContextKey.self]
41+
}
42+
set {
43+
self[InMemorySpanContextKey.self] = newValue
44+
}
45+
}
46+
}
47+
48+
private struct InMemorySpanContextKey: ServiceContextKey {
49+
typealias Value = InMemorySpanContext
50+
}

0 commit comments

Comments
 (0)