Skip to content

+span additional withSpan to ease entering async world from non-async world #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Sources/Tracing/Tracer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,36 @@ extension Tracer {
throw error // rethrow
}
}

/// Execute the given async operation within a newly created `Span`,
/// started as a child of the passed in `Baggage` or as a root span if `nil`.
///
/// DO NOT `end()` the passed in span manually. It will be ended automatically when the `operation` returns.
///
/// - Parameters:
/// - operationName: The name of the operation being traced. This may be a handler function, database call, ...
/// - baggage: The baggage to be used for the newly created span. It may be obtained by the user manually from the `Baggage.current`,
// task local and modified before passing into this function. The baggage will be made the current task-local baggage for the duration of the `operation`.
/// - kind: The `SpanKind` of the `Span` to be created. Defaults to `.internal`.
/// - operation: operation to wrap in a span start/end and execute immediately
/// - Returns: the value returned by `operation`
/// - Throws: the error the `operation` has thrown (if any)
public func withSpan<T>(
_ operationName: String,
baggage: Baggage?,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep it more consistent with the synchronous counterpart of this withSpan, I'd make baggage a non-optional required parameter. If folks wanna start top-level let's make it obvious they do so.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, thank you!

And I'll fix the old swift issues.

ofKind kind: SpanKind = .internal,
_ operation: (Span) async throws -> T
) async rethrows -> T {
let span = self.startSpan(operationName, baggage: baggage ?? .topLevel, ofKind: kind)
defer { span.end() }
do {
return try await Baggage.$current.withValue(span.baggage) {
try await operation(span)
}
} catch {
span.recordError(error)
throw error // rethrow
}
}
}
#endif
33 changes: 33 additions & 0 deletions Tests/TracingTests/TracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,39 @@ final class TracerTests: XCTestCase {
#endif
}


func testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation() async throws {
#if swift(>=5.5) && canImport(_Concurrency)
guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) else {
throw XCTSkip("Task locals are not supported on this platform.")
}

let tracer = TestTracer()
InstrumentationSystem.bootstrapInternal(tracer)
defer {
InstrumentationSystem.bootstrapInternal(nil)
}

var spanEnded = false
tracer.onEndSpan = { _ in spanEnded = true }

func operation(span: Span) async -> String {
"world"
}

var fromNonAsyncWorld = Baggage.topLevel
fromNonAsyncWorld.traceID = "1234-5678"
let value = await tracer.withSpan("hello", baggage: fromNonAsyncWorld) { (span: Span) -> String in
XCTAssertEqual(span.baggage.traceID, Baggage.current?.traceID)
XCTAssertEqual(span.baggage.traceID, fromNonAsyncWorld.traceID)
return await operation(span: span)
}

XCTAssertEqual(value, "world")
XCTAssertTrue(spanEnded)
#endif
}

func testWithSpan_automaticBaggagePropagation_async_throws() throws {
#if swift(>=5.5) && canImport(_Concurrency)
guard #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) else {
Expand Down