@@ -23,6 +23,7 @@ import NIOPosix
2323import NIOSSL
2424import NIOTLS
2525import NIOTransportServices
26+ import Tracing
2627
2728extension Logger {
2829 private func requestInfo( _ request: HTTPClient . Request ) -> Logger . Metadata . Value {
@@ -62,15 +63,29 @@ public final class HTTPClient: Sendable {
6263 ///
6364 /// All HTTP transactions will occur on loops owned by this group.
6465 public let eventLoopGroup : EventLoopGroup
65- let configuration : Configuration
6666 let poolManager : HTTPConnectionPool . Manager
6767
68+ @usableFromInline
69+ let configuration : Configuration
70+
6871 /// Shared thread pool used for file IO. It is lazily created on first access of ``Task/fileIOThreadPool``.
6972 private let fileIOThreadPool : NIOLockedValueBox < NIOThreadPool ? >
7073
7174 private let state : NIOLockedValueBox < State >
7275 private let canBeShutDown : Bool
7376
77+ /// Tracer configured for this HTTPClient at configuration time.
78+ @available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
79+ public var tracer : ( any Tracer ) ? {
80+ configuration. tracing. tracer
81+ }
82+
83+ /// Access to tracing configuration in order to get configured attribute keys etc.
84+ @usableFromInline
85+ package var tracing : TracingConfiguration {
86+ self . configuration. tracing
87+ }
88+
7489 static let loggingDisabled = Logger ( label: " AHC-do-not-log " , factory: { _ in SwiftLogNoOpLogHandler ( ) } )
7590
7691 /// Create an ``HTTPClient`` with specified `EventLoopGroup` provider and configuration.
@@ -705,6 +720,7 @@ public final class HTTPClient: Sendable {
705720 request,
706721 requestID: globalRequestID. wrappingIncrementThenLoad ( ordering: . relaxed)
707722 )
723+
708724 let taskEL : EventLoop
709725 switch eventLoopPreference. preference {
710726 case . indifferent:
@@ -734,7 +750,7 @@ public final class HTTPClient: Sendable {
734750 ]
735751 )
736752
737- let failedTask : Task < Delegate . Response > ? = self . state. withLockedValue { state in
753+ let failedTask : Task < Delegate . Response > ? = self . state. withLockedValue { state -> ( Task < Delegate . Response > ? ) in
738754 switch state {
739755 case . upAndRunning:
740756 return nil
@@ -744,6 +760,7 @@ public final class HTTPClient: Sendable {
744760 eventLoop: taskEL,
745761 error: HTTPClientError . alreadyShutdown,
746762 logger: logger,
763+ tracing: tracing,
747764 makeOrGetFileIOThreadPool: self . makeOrGetFileIOThreadPool
748765 )
749766 }
@@ -768,11 +785,14 @@ public final class HTTPClient: Sendable {
768785 }
769786 } ( )
770787
771- let task = Task < Delegate . Response > (
772- eventLoop: taskEL,
773- logger: logger,
774- makeOrGetFileIOThreadPool: self . makeOrGetFileIOThreadPool
775- )
788+ let task : HTTPClient . Task < Delegate . Response > =
789+ Task < Delegate . Response > (
790+ eventLoop: taskEL,
791+ logger: logger,
792+ tracing: self . tracing,
793+ makeOrGetFileIOThreadPool: self . makeOrGetFileIOThreadPool
794+ )
795+
776796 do {
777797 let requestBag = try RequestBag (
778798 request: request,
@@ -884,6 +904,9 @@ public final class HTTPClient: Sendable {
884904 /// A method with access to the HTTP/2 stream channel that is called when creating the stream.
885905 public var http2StreamChannelDebugInitializer : ( @Sendable ( Channel ) -> EventLoopFuture < Void > ) ?
886906
907+ /// Configuration how distributed traces are created and handled.
908+ public var tracing : TracingConfiguration = . init( )
909+
887910 public init (
888911 tlsConfiguration: TLSConfiguration ? = nil ,
889912 redirectConfiguration: RedirectConfiguration ? = nil ,
@@ -1012,6 +1035,84 @@ public final class HTTPClient: Sendable {
10121035 self . http2ConnectionDebugInitializer = http2ConnectionDebugInitializer
10131036 self . http2StreamChannelDebugInitializer = http2StreamChannelDebugInitializer
10141037 }
1038+
1039+ public init (
1040+ tlsConfiguration: TLSConfiguration ? = nil ,
1041+ redirectConfiguration: RedirectConfiguration ? = nil ,
1042+ timeout: Timeout = Timeout ( ) ,
1043+ connectionPool: ConnectionPool = ConnectionPool ( ) ,
1044+ proxy: Proxy ? = nil ,
1045+ ignoreUncleanSSLShutdown: Bool = false ,
1046+ decompression: Decompression = . disabled,
1047+ http1_1ConnectionDebugInitializer: ( @Sendable ( Channel ) -> EventLoopFuture < Void > ) ? = nil ,
1048+ http2ConnectionDebugInitializer: ( @Sendable ( Channel ) -> EventLoopFuture < Void > ) ? = nil ,
1049+ http2StreamChannelDebugInitializer: ( @Sendable ( Channel ) -> EventLoopFuture < Void > ) ? = nil ,
1050+ tracing: TracingConfiguration = . init( )
1051+ ) {
1052+ self . init (
1053+ tlsConfiguration: tlsConfiguration,
1054+ redirectConfiguration: redirectConfiguration,
1055+ timeout: timeout,
1056+ connectionPool: connectionPool,
1057+ proxy: proxy,
1058+ ignoreUncleanSSLShutdown: ignoreUncleanSSLShutdown,
1059+ decompression: decompression
1060+ )
1061+ self . http1_1ConnectionDebugInitializer = http1_1ConnectionDebugInitializer
1062+ self . http2ConnectionDebugInitializer = http2ConnectionDebugInitializer
1063+ self . http2StreamChannelDebugInitializer = http2StreamChannelDebugInitializer
1064+ self . tracing = tracing
1065+ }
1066+ }
1067+
1068+ public struct TracingConfiguration : Sendable {
1069+
1070+ @usableFromInline
1071+ var _tracer : Optional < any Sendable > // erasure trick so we don't have to make Configuration @available
1072+
1073+ /// Tracer that should be used by the HTTPClient.
1074+ ///
1075+ /// This is selected at configuration creation time, and if no tracer is passed explicitly,
1076+ /// (including `nil` in order to disable traces), the default global bootstrapped tracer will
1077+ /// be stored in this property, and used for all subsequent requests made by this client.
1078+ @inlinable
1079+ @available ( macOS 10 . 15 , iOS 13 , tvOS 13 , watchOS 6 , * )
1080+ public var tracer : ( any Tracer ) ? {
1081+ get {
1082+ guard let _tracer else {
1083+ return nil
1084+ }
1085+ return _tracer as! ( any Tracer ) ?
1086+ }
1087+ set {
1088+ self . _tracer = newValue
1089+ }
1090+ }
1091+
1092+ // TODO: Open up customization of keys we use?
1093+ /// Configuration for tracing attributes set by the HTTPClient.
1094+ @usableFromInline
1095+ package var attributeKeys : AttributeKeys
1096+
1097+ public init ( ) {
1098+ self . _tracer = nil
1099+ self . attributeKeys = . init( )
1100+ }
1101+
1102+ /// Span attribute keys that the HTTPClient should set automatically.
1103+ /// This struct allows the configuration of the attribute names (keys) which will be used for the apropriate values.
1104+ @usableFromInline
1105+ package struct AttributeKeys : Sendable {
1106+ @usableFromInline package var requestMethod : String = " http.request.method "
1107+ @usableFromInline package var requestBodySize : String = " http.request.body.size "
1108+
1109+ @usableFromInline package var responseBodySize : String = " http.response.body.size "
1110+ @usableFromInline package var responseStatusCode : String = " http.status_code "
1111+
1112+ @usableFromInline package var httpFlavor : String = " http.flavor "
1113+
1114+ @usableFromInline package init ( ) { }
1115+ }
10151116 }
10161117
10171118 /// Specifies how `EventLoopGroup` will be created and establishes lifecycle ownership.
0 commit comments