diff --git a/Sources/Datadog/OpenTracing/OTTracer.swift b/Sources/Datadog/OpenTracing/OTTracer.swift index 48c15c9ba3..d3dea59d09 100644 --- a/Sources/Datadog/OpenTracing/OTTracer.swift +++ b/Sources/Datadog/OpenTracing/OTTracer.swift @@ -8,12 +8,14 @@ public protocol OTTracer { /// Start a new span with the given operation name. /// /// - parameter operationName: the operation name for the newly-started span - /// - parameter references: an optional list of Reference instances to record causal relationships - /// - parameter tags: a set of tag keys and values per OTSpan#setTag:value:, or nil to start with + /// - parameter references: an optional list of Reference instances to record causal relationships. If no + /// reference is provided, and an active span exists in the current execution context + /// the active span will be used as the parent. + /// - parameter tags: a set of tag keys and values per `OTSpan#setTag:value:`, or `nil` to start with /// an empty tag map - /// - parameter startTime: an explicitly specified start timestamp for the OTSpan, or nil to use the + /// - parameter startTime: an explicitly specified start timestamp for the `OTSpan`, or `nil` to use the /// current walltime - /// - returns: a valid Span instance; it is the caller's responsibility to call finish() + /// - returns: a valid Span instance; it is the caller's responsibility to call `finish()`. func startSpan( operationName: String, references: [OTReference]?, @@ -21,6 +23,20 @@ public protocol OTTracer { startTime: Date? ) -> OTSpan + /// Start a new root span with the given operation name. + /// - Parameters: + /// - operationName: the operation name for the newly-started span + /// - tags: a set of tag keys and values per `OTSpan#setTag:value:`, or `nil` to start with + /// an empty tag map + /// - startTime: an explicitly specified start timestamp for the `OTSpan`, or `nil` to use the + /// current walltime + /// - returns: a valid Span instance; it is the caller's responsibility to call `finish()`. + func startRootSpan( + operationName: String, + tags: [String: Encodable]?, + startTime: Date? + ) -> OTSpan + /// Transfer the span information into the carrier of the given format. /// /// For example: @@ -67,7 +83,9 @@ public extension OTTracer { /// Start a new span with the given operation name. /// /// - parameter operationName: the operation name for the newly-started span - /// - parameter parent: span context that will be a parent reference + /// - parameter parent: span context that will be a parent reference. If no + /// reference is provided, and an active span exists in the current execution context + /// the active span will be used as the parent. /// - parameter tags: a set of tag keys and values per OTSpan#setTag:value:, or nil to start with /// an empty tag map /// - parameter startTime: an explicitly specified start timestamp for the OTSpan, or nil to use the @@ -87,4 +105,24 @@ public extension OTTracer { startTime: startTime ) } + + /// Start a new root span with the given operation name. + /// - Parameters: + /// - operationName: the operation name for the newly-started span + /// - tags: a set of tag keys and values per `OTSpan#setTag:value:`, or `nil` to start with + /// an empty tag map + /// - startTime: an explicitly specified start timestamp for the `OTSpan`, or `nil` to use the + /// current walltime + /// - returns: a valid Span instance; it is the caller's responsibility to call `finish()`. + func startRootSpan( + operationName: String, + tags: [String: Encodable]? = nil, + startTime: Date? = nil + ) -> OTSpan { + return self.startRootSpan( + operationName: operationName, + tags: tags, + startTime: startTime + ) + } } diff --git a/Sources/Datadog/Tracer.swift b/Sources/Datadog/Tracer.swift index cc2ba1df6e..66a4e64415 100644 --- a/Sources/Datadog/Tracer.swift +++ b/Sources/Datadog/Tracer.swift @@ -134,10 +134,18 @@ public class Tracer: OTTracer { // MARK: - Open Tracing interface public func startSpan(operationName: String, references: [OTReference]? = nil, tags: [String: Encodable]? = nil, startTime: Date? = nil) -> OTSpan { - let parentSpanContext = references?.compactMap { $0.context.dd }.last - let spanContext = createSpanContext(parentSpanContext: parentSpanContext) + let parentSpanContext = references?.compactMap { $0.context.dd }.last ?? activeSpan?.context as? DDSpanContext return startSpan( - spanContext: spanContext, + spanContext: createSpanContext(parentSpanContext: parentSpanContext), + operationName: operationName, + tags: tags, + startTime: startTime + ) + } + + public func startRootSpan(operationName: String, tags: [String: Encodable]? = nil, startTime: Date? = nil) -> OTSpan { + return startSpan( + spanContext: createSpanContext(parentSpanContext: nil), operationName: operationName, tags: tags, startTime: startTime @@ -160,12 +168,11 @@ public class Tracer: OTTracer { // MARK: - Internal internal func createSpanContext(parentSpanContext: DDSpanContext? = nil) -> DDSpanContext { - let parentContext = parentSpanContext ?? activeSpan?.context as? DDSpanContext return DDSpanContext( - traceID: parentContext?.traceID ?? tracingUUIDGenerator.generateUnique(), + traceID: parentSpanContext?.traceID ?? tracingUUIDGenerator.generateUnique(), spanID: tracingUUIDGenerator.generateUnique(), - parentSpanID: parentContext?.spanID, - baggageItems: BaggageItems(targetQueue: queue, parentSpanItems: parentContext?.baggageItems) + parentSpanID: parentSpanContext?.spanID, + baggageItems: BaggageItems(targetQueue: queue, parentSpanItems: parentSpanContext?.baggageItems) ) } diff --git a/Tests/DatadogTests/Datadog/TracerTests.swift b/Tests/DatadogTests/Datadog/TracerTests.swift index 8d4e02dd39..34ae289572 100644 --- a/Tests/DatadogTests/Datadog/TracerTests.swift +++ b/Tests/DatadogTests/Datadog/TracerTests.swift @@ -280,6 +280,37 @@ class TracerTests: XCTestCase { XCTAssertEqual(try spanMatchers[1].parentSpanID(), "0") } + func testStartingRootActiveSpanInAsynchronousJobs() throws { + TracingFeature.instance = .mockByRecordingSpanMatchers(directory: temporaryDirectory) + defer { TracingFeature.instance = nil } + + let tracer = Tracer.initialize(configuration: .init()) + let queue = DispatchQueue(label: "\(#function)") + + func makeFakeAPIRequest(on queue: DispatchQueue, completion: @escaping () -> Void) { + let requestSpan = tracer.startRootSpan(operationName: "request").setActive() + queue.asyncAfter(deadline: .now() + 1) { + let responseDecodingSpan = tracer.startSpan(operationName: "response decoding") + responseDecodingSpan.finish() + requestSpan.finish() + completion() + } + } + makeFakeAPIRequest(on: queue) {} + makeFakeAPIRequest(on: queue) {} + + let spanMatchers = try TracingFeature.waitAndReturnSpanMatchers(count: 4) + let response1Matcher = spanMatchers[0] + let request1Matcher = spanMatchers[1] + let response2Matcher = spanMatchers[2] + let request2Matcher = spanMatchers[3] + + XCTAssertEqual(try response1Matcher.parentSpanID(), try request1Matcher.spanID()) + XCTAssertEqual(try request1Matcher.parentSpanID(), "0") + XCTAssertEqual(try response2Matcher.parentSpanID(), try request2Matcher.spanID()) + XCTAssertEqual(try request2Matcher.parentSpanID(), "0") + } + // MARK: - Sending user info func testSendingUserInfo() throws {