Skip to content

Commit ef580c1

Browse files
committed
[WIP] Implement Distributed Tracing
1 parent 2064247 commit ef580c1

File tree

3 files changed

+104
-31
lines changed

3 files changed

+104
-31
lines changed

Diff for: Package.swift

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ let package = Package(
2727
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"),
2828
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.19.0"),
2929
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"),
30+
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"),
31+
.package(url: "https://github.com/apple/swift-service-context.git", from: "1.0.0"),
3032
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"),
3133
.package(url: "https://github.com/apple/swift-algorithms.git", from: "1.0.0"),
3234
],
@@ -53,6 +55,8 @@ let package = Package(
5355
.product(name: "NIOSOCKS", package: "swift-nio-extras"),
5456
.product(name: "NIOTransportServices", package: "swift-nio-transport-services"),
5557
.product(name: "Logging", package: "swift-log"),
58+
.product(name: "Tracing", package: "swift-distributed-tracing"),
59+
.product(name: "ServiceContextModule", package: "swift-service-context"),
5660
.product(name: "Atomics", package: "swift-atomics"),
5761
.product(name: "Algorithms", package: "swift-algorithms"),
5862
]

Diff for: Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift

+68-31
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import Logging
1616
import NIOCore
1717
import NIOHTTP1
18+
import ServiceContextModule
19+
import Tracing
1820

1921
import struct Foundation.URL
2022

@@ -34,18 +36,20 @@ extension HTTPClient {
3436
public func execute(
3537
_ request: HTTPClientRequest,
3638
deadline: NIODeadline,
37-
logger: Logger? = nil
39+
logger: Logger? = nil,
40+
context: ServiceContext? = nil
3841
) async throws -> HTTPClientResponse {
3942
try await self.executeAndFollowRedirectsIfNeeded(
4043
request,
4144
deadline: deadline,
4245
logger: logger ?? Self.loggingDisabled,
46+
context: context ?? .current ?? .topLevel,
4347
redirectState: RedirectState(self.configuration.redirectConfiguration.mode, initialURL: request.url)
4448
)
4549
}
4650
}
4751

48-
// MARK: Connivence methods
52+
// MARK: Convenience methods
4953

