Skip to content

Commit d633b17

Browse files
ktososlashmo
andauthored
Recording an error may carry attributes (#90)
* Recording an error may carry attributes **Motivation:** It can be used to attach more information bout the error, like its stacktrace and similar information. This is also supported by the otel spec, so we're aligning closer with it here. **Modifications:** Change the recordError to accept optional span attributes **Result:** more powerful record error API * Update Sources/Tracing/Span.swift Co-authored-by: Moritz Lang <[email protected]> * Update Sources/Tracing/Span.swift Co-authored-by: Moritz Lang <[email protected]> --------- Co-authored-by: Moritz Lang <[email protected]>
1 parent b7a1e20 commit d633b17

7 files changed

+55
-8
lines changed

Sources/Tracing/NoOpTracer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public struct NoOpTracer: Tracer {
7171

7272
public func addEvent(_ event: SpanEvent) {}
7373

74-
public func recordError(_ error: Error) {}
74+
public func recordError(_ error: Error, attributes: SpanAttributes) {}
7575

7676
public var attributes: SpanAttributes {
7777
get {

Sources/Tracing/Span.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public protocol Span: AnyObject, _SwiftTracingSendableSpan {
5959
///
6060
/// - Parameters:
6161
/// - error: The error to be recorded.
62-
func recordError(_ error: Error)
62+
/// - attributes: Additional attributes describing the error.
63+
func recordError(_ error: Error, attributes: SpanAttributes)
6364

6465
/// The attributes describing this `Span`.
6566
var attributes: SpanAttributes { get set }
@@ -114,6 +115,16 @@ extension Span {
114115
}
115116
}
116117

118+
extension Span {
119+
/// Record a failure described by the given error.
120+
///
121+
/// - Parameters:
122+
/// - error: The error to be recorded.
123+
public func recordError(_ error: Error) {
124+
self.recordError(error, attributes: [:])
125+
}
126+
}
127+
117128
// ==== ----------------------------------------------------------------------------------------------------------------
118129
// MARK: Span Event
119130

Tests/TracingTests/DynamicTracepointTracerTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ extension DynamicTracepointTestTracer {
276276
// nothing
277277
}
278278

279-
func recordError(_ error: Error) {
279+
func recordError(_ error: Error, attributes: SpanAttributes) {
280280
print("")
281281
}
282282

Tests/TracingTests/TestTracer.swift

+8-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import Tracing
2121
/// Only intended to be used in single-threaded testing.
2222
final class TestTracer: Tracer {
2323
private(set) var spans = [TestSpan]()
24-
var onEndSpan: (Span) -> Void = { _ in }
24+
var onEndSpan: (TestSpan) -> Void = { _ in }
2525

2626
func startSpan(
2727
_ operationName: String,
@@ -103,6 +103,8 @@ final class TestSpan: Span {
103103
private let startTime: DispatchWallTime
104104
private(set) var endTime: DispatchWallTime?
105105

106+
private(set) var recordedErrors: [(Error, SpanAttributes)] = []
107+
106108
var operationName: String
107109
let baggage: Baggage
108110

@@ -122,14 +124,14 @@ final class TestSpan: Span {
122124

123125
private(set) var isRecording = false
124126

125-
let onEnd: (Span) -> Void
127+
let onEnd: (TestSpan) -> Void
126128

127129
init(
128130
operationName: String,
129131
startTime: DispatchWallTime,
130132
baggage: Baggage,
131133
kind: SpanKind,
132-
onEnd: @escaping (Span) -> Void
134+
onEnd: @escaping (TestSpan) -> Void
133135
) {
134136
self.operationName = operationName
135137
self.startTime = startTime
@@ -151,7 +153,9 @@ final class TestSpan: Span {
151153
self.events.append(event)
152154
}
153155

154-
func recordError(_ error: Error) {}
156+
func recordError(_ error: Error, attributes: SpanAttributes) {
157+
self.recordedErrors.append((error, attributes))
158+
}
155159

156160
func end(at time: DispatchWallTime) {
157161
self.endTime = time

Tests/TracingTests/TracedLockTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ private final class TracedLockPrintlnTracer: Tracer {
151151
self.events.append(event)
152152
}
153153

154-
func recordError(_ error: Error) {}
154+
func recordError(_ error: Error, attributes: SpanAttributes) {}
155155

156156
func end(at time: DispatchWallTime) {
157157
self.endTime = time

Tests/TracingTests/TracerTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ extension TracerTests {
3535
("testWithSpan_automaticBaggagePropagation_async", testWithSpan_automaticBaggagePropagation_async),
3636
("testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation", testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation),
3737
("testWithSpan_automaticBaggagePropagation_async_throws", testWithSpan_automaticBaggagePropagation_async_throws),
38+
("testWithSpan_recordErrorWithAttributes", testWithSpan_recordErrorWithAttributes),
3839
]
3940
}
4041
}

Tests/TracingTests/TracerTests.swift

+31
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,37 @@ final class TracerTests: XCTestCase {
253253
#endif
254254
}
255255

256+
func testWithSpan_recordErrorWithAttributes() throws {
257+
#if swift(>=5.5) && canImport(_Concurrency)
258+
guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) else {
259+
throw XCTSkip("Task locals are not supported on this platform.")
260+
}
261+
262+
let tracer = TestTracer()
263+
InstrumentationSystem.bootstrapInternal(tracer)
264+
defer {
265+
InstrumentationSystem.bootstrapInternal(nil)
266+
}
267+
268+
var endedSpan: TestSpan?
269+
tracer.onEndSpan = { span in endedSpan = span }
270+
271+
let errorToThrow = ExampleSpanError()
272+
let attrsForError: SpanAttributes = ["attr": "value"]
273+
274+
tracer.withSpan("hello") { span in
275+
span.recordError(errorToThrow, attributes: attrsForError)
276+
}
277+
278+
XCTAssertTrue(endedSpan != nil)
279+
XCTAssertEqual(endedSpan!.recordedErrors.count, 1)
280+
let error = endedSpan!.recordedErrors.first!.0
281+
XCTAssertEqual(error as! ExampleSpanError, errorToThrow)
282+
let attrs = endedSpan!.recordedErrors.first!.1
283+
XCTAssertEqual(attrs, attrsForError)
284+
#endif
285+
}
286+
256287
#if swift(>=5.5) && canImport(_Concurrency)
257288
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
258289
/// Helper method to execute async operations until we can use async tests (currently incompatible with the generated LinuxMain file).

0 commit comments

Comments
 (0)