@@ -31,10 +31,13 @@ package import Tracing
3131/// - https://opentelemetry.io/docs/specs/semconv/rpc/grpc/
3232public struct ClientOTelTracingInterceptor : ClientInterceptor {
3333 private let injector : ClientRequestInjector
34- private let traceEachMessage : Bool
3534 private var serverHostname : String
3635 private var networkTransportMethod : String
3736
37+ private let traceEachMessage : Bool
38+ private var includeRequestMetadata : Bool
39+ private var includeResponseMetadata : Bool
40+
3841 /// Create a new instance of a ``ClientOTelTracingInterceptor``.
3942 ///
4043 /// - Parameters:
@@ -43,15 +46,46 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor {
4346 /// `network.transport` attribute in spans.
4447 /// - traceEachMessage: If `true`, each request part sent and response part received will be recorded as a separate
4548 /// event in a tracing span.
49+ ///
50+ /// - Important: Be careful when setting `includeRequestMetadata` or `includeResponseMetadata` to `true`,
51+ /// as including all request/response metadata can be a security risk.
4652 public init (
4753 serverHostname: String ,
4854 networkTransportMethod: String ,
4955 traceEachMessage: Bool = true
56+ ) {
57+ self . init (
58+ serverHostname: serverHostname,
59+ networkTransportMethod: networkTransportMethod,
60+ traceEachMessage: traceEachMessage,
61+ includeRequestMetadata: false ,
62+ includeResponseMetadata: false
63+ )
64+ }
65+
66+ /// Create a new instance of a ``ClientOTelTracingInterceptor``.
67+ ///
68+ /// - Parameters:
69+ /// - severHostname: The hostname of the RPC server. This will be the value for the `server.address` attribute in spans.
70+ /// - networkTransportMethod: The transport in use (e.g. "tcp", "unix"). This will be the value for the
71+ /// `network.transport` attribute in spans.
72+ /// - traceEachMessage: If `true`, each request part sent and response part received will be recorded as a separate
73+ /// event in a tracing span.
74+ /// - includeRequestMetadata: if `true`, **all** metadata keys with string values included in the request will be added to the span as attributes.
75+ /// - includeResponseMetadata: if `true`, **all** metadata keys with string values included in the response will be added to the span as attributes.
76+ public init (
77+ serverHostname: String ,
78+ networkTransportMethod: String ,
79+ traceEachMessage: Bool = true ,
80+ includeRequestMetadata: Bool = false ,
81+ includeResponseMetadata: Bool = false
5082 ) {
5183 self . injector = ClientRequestInjector ( )
5284 self . serverHostname = serverHostname
5385 self . networkTransportMethod = networkTransportMethod
5486 self . traceEachMessage = traceEachMessage
87+ self . includeRequestMetadata = includeRequestMetadata
88+ self . includeResponseMetadata = includeResponseMetadata
5589 }
5690
5791 /// This interceptor will inject as the request's metadata whatever `ServiceContext` key-value pairs
@@ -93,12 +127,6 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor {
93127 var request = request
94128 let serviceContext = ServiceContext . current ?? . topLevel
95129
96- tracer. inject (
97- serviceContext,
98- into: & request. metadata,
99- using: self . injector
100- )
101-
102130 return try await tracer. withSpan (
103131 context. descriptor. fullyQualifiedMethod,
104132 context: serviceContext,
@@ -110,6 +138,16 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor {
110138 networkTransportMethod: self . networkTransportMethod
111139 )
112140
141+ if self . includeRequestMetadata {
142+ span. setMetadataStringAttributesAsRequestSpanAttributes ( request. metadata)
143+ }
144+
145+ tracer. inject (
146+ serviceContext,
147+ into: & request. metadata,
148+ using: self . injector
149+ )
150+
113151 if self . traceEachMessage {
114152 let wrappedProducer = request. producer
115153 request. producer = { writer in
@@ -131,6 +169,11 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor {
131169 }
132170
133171 var response = try await next ( request, context)
172+
173+ if self . includeResponseMetadata {
174+ span. setMetadataStringAttributesAsResponseSpanAttributes ( response. metadata)
175+ }
176+
134177 switch response. accepted {
135178 case . success( var success) :
136179 let hookedSequence :
@@ -139,14 +182,22 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor {
139182 >
140183 if self . traceEachMessage {
141184 let messageReceivedCounter = Atomic ( 1 )
142- hookedSequence = HookedRPCAsyncSequence ( wrapping: success. bodyParts) { _ in
143- var event = SpanEvent ( name: " rpc.message " )
144- event. attributes [ GRPCTracingKeys . rpcMessageType] = " RECEIVED "
145- event. attributes [ GRPCTracingKeys . rpcMessageID] =
146- messageReceivedCounter
147- . wrappingAdd ( 1 , ordering: . sequentiallyConsistent)
148- . oldValue
149- span. addEvent ( event)
185+ hookedSequence = HookedRPCAsyncSequence ( wrapping: success. bodyParts) { part in
186+ switch part {
187+ case . message:
188+ var event = SpanEvent ( name: " rpc.message " )
189+ event. attributes [ GRPCTracingKeys . rpcMessageType] = " RECEIVED "
190+ event. attributes [ GRPCTracingKeys . rpcMessageID] =
191+ messageReceivedCounter
192+ . wrappingAdd ( 1 , ordering: . sequentiallyConsistent)
193+ . oldValue
194+ span. addEvent ( event)
195+
196+ case . trailingMetadata( let trailingMetadata) :
197+ if self . includeResponseMetadata {
198+ span. setMetadataStringAttributesAsResponseSpanAttributes ( trailingMetadata)
199+ }
200+ }
150201 } onFinish: { error in
151202 if let error {
152203 if let errorCode = error. grpcErrorCode {
0 commit comments