5054
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
5155
extension HTTPClient {
@@ -63,12 +67,14 @@ extension HTTPClient {
6367
public func execute(
6468
_ request: HTTPClientRequest,
6569
timeout: TimeAmount,
66-
logger: Logger? = nil
70+
logger: Logger? = nil,
71+
context: ServiceContext? = nil
6772
) async throws -> HTTPClientResponse {
6873
try await self.execute(
6974
request,
7075
deadline: .now() + timeout,
71-
logger: logger
76+
logger: logger,
77+
context: context
7278
)
7379
}
7480
}
@@ -81,6 +87,7 @@ extension HTTPClient {
8187
_ request: HTTPClientRequest,
8288
deadline: NIODeadline,
8389
logger: Logger,
90+
context: ServiceContext,
8491
redirectState: RedirectState?
8592
) async throws -> HTTPClientResponse {
8693
var currentRequest = request
@@ -140,6 +147,7 @@ extension HTTPClient {
140147
return response
141148
}
142149

150+
resendCount += 1
143151
currentRequest = newRequest
144152
}
145153
}
@@ -149,39 +157,68 @@ extension HTTPClient {
149157
private func executeCancellable(
150158
_ request: HTTPClientRequest.Prepared,
151159
deadline: NIODeadline,
152-
logger: Logger
160+
logger: Logger,
161+
context: ServiceContext,
162+
resendCount: Int?
153163
) async throws -> HTTPClientResponse {
154164
let cancelHandler = TransactionCancelHandler()
155165

156-
return try await withTaskCancellationHandler(
157-
operation: { () async throws -> HTTPClientResponse in
158-
let eventLoop = self.eventLoopGroup.any()
159-
let deadlineTask = eventLoop.scheduleTask(deadline: deadline) {
160-
cancelHandler.cancel(reason: .deadlineExceeded)
161-
}
162-
defer {
163-
deadlineTask.cancel()
164-
}
165-
return try await withCheckedThrowingContinuation {
166-
(continuation: CheckedContinuation<HTTPClientResponse, Swift.Error>) -> Void in
167-
let transaction = Transaction(
168-
request: request,
169-
requestOptions: .fromClientConfiguration(self.configuration),
170-
logger: logger,
171-
connectionDeadline: .now() + (self.configuration.timeout.connectionCreationTimeout),
172-
preferredEventLoop: eventLoop,
173-
responseContinuation: continuation
174-
)
166+
return try await withSpan(request.head.method.rawValue, context: context, ofKind: .client) { span in
167+
var request = request
168+
request.head.headers.propagate(span.context)
169+
span.updateAttributes { attributes in
170+
attributes["http.request.method"] = request.head.method.rawValue
171+
attributes["server.address"] = request.poolKey.connectionTarget.host
172+
attributes["server.port"] = request.poolKey.connectionTarget.port
173+
attributes["url.full"] = request.url.absoluteString
174+
attributes["http.request.resend_count"] = resendCount
175+
}
176+
177+
do {
178+
let response = try await withTaskCancellationHandler(
179+
operation: { () async throws -> HTTPClientResponse in
180+
let eventLoop = self.eventLoopGroup.any()
181+
let deadlineTask = eventLoop.scheduleTask(deadline: deadline) {
182+
cancelHandler.cancel(reason: .deadlineExceeded)
183+
}
184+
defer {
185+
deadlineTask.cancel()
186+
}
187+
let response = try await withCheckedThrowingContinuation {
188+
(continuation: CheckedContinuation<HTTPClientResponse, Swift.Error>) -> Void in
189+
let transaction = Transaction(
190+
request: request,
191+
requestOptions: .fromClientConfiguration(self.configuration),
192+
logger: logger,
193+
connectionDeadline: .now() + (self.configuration.timeout.connectionCreationTimeout),
194+
preferredEventLoop: eventLoop,
195+
responseContinuation: continuation
196+
)
175197

176-
cancelHandler.registerTransaction(transaction)
198+
cancelHandler.registerTransaction(transaction)
177199

178-
self.poolManager.executeRequest(transaction)
179-
}
180-
},
181-
onCancel: {
182-
cancelHandler.cancel(reason: .taskCanceled)
200+
self.poolManager.executeRequest(transaction)
201+
}
202+
if response.status.code >= 400 {
203+
span.setStatus(.init(code: .error))
204+
span.attributes["error.type"] = "\(response.status.code)"
205+
}
206+
span.attributes["http.response.status_code"] = "\(response.status.code)"
207+
return response
208+
},
209+
onCancel: {
210+
span.setStatus(.init(code: .error))
211+
span.attributes["error.type"] = "\(CancellationError.self)"
212+
cancelHandler.cancel(reason: .taskCanceled)
213+
}
214+
)
215+
return response
216+
} catch {
217+
span.setStatus(.init(code: .error))
218+
span.attributes["error.type"] = "\(type(of: error))"
219+
throw error
183220
}
184-
)
221+
}
185222
}
186223
}
187224

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Instrumentation
16+
import NIOHTTP1
17+
import ServiceContextModule
18+
19+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
20+
extension HTTPHeaders {
21+
mutating func propagate(_ context: ServiceContext) {
22+
InstrumentationSystem.instrument.inject(context, into: &self, using: Self.injector)
23+
}
24+
25+
private static let injector = HTTPHeadersInjector()
26+
}
27+
28+
private struct HTTPHeadersInjector: Injector {
29+
func inject(_ value: String, forKey name: String, into headers: inout HTTPHeaders) {
30+
headers.add(name: name, value: value)
31+
}
32+
}

0 commit comments

Comments
 (0)