From bb00b989387925436e9f358c2621e37cec9cdc14 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 19 Aug 2022 12:22:34 +0900 Subject: [PATCH 1/3] +span additional withSpan to ease entering async world from non-async world --- Sources/Tracing/Tracer.swift | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Sources/Tracing/Tracer.swift b/Sources/Tracing/Tracer.swift index 241dda1..9dfd398 100644 --- a/Sources/Tracing/Tracer.swift +++ b/Sources/Tracing/Tracer.swift @@ -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( + _ operationName: String, + baggage: Baggage?, + 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 From 9e8edfbffb203268b2ac60bb649b9b3f36611a72 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 19 Aug 2022 12:29:30 +0900 Subject: [PATCH 2/3] add test --- Tests/TracingTests/TracerTests.swift | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Tests/TracingTests/TracerTests.swift b/Tests/TracingTests/TracerTests.swift index 02141cd..d4c3cd1 100644 --- a/Tests/TracingTests/TracerTests.swift +++ b/Tests/TracingTests/TracerTests.swift @@ -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 { From 03af4c2047599545bccd51024f9ae63ff8b4bcb1 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 19 Aug 2022 17:39:46 +0900 Subject: [PATCH 3/3] fix API, always require Baggage, people should pass .topLevel --- Sources/Tracing/Tracer.swift | 4 ++-- Tests/TracingTests/TracerTests+XCTest.swift | 1 + Tests/TracingTests/TracerTests.swift | 23 +++++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Sources/Tracing/Tracer.swift b/Sources/Tracing/Tracer.swift index 9dfd398..91857d7 100644 --- a/Sources/Tracing/Tracer.swift +++ b/Sources/Tracing/Tracer.swift @@ -166,11 +166,11 @@ extension Tracer { /// - Throws: the error the `operation` has thrown (if any) public func withSpan( _ operationName: String, - baggage: Baggage?, + baggage: Baggage, ofKind kind: SpanKind = .internal, _ operation: (Span) async throws -> T ) async rethrows -> T { - let span = self.startSpan(operationName, baggage: baggage ?? .topLevel, ofKind: kind) + let span = self.startSpan(operationName, baggage: baggage, ofKind: kind) defer { span.end() } do { return try await Baggage.$current.withValue(span.baggage) { diff --git a/Tests/TracingTests/TracerTests+XCTest.swift b/Tests/TracingTests/TracerTests+XCTest.swift index 1737ae5..360df37 100644 --- a/Tests/TracingTests/TracerTests+XCTest.swift +++ b/Tests/TracingTests/TracerTests+XCTest.swift @@ -33,6 +33,7 @@ extension TracerTests { ("testWithSpan_automaticBaggagePropagation_sync", testWithSpan_automaticBaggagePropagation_sync), ("testWithSpan_automaticBaggagePropagation_sync_throws", testWithSpan_automaticBaggagePropagation_sync_throws), ("testWithSpan_automaticBaggagePropagation_async", testWithSpan_automaticBaggagePropagation_async), + ("testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation", testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation), ("testWithSpan_automaticBaggagePropagation_async_throws", testWithSpan_automaticBaggagePropagation_async_throws), ] } diff --git a/Tests/TracingTests/TracerTests.swift b/Tests/TracingTests/TracerTests.swift index d4c3cd1..987f3e7 100644 --- a/Tests/TracingTests/TracerTests.swift +++ b/Tests/TracingTests/TracerTests.swift @@ -187,8 +187,7 @@ final class TracerTests: XCTestCase { #endif } - - func testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation() async throws { + func testWithSpan_enterFromNonAsyncCode_passBaggage_asyncOperation() 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.") @@ -207,16 +206,18 @@ final class TracerTests: XCTestCase { "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) - } + self.testAsync { + 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) + XCTAssertEqual(value, "world") + XCTAssertTrue(spanEnded) + } #endif }