Skip to content

Commit c0213fe

Browse files
authored
+span implement withSpan() convenience (#13)
* +span implement withSpan() convenience * Delete OSSignpostTracing.swift
1 parent e636e34 commit c0213fe

File tree

4 files changed

+108
-6
lines changed

4 files changed

+108
-6
lines changed

Sources/Tracing/NoOpTracer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import Dispatch
1717

1818
/// No operation Tracer, used when no tracing is required.
1919
public struct NoOpTracer: Tracer {
20+
public init() {}
21+
2022
public func startSpan(
2123
_ operationName: String,
2224
baggage: Baggage,

Sources/Tracing/Tracer.swift

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public protocol Tracer: Instrument {
4242
}
4343

4444
extension Tracer {
45-
/// Start a new `Span` with the given `Baggage` starting at `Timestamp.now()`.
45+
/// Start a new `Span` with the given `Baggage` starting at `DispatchWallTime.now()`.
4646
///
4747
/// - Parameters:
4848
/// - operationName: The name of the operation being traced. This may be a handler function, database call, ...
@@ -55,15 +55,17 @@ extension Tracer {
5555
) -> Span {
5656
return self.startSpan(operationName, baggage: baggage, ofKind: kind, at: .now())
5757
}
58+
}
5859

59-
// ==== --------------------------------------------------------------------
60-
// MARK: LoggingContext accepting
60+
// ==== ----------------------------------------------------------------------------------------------------------------
61+
// MARK: Span creation: with `LoggingContext`
6162

62-
/// Start a new `Span` with the given `Baggage` starting at `Timestamp.now()`.
63+
extension Tracer {
64+
/// Start a new `Span` with the given `Baggage` starting at `DispatchWallTime.now()`.
6365
///
6466
/// - Parameters:
6567
/// - operationName: The name of the operation being traced. This may be a handler function, database call, ...
66-
/// - context: Logging context containing a `Baggage` whichi may contain trace identifiers of a parent `Span`.
68+
/// - context: Logging context containing a `Baggage` which may contain trace identifiers of a parent `Span`.
6769
/// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`.
6870
public func startSpan(
6971
_ operationName: String,
@@ -73,3 +75,62 @@ extension Tracer {
7375
return self.startSpan(operationName, baggage: context.baggage, ofKind: kind, at: .now())
7476
}
7577
}
78+
79+
// ==== ----------------------------------------------------------------------------------------------------------------
80+
// MARK: Starting spans: `withSpan`
81+
82+
extension Tracer {
83+
/// Execute a specific task within a newly created `Span`.
84+
///
85+
/// DO NOT `end()` the passed in span manually. It will be ended automatically when the `function` returns.
86+
///
87+
/// - Parameters:
88+
/// - operationName: The name of the operation being traced. This may be a handler function, database call, ...
89+
/// - context: Logging context containing a `Baggage` which may contain trace identifiers of a parent `Span`.
90+
/// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`.
91+
/// - function: function to wrap in a span start/end and execute immediately
92+
/// - Returns: the value returned by `function`
93+
/// - Throws: the error the `function` has thrown (if any)
94+
public func withSpan<T>(
95+
_ operationName: String,
96+
context: LoggingContext,
97+
ofKind kind: SpanKind = .internal,
98+
_ function: (Span) throws -> T
99+
) rethrows -> T {
100+
let span = self.startSpan(operationName, context: context, ofKind: kind)
101+
do {
102+
return try function(span)
103+
} catch {
104+
span.recordError(error)
105+
span.end()
106+
throw error // rethrow
107+
}
108+
}
109+
110+
/// Execute a specific task within a newly created `Span`.
111+
///
112+
/// DO NOT `end()` the passed in span manually. It will be ended automatically when the `function` returns.
113+
///
114+
/// - Parameters:
115+
/// - operationName: The name of the operation being traced. This may be a handler function, database call, ...
116+
/// - baggage: Baggage potentially containing trace identifiers of a parent `Span`.
117+
/// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`.
118+
/// - function: function to wrap in a span start/end and execute immediately
119+
/// - Returns: the value returned by `function`
120+
/// - Throws: the error the `function` has thrown (if any)
121+
public func withSpan<T>(
122+
_ operationName: String,
123+
baggage: Baggage,
124+
ofKind kind: SpanKind = .internal,
125+
_ function: (Span) throws -> T
126+
) rethrows -> T {
127+
let span = self.startSpan(operationName, baggage: baggage, ofKind: kind)
128+
do {
129+
return try function(span)
130+
} catch {
131+
span.recordError(error)
132+
span.end()
133+
throw error // rethrow
134+
}
135+
}
136+
}

Tests/TracingTests/TracerTests+XCTest.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ extension TracerTests {
2727
return [
2828
("testContextPropagation", testContextPropagation),
2929
("testContextPropagationWithNoOpSpan", testContextPropagationWithNoOpSpan),
30+
("testWithSpan_success", testWithSpan_success),
31+
("testWithSpan_throws", testWithSpan_throws),
3032
]
3133
}
3234
}

Tests/TracingTests/TracerTests.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ final class TracerTests: XCTestCase {
2424

2525
func testContextPropagation() {
2626
let tracer = TestTracer()
27-
InstrumentationSystem.bootstrap(tracer)
27+
InstrumentationSystem.bootstrapInternal(tracer)
28+
defer {
29+
InstrumentationSystem.bootstrapInternal(NoOpTracer())
30+
}
2831

2932
let httpServer = FakeHTTPServer { context, _, client -> FakeHTTPResponse in
3033
client.performRequest(context, request: FakeHTTPRequest(path: "/test", headers: []))
@@ -52,8 +55,42 @@ final class TracerTests: XCTestCase {
5255
XCTAssertEqual(httpServer.client.baggages.count, 1)
5356
XCTAssertEqual(httpServer.client.baggages.first?.traceID, "test")
5457
}
58+
59+
func testWithSpan_success() {
60+
let tracer = TestTracer()
61+
InstrumentationSystem.bootstrapInternal(tracer)
62+
defer {
63+
InstrumentationSystem.bootstrapInternal(NoOpTracer())
64+
}
65+
66+
let value = tracer.withSpan("hello", baggage: .topLevel) { _ in
67+
"yes"
68+
}
69+
70+
XCTAssertEqual(value, "yes")
71+
}
72+
73+
func testWithSpan_throws() {
74+
let tracer = TestTracer()
75+
InstrumentationSystem.bootstrapInternal(tracer)
76+
defer {
77+
InstrumentationSystem.bootstrapInternal(NoOpTracer())
78+
}
79+
80+
do {
81+
_ = try tracer.withSpan("hello", baggage: .topLevel) { _ in
82+
throw ExampleSpanError()
83+
}
84+
} catch {
85+
XCTAssertEqual(error as? ExampleSpanError, ExampleSpanError())
86+
return
87+
}
88+
XCTFail("Should have throw")
89+
}
5590
}
5691

92+
struct ExampleSpanError: Error, Equatable {}
93+
5794
// MARK: - Fake HTTP Server
5895

5996
typealias HTTPHeaders = [(String, String)]

0 commit comments

Comments
 (0)