-
Notifications
You must be signed in to change notification settings - Fork 48
Add TestTracer to new TestTracingKit #180
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Distributed Tracing open source project | ||
// | ||
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
@_spi(Locking) import Instrumentation | ||
import Tracing | ||
|
||
public struct TestSpan: Span { | ||
public let context: ServiceContext | ||
public let spanContext: TestSpanContext | ||
public let kind: SpanKind | ||
public let startInstant: any TracerInstant | ||
Comment on lines
+18
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add some doc comments to this? Also can all of these properties be |
||
|
||
init( | ||
operationName: String, | ||
context: ServiceContext, | ||
spanContext: TestSpanContext, | ||
kind: SpanKind, | ||
startInstant: any TracerInstant, | ||
onEnd: @escaping @Sendable (FinishedTestSpan) -> Void | ||
ktoso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) { | ||
self._operationName = LockedValueBox(operationName) | ||
self.context = context | ||
self.spanContext = spanContext | ||
self.kind = kind | ||
self.startInstant = startInstant | ||
self.onEnd = onEnd | ||
} | ||
|
||
public var isRecording: Bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Can we add doc comments to all of those please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These have docs on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we could add That said - probably worth doing in a separate PR. Filed #182 |
||
_isRecording.withValue { $0 } | ||
} | ||
|
||
public var operationName: String { | ||
get { | ||
_operationName.withValue { $0 } | ||
} | ||
nonmutating set { | ||
assertIsRecording() | ||
ktoso marked this conversation as resolved.
Show resolved
Hide resolved
|
||
_operationName.withValue { $0 = newValue } | ||
} | ||
} | ||
|
||
public var attributes: SpanAttributes { | ||
get { | ||
_attributes.withValue { $0 } | ||
} | ||
nonmutating set { | ||
assertIsRecording() | ||
_attributes.withValue { $0 = newValue } | ||
} | ||
} | ||
|
||
public var events: [SpanEvent] { | ||
_events.withValue { $0 } | ||
} | ||
|
||
public func addEvent(_ event: SpanEvent) { | ||
assertIsRecording() | ||
_events.withValue { $0.append(event) } | ||
} | ||
|
||
public var links: [SpanLink] { | ||
_links.withValue { $0 } | ||
} | ||
|
||
public func addLink(_ link: SpanLink) { | ||
assertIsRecording() | ||
_links.withValue { $0.append(link) } | ||
} | ||
|
||
public var errors: [RecordedError] { | ||
_errors.withValue { $0 } | ||
} | ||
|
||
public func recordError( | ||
_ error: any Error, | ||
attributes: SpanAttributes, | ||
at instant: @autoclosure () -> some TracerInstant | ||
) { | ||
assertIsRecording() | ||
_errors.withValue { | ||
$0.append(RecordedError(error: error, attributes: attributes, instant: instant())) | ||
} | ||
} | ||
|
||
public var status: SpanStatus? { | ||
_status.withValue { $0 } | ||
} | ||
|
||
public func setStatus(_ status: SpanStatus) { | ||
assertIsRecording() | ||
_status.withValue { $0 = status } | ||
} | ||
|
||
public func end(at instant: @autoclosure () -> some TracerInstant) { | ||
assertIsRecording() | ||
let finishedSpan = FinishedTestSpan( | ||
operationName: operationName, | ||
context: context, | ||
kind: kind, | ||
spanContext: spanContext, | ||
startInstant: startInstant, | ||
endInstant: instant(), | ||
attributes: attributes, | ||
events: events, | ||
links: links, | ||
errors: errors, | ||
status: status | ||
) | ||
_isRecording.withValue { $0 = false } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is racy with the assertIsRecording; you have to check + swap to false at the same time as asserting, otherwise you can get two tasks |
||
onEnd(finishedSpan) | ||
} | ||
|
||
public struct RecordedError: Sendable { | ||
public let error: Error | ||
public let attributes: SpanAttributes | ||
public let instant: any TracerInstant | ||
} | ||
|
||
private let _operationName: LockedValueBox<String> | ||
private let _attributes = LockedValueBox<SpanAttributes>([:]) | ||
private let _events = LockedValueBox<[SpanEvent]>([]) | ||
private let _links = LockedValueBox<[SpanLink]>([]) | ||
private let _errors = LockedValueBox<[RecordedError]>([]) | ||
private let _status = LockedValueBox<SpanStatus?>(nil) | ||
private let _isRecording = LockedValueBox<Bool>(true) | ||
private let onEnd: @Sendable (FinishedTestSpan) -> Void | ||
|
||
private func assertIsRecording( | ||
file: StaticString = #file, | ||
line: UInt = #line | ||
) { | ||
assert( | ||
_isRecording.withValue { $0 } == true, | ||
"Attempted to mutate already ended span.", | ||
file: file, | ||
line: line | ||
) | ||
} | ||
} | ||
|
||
public struct FinishedTestSpan: Sendable { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move this into a new file, add doc comments and make all the properties There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure this needs new file but +1 on the var, there's no reason not to :) |
||
public let operationName: String | ||
public let context: ServiceContext | ||
public let kind: SpanKind | ||
public let spanContext: TestSpanContext | ||
public let startInstant: any TracerInstant | ||
public let endInstant: any TracerInstant | ||
public let attributes: SpanAttributes | ||
public let events: [SpanEvent] | ||
public let links: [SpanLink] | ||
public let errors: [TestSpan.RecordedError] | ||
public let status: SpanStatus? | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Distributed Tracing open source project | ||
// | ||
// Copyright (c) 2025 Apple Inc. and the Swift Distributed Tracing project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of Swift Distributed Tracing project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import ServiceContextModule | ||
|
||
public struct TestSpanContext: Sendable, Hashable { | ||
public let traceID: String | ||
public let spanID: String | ||
public let parentSpanID: String? | ||
|
||
public init(traceID: String, spanID: String, parentSpanID: String?) { | ||
self.traceID = traceID | ||
self.spanID = spanID | ||
self.parentSpanID = parentSpanID | ||
} | ||
} | ||
|
||
extension ServiceContext { | ||
var testSpanContext: TestSpanContext? { | ||
get { | ||
self[TestSpanContextKey.self] | ||
} | ||
set { | ||
self[TestSpanContextKey.self] = newValue | ||
} | ||
} | ||
} | ||
|
||
private struct TestSpanContextKey: ServiceContextKey { | ||
typealias Value = TestSpanContext | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should call this InMemoryTracing maybe, and not have it be strictly called "just for testing" there may be various use cases to use this