Skip to content

Commit 96cb4c5

Browse files
committed
Polish up impl, remove pasted test tracer, add more tests
1 parent d1dbd7d commit 96cb4c5

File tree

11 files changed

+302
-495
lines changed

11 files changed

+302
-495
lines changed

[email protected]

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ let package = Package(
8383
.product(name: "Algorithms", package: "swift-algorithms"),
8484
// Observability support
8585
.product(name: "Tracing", package: "swift-distributed-tracing", condition: .when(traits: ["TracingSupport"])),
86+
.product(name: "InMemoryTracing", package: "swift-distributed-tracing", condition: .when(traits: ["TracingSupport"])),
8687
],
8788
swiftSettings: strictConcurrencySettings
8889
),
@@ -102,6 +103,9 @@ let package = Package(
102103
.product(name: "Logging", package: "swift-log"),
103104
.product(name: "Atomics", package: "swift-atomics"),
104105
.product(name: "Algorithms", package: "swift-algorithms"),
106+
// Observability support
107+
.product(name: "Tracing", package: "swift-distributed-tracing", condition: .when(traits: ["TracingSupport"])),
108+
.product(name: "InMemoryTracing", package: "swift-distributed-tracing", condition: .when(traits: ["TracingSupport"])),
105109
],
106110
resources: [
107111
.copy("Resources/self_signed_cert.pem"),

Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,27 @@ extension HTTPClient {
3939
deadline: NIODeadline,
4040
logger: Logger? = nil
4141
) async throws -> HTTPClientResponse {
42-
try await self.executeAndFollowRedirectsIfNeeded(
43-
request,
44-
deadline: deadline,
45-
logger: logger ?? Self.loggingDisabled,
46-
redirectState: RedirectState(self.configuration.redirectConfiguration.mode, initialURL: request.url)
47-
)
42+
func doExecute() async throws -> HTTPClientResponse {
43+
try await self.executeAndFollowRedirectsIfNeeded(
44+
request,
45+
deadline: deadline,
46+
logger: logger ?? Self.loggingDisabled,
47+
redirectState: RedirectState(self.configuration.redirectConfiguration.mode, initialURL: request.url)
48+
)
49+
}
50+
51+
#if TracingSupport
52+
if let tracer = self.tracer {
53+
return try await tracer.withSpan("\(request.method)") { span -> (HTTPClientResponse) in
54+
let attr = self.configuration.tracing.attributeKeys
55+
span.attributes[attr.requestMethod] = request.method.rawValue
56+
// Set more attributes on the span
57+
return try await doExecute()
58+
}
59+
}
60+
#endif
61+
62+
return try await doExecute()
4863
}
4964
}
5065

Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ final class Transaction:
5252
span: (any Span)?,
5353
responseContinuation: CheckedContinuation<HTTPClientResponse, Error>
5454
) {
55-
print("[swift] new transaction WITH SPAN = \(request); span = \(span)")
5655
self.request = request
5756
self.requestOptions = requestOptions
5857
self.logger = logger

Sources/AsyncHTTPClient/HTTPClient.swift

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ let globalRequestID = ManagedAtomic(0)
6161
/// }
6262
/// }
6363
/// ```
64-
// #if TracingSupport
65-
// @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
66-
// #endif
6764
public final class HTTPClient: Sendable {
6865
/// The `EventLoopGroup` in use by this ``HTTPClient``.
6966
///
@@ -80,8 +77,9 @@ public final class HTTPClient: Sendable {
8077

8178
#if TracingSupport
8279
@_spi(Tracing)
83-
public var tracer: (any Tracer)? { // Tracer requires macOS 10.15... so we're forced into annotating HTTPClient :-/
84-
configuration.tracer
80+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) // for TaskLocal ServiceContext
81+
public var tracer: (any Tracer)? {
82+
configuration.tracing.tracer
8583
}
8684
#endif // TracingSupport
8785

@@ -368,8 +366,7 @@ public final class HTTPClient: Sendable {
368366
/// - url: Remote URL.
369367
/// - deadline: Point in time by which the request must complete.
370368
public func get(url: String, deadline: NIODeadline? = nil) -> EventLoopFuture<Response> {
371-
print("[swift] GET \(url)")
372-
return self.get(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled)
369+
self.get(url: url, deadline: deadline, logger: HTTPClient.loggingDisabled)
373370
}
374371

375372
/// Execute `GET` request using specified URL.
@@ -496,7 +493,6 @@ public final class HTTPClient: Sendable {
496493
) -> EventLoopFuture<Response> {
497494
do {
498495
let request = try Request(url: url, method: method, body: body)
499-
print("[swift] request \(request)")
500496
return self.execute(request: request, deadline: deadline, logger: logger ?? HTTPClient.loggingDisabled)
501497
} catch {
502498
return self.eventLoopGroup.any().makeFailedFuture(error)
@@ -575,7 +571,7 @@ public final class HTTPClient: Sendable {
575571
/// - deadline: Point in time by which the request must complete.
576572
/// - logger: The logger to use for this request.
577573
public func execute(request: Request, deadline: NIODeadline? = nil, logger: Logger) -> EventLoopFuture<Response> {
578-
let accumulator = ResponseAccumulator(request: request) // FIXME: end here?
574+
let accumulator = ResponseAccumulator(request: request)
579575
return self.execute(request: request, delegate: accumulator, deadline: deadline, logger: logger).futureResult
580576
}
581577

@@ -688,8 +684,6 @@ public final class HTTPClient: Sendable {
688684
deadline: NIODeadline? = nil,
689685
logger: Logger?
690686
) -> Task<Delegate.Response> {
691-
print("[swift] EXECUTE HERE")
692-
assert(self.configuration.tracer != nil)
693687
return self._execute(
694688
request: request,
695689
delegate: delegate,
@@ -717,14 +711,28 @@ public final class HTTPClient: Sendable {
717711
eventLoop eventLoopPreference: EventLoopPreference,
718712
deadline: NIODeadline? = nil,
719713
logger originalLogger: Logger?,
720-
redirectState: RedirectState?
714+
redirectState: RedirectState?,
721715
) -> Task<Delegate.Response> {
722-
assert(self.configuration.tracer != nil)
723716
let logger = (originalLogger ?? HTTPClient.loggingDisabled).attachingRequestInformation(
724717
request,
725718
requestID: globalRequestID.wrappingIncrementThenLoad(ordering: .relaxed)
726719
)
727-
let tracer = self.configuration.tracer
720+
721+
// #if TracingSupport
722+
// let span: (any Span)? // we may be still executing the same span, e.g. under redirection etc.
723+
// if let activeSpan {
724+
// span = activeSpan
725+
// } else if let tracer = self.tracer {
726+
// let s = tracer.startSpan(request.method.rawValue)
727+
// let attrs = self.configuration.tracing.attributeKeys
728+
// s.attributes[attrs.requestMethod] = request.method.rawValue
729+
// s.attributes["loc"] = "\(#fileID):\(#line)"
730+
// span = s
731+
// } else {
732+
// span = nil
733+
// }
734+
// #endif
735+
728736
let taskEL: EventLoop
729737
switch eventLoopPreference.preference {
730738
case .indifferent:
@@ -760,10 +768,14 @@ public final class HTTPClient: Sendable {
760768
return nil
761769
case .shuttingDown, .shutDown:
762770
logger.debug("client is shutting down, failing request")
763-
// TODO: span immediately failed ops
771+
let error = HTTPClientError.alreadyShutdown
772+
// #if TracingSupport
773+
// span?.recordError(error)
774+
// span?.end()
775+
// #endif
764776
return Task<Delegate.Response>.failedTask(
765777
eventLoop: taskEL,
766-
error: HTTPClientError.alreadyShutdown,
778+
error: error,
767779
logger: logger,
768780
tracer: tracer,
769781
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool
@@ -778,7 +790,6 @@ public final class HTTPClient: Sendable {
778790
let redirectHandler: RedirectHandler<Delegate.Response>? = {
779791
guard let redirectState = redirectState else { return nil }
780792

781-
// TODO: span for redirects?
782793
return .init(request: request, redirectState: redirectState) { newRequest, newRedirectState in
783794
self._execute(
784795
request: newRequest,
@@ -791,26 +802,12 @@ public final class HTTPClient: Sendable {
791802
}
792803
}()
793804

794-
print("[swift](\(#fileID):\(#line)) tracer was = \(tracer)")
795-
precondition(tracer != nil, "Expected tracer to be set in \(#function) @ \(#fileID):\(#line)")
796-
#if TracingSupport
797-
let task = Task<Delegate.Response>(
798-
eventLoop: taskEL,
799-
logger: logger,
800-
tracer: tracer,
801-
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool
802-
)
803-
#else
804805
let task = Task<Delegate.Response>(
805-
eventLoop: taskEL,
806-
logger: logger,
807-
tracer: tracer,
808-
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool
809-
)
810-
#endif // TracingSupport
811-
812-
precondition(task.tracer != nil, "Expected tracer to be set in \(#function)")
813-
print("[swift] task.tracer was nil? \(task.tracer == nil)")
806+
eventLoop: taskEL,
807+
logger: logger,
808+
tracer: tracer,
809+
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool
810+
)
814811

815812
do {
816813
let requestBag = try RequestBag(
@@ -924,12 +921,7 @@ public final class HTTPClient: Sendable {
924921
public var http2StreamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)?
925922

926923
#if TracingSupport
927-
/// Tracer that should be used by the HTTPClient.
928-
///
929-
/// This is selected at configuration creation time, and if no tracer is passed explicitly,
930-
/// (including `nil` in order to disable traces), the default global bootstrapped tracer will
931-
/// be stored in this property, and used for all subsequent requests made by this client.
932-
public var tracer: (any Tracer)? = InstrumentationSystem.tracer
924+
public var tracing: TracingConfiguration = .init()
933925
#endif
934926

935927
public init(
@@ -1059,9 +1051,6 @@ public final class HTTPClient: Sendable {
10591051
self.http1_1ConnectionDebugInitializer = http1_1ConnectionDebugInitializer
10601052
self.http2ConnectionDebugInitializer = http2ConnectionDebugInitializer
10611053
self.http2StreamChannelDebugInitializer = http2StreamChannelDebugInitializer
1062-
#if TracingSupport
1063-
self.tracer = InstrumentationSystem.tracer
1064-
#endif
10651054
}
10661055

10671056
#if TracingSupport
@@ -1076,7 +1065,7 @@ public final class HTTPClient: Sendable {
10761065
http1_1ConnectionDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)? = nil,
10771066
http2ConnectionDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)? = nil,
10781067
http2StreamChannelDebugInitializer: (@Sendable (Channel) -> EventLoopFuture<Void>)? = nil,
1079-
tracer: (any Tracer)? = InstrumentationSystem.tracer
1068+
tracing: TracingConfiguration = .init()
10801069
) {
10811070
self.init(
10821071
tlsConfiguration: tlsConfiguration,
@@ -1090,11 +1079,62 @@ public final class HTTPClient: Sendable {
10901079
self.http1_1ConnectionDebugInitializer = http1_1ConnectionDebugInitializer
10911080
self.http2ConnectionDebugInitializer = http2ConnectionDebugInitializer
10921081
self.http2StreamChannelDebugInitializer = http2StreamChannelDebugInitializer
1093-
self.tracer = tracer
1082+
self.tracing = tracing
10941083
}
10951084
#endif
10961085
}
10971086

1087+
#if TracingSupport
1088+
public struct TracingConfiguration: Sendable {
1089+
1090+
@usableFromInline
1091+
var _tracer: Optional<any Sendable> // erasure trick so we don't have to make Configuration @available
1092+
1093+
/// Tracer that should be used by the HTTPClient.
1094+
///
1095+
/// This is selected at configuration creation time, and if no tracer is passed explicitly,
1096+
/// (including `nil` in order to disable traces), the default global bootstrapped tracer will
1097+
/// be stored in this property, and used for all subsequent requests made by this client.
1098+
@inlinable
1099+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
1100+
public var tracer: (any Tracer)? {
1101+
get {
1102+
guard let _tracer else {
1103+
return nil
1104+
}
1105+
return _tracer as! (any Tracer)?
1106+
}
1107+
set {
1108+
self._tracer = newValue
1109+
}
1110+
}
1111+
1112+
public var attributeKeys: AttributeKeys
1113+
1114+
public init(
1115+
tracer: (any Tracer)? = InstrumentationSystem.tracer,
1116+
attributeKeys: AttributeKeys = .init()
1117+
) {
1118+
self._tracer = tracer
1119+
self.attributeKeys = attributeKeys
1120+
}
1121+
1122+
/// Span attribute keys that the HTTPClient should set automatically.
1123+
/// This struct allows the configuration of the attribute names (keys) which will be used for the apropriate values.
1124+
public struct AttributeKeys: Sendable {
1125+
public var requestMethod: String = "http.request.method"
1126+
public var requestBodySize: String = "http.request.body.size"
1127+
1128+
public var responseBodySize: String = "http.response.size"
1129+
public var responseStatusCode: String = "http.status_code"
1130+
1131+
public var httpFlavor: String = "http.flavor"
1132+
1133+
public init() {}
1134+
}
1135+
}
1136+
#endif
1137+
10981138
/// Specifies how `EventLoopGroup` will be created and establishes lifecycle ownership.
10991139
public enum EventLoopGroupProvider {
11001140
/// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for its lifecycle.

Sources/AsyncHTTPClient/RequestBag.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate & Sendable>: Sendabl
5454
var consumeBodyPartStackDepth: Int
5555
// if a redirect occurs, we store the task for it so we can propagate cancellation
5656
var redirectTask: HTTPClient.Task<Delegate.Response>? = nil
57-
// The current span, if active.
57+
58+
#if TracingSupport
59+
// The current span, representing the entire request/response made by an execute call.
5860
var activeSpan: (any Span)? = nil
61+
#endif // TracingSupport
5962
}
6063

6164
private let loopBoundState: NIOLoopBoundBox<LoopBoundState>
@@ -66,13 +69,11 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate & Sendable>: Sendabl
6669
self.task.logger
6770
}
6871

69-
#if TracingSupport
70-
var tracer: (any Tracer)? {
72+
let connectionDeadline: NIODeadline
73+
74+
var tracer: HTTPClientTracingSupportTracerType? {
7175
self.task.tracer
7276
}
73-
#endif
74-
75-
let connectionDeadline: NIODeadline
7677

7778
let requestOptions: RequestOptions
7879

@@ -126,17 +127,20 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate & Sendable>: Sendabl
126127
// MARK: - Request -
127128

128129
private func willExecuteRequest0(_ executor: HTTPRequestExecutor) {
130+
// Immediately start a span for the "whole" request
131+
self.loopBoundState.value.startRequestSpan(tracer: self.tracer)
132+
129133
let action = self.loopBoundState.value.state.willExecuteRequest(executor)
130134
switch action {
131135
case .cancelExecuter(let executor):
132136
executor.cancelRequest(self)
137+
self.loopBoundState.value.failRequestSpan(error: CancellationError())
133138
case .failTaskAndCancelExecutor(let error, let executor):
134139
self.delegate.didReceiveError(task: self.task, error)
135140
self.task.failInternal(with: error)
136141
executor.cancelRequest(self)
142+
self.loopBoundState.value.failRequestSpan(error: CancellationError())
137143
case .none:
138-
let request = self.loopBoundState.value.request
139-
self.activeSpan = self.tracer?.startSpan("\(request.method) \(request.host)")
140144
break
141145
}
142146
}
@@ -244,6 +248,7 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate & Sendable>: Sendabl
244248

245249
private func receiveResponseHead0(_ head: HTTPResponseHead) {
246250
self.delegate.didVisitURL(task: self.task, self.loopBoundState.value.request, head)
251+
self.loopBoundState.value.endRequestSpan(response: head)
247252

248253
// runs most likely on channel eventLoop
249254
switch self.loopBoundState.value.state.receiveResponseHead(head) {

0 commit comments

Comments
 (0)