Skip to content

Commit c0209df

Browse files
committed
Auto extract IDs from headers on the server
Motivation: It's common for service authors to extract a tracing ID from a request header and attach that value to the logger for an RPC. This only requires a few lines of code but must be done in each RPC. gRPC should provide configuration to do this automatically. Modifications: - Add server configuration which extracts a value from headers and attaches it to a logger at the start of each RPC. - Wire up the config. Add tests. Result: Trace IDs can be automatically extracted from headers on the server and attached to loggers.
1 parent 3677a71 commit c0209df

18 files changed

+474
-36
lines changed

Sources/GRPC/AsyncAwaitSupport/GRPCAsyncServerHandler.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,10 @@ internal final class AsyncServerHandler<
191191

192192
/// A logger.
193193
@usableFromInline
194-
internal let logger: Logger
194+
internal var logger: Logger
195+
196+
@usableFromInline
197+
internal let traceIDExtractor: Server.Configuration.TraceIDExtractor?
195198

196199
/// A reference to the user info. This is shared with the interceptor pipeline and may be accessed
197200
/// from the async call context. `UserInfo` is _not_ `Sendable` and must always be accessed from
@@ -267,6 +270,7 @@ internal final class AsyncServerHandler<
267270
self.compressionEnabledOnRPC = context.encoding.isEnabled
268271
self.compressResponsesIfPossible = true
269272
self.logger = context.logger
273+
self.traceIDExtractor = context.traceIDExtractor
270274

271275
self.userInfoRef = Ref(UserInfo())
272276
self.handlerStateMachine = .init()
@@ -295,6 +299,10 @@ internal final class AsyncServerHandler<
295299
internal func receiveMetadata(_ headers: HPACKHeaders) {
296300
switch self.interceptorStateMachine.interceptRequestMetadata() {
297301
case .intercept:
302+
if let extractor = self.traceIDExtractor, let id = extractor.extract(from: headers) {
303+
self.logger[metadataKey: extractor.loggerKey] = "\(id)"
304+
self.interceptors?.logger[metadataKey: extractor.loggerKey] = "\(id)"
305+
}
298306
self.interceptors?.receive(.metadata(headers))
299307
case .cancel:
300308
self.cancel(error: nil)

Sources/GRPC/CallHandlers/BidirectionalStreamingServerHandler.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import Logging
1617
import NIOCore
1718
import NIOHPACK
1819

@@ -56,6 +57,9 @@ public final class BidirectionalStreamingServerHandler<
5657
@usableFromInline
5758
internal var state: State = .idle
5859

60+
@usableFromInline
61+
internal var logger: Logger
62+
5963
@usableFromInline
6064
internal enum State {
6165
// No headers have been received.
@@ -85,6 +89,7 @@ public final class BidirectionalStreamingServerHandler<
8589

8690
let userInfoRef = Ref(UserInfo())
8791
self.userInfoRef = userInfoRef
92+
self.logger = context.logger
8893
self.interceptors = ServerInterceptorPipeline(
8994
logger: context.logger,
9095
eventLoop: context.eventLoop,
@@ -102,6 +107,10 @@ public final class BidirectionalStreamingServerHandler<
102107

103108
@inlinable
104109
public func receiveMetadata(_ headers: HPACKHeaders) {
110+
if let extractor = self.context.traceIDExtractor, let id = extractor.extract(from: headers) {
111+
self.logger[metadataKey: extractor.loggerKey] = "\(id)"
112+
self.interceptors.logger[metadataKey: extractor.loggerKey] = "\(id)"
113+
}
105114
self.interceptors.receive(.metadata(headers))
106115
}
107116

@@ -164,7 +173,7 @@ public final class BidirectionalStreamingServerHandler<
164173
let context = _StreamingResponseCallContext<Request, Response>(
165174
eventLoop: self.context.eventLoop,
166175
headers: headers,
167-
logger: self.context.logger,
176+
logger: self.logger,
168177
userInfoRef: self.userInfoRef,
169178
compressionIsEnabled: self.context.encoding.isEnabled,
170179
closeFuture: self.context.closeFuture,

Sources/GRPC/CallHandlers/ClientStreamingServerHandler.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import Logging
1617
import NIOCore
1718
import NIOHPACK
1819

@@ -56,6 +57,9 @@ public final class ClientStreamingServerHandler<
5657
@usableFromInline
5758
internal var state: State = .idle
5859

60+
@usableFromInline
61+
internal var logger: Logger
62+
5963
@usableFromInline
6064
internal enum State {
6165
// Nothing has happened yet.
@@ -86,6 +90,7 @@ public final class ClientStreamingServerHandler<
8690

8791
let userInfoRef = Ref(UserInfo())
8892
self.userInfoRef = userInfoRef
93+
self.logger = context.logger
8994
self.interceptors = ServerInterceptorPipeline(
9095
logger: context.logger,
9196
eventLoop: context.eventLoop,
@@ -103,6 +108,10 @@ public final class ClientStreamingServerHandler<
103108

104109
@inlinable
105110
public func receiveMetadata(_ headers: HPACKHeaders) {
111+
if let extractor = self.context.traceIDExtractor, let id = extractor.extract(from: headers) {
112+
self.logger[metadataKey: extractor.loggerKey] = "\(id)"
113+
self.interceptors.logger[metadataKey: extractor.loggerKey] = "\(id)"
114+
}
106115
self.interceptors.receive(.metadata(headers))
107116
}
108117

@@ -165,7 +174,7 @@ public final class ClientStreamingServerHandler<
165174
let context = UnaryResponseCallContext<Response>(
166175
eventLoop: self.context.eventLoop,
167176
headers: headers,
168-
logger: self.context.logger,
177+
logger: self.logger,
169178
userInfoRef: self.userInfoRef,
170179
closeFuture: self.context.closeFuture
171180
)

Sources/GRPC/CallHandlers/ServerStreamingServerHandler.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import Logging
1617
import NIOCore
1718
import NIOHPACK
1819

@@ -52,6 +53,9 @@ public final class ServerStreamingServerHandler<
5253
@usableFromInline
5354
internal var state: State = .idle
5455

56+
@usableFromInline
57+
internal var logger: Logger
58+
5559
@usableFromInline
5660
internal enum State {
5761
// Initial state. Nothing has happened yet.
@@ -82,6 +86,7 @@ public final class ServerStreamingServerHandler<
8286

8387
let userInfoRef = Ref(UserInfo())
8488
self.userInfoRef = userInfoRef
89+
self.logger = context.logger
8590
self.interceptors = ServerInterceptorPipeline(
8691
logger: context.logger,
8792
eventLoop: context.eventLoop,
@@ -99,6 +104,10 @@ public final class ServerStreamingServerHandler<
99104

100105
@inlinable
101106
public func receiveMetadata(_ headers: HPACKHeaders) {
107+
if let extractor = self.context.traceIDExtractor, let id = extractor.extract(from: headers) {
108+
self.logger[metadataKey: extractor.loggerKey] = "\(id)"
109+
self.interceptors.logger[metadataKey: extractor.loggerKey] = "\(id)"
110+
}
102111
self.interceptors.receive(.metadata(headers))
103112
}
104113

@@ -161,7 +170,7 @@ public final class ServerStreamingServerHandler<
161170
let context = _StreamingResponseCallContext<Request, Response>(
162171
eventLoop: self.context.eventLoop,
163172
headers: headers,
164-
logger: self.context.logger,
173+
logger: self.logger,
165174
userInfoRef: self.userInfoRef,
166175
compressionIsEnabled: self.context.encoding.isEnabled,
167176
closeFuture: self.context.closeFuture,

Sources/GRPC/CallHandlers/UnaryServerHandler.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
import Logging
1617
import NIOCore
1718
import NIOHPACK
1819

@@ -51,6 +52,9 @@ public final class UnaryServerHandler<
5152
@usableFromInline
5253
internal var state: State = .idle
5354

55+
@usableFromInline
56+
internal var logger: Logger
57+
5458
@usableFromInline
5559
internal enum State {
5660
// Initial state. Nothing has happened yet.
@@ -77,6 +81,7 @@ public final class UnaryServerHandler<
7781
self.serializer = responseSerializer
7882
self.deserializer = requestDeserializer
7983
self.context = context
84+
self.logger = context.logger
8085

8186
let userInfoRef = Ref(UserInfo())
8287
self.userInfoRef = userInfoRef
@@ -97,6 +102,10 @@ public final class UnaryServerHandler<
97102

98103
@inlinable
99104
public func receiveMetadata(_ metadata: HPACKHeaders) {
105+
if let extractor = self.context.traceIDExtractor, let id = extractor.extract(from: metadata) {
106+
self.logger[metadataKey: extractor.loggerKey] = "\(id)"
107+
self.interceptors.logger[metadataKey: extractor.loggerKey] = "\(id)"
108+
}
100109
self.interceptors.receive(.metadata(metadata))
101110
}
102111

@@ -159,7 +168,7 @@ public final class UnaryServerHandler<
159168
let context = UnaryResponseCallContext<Response>(
160169
eventLoop: self.context.eventLoop,
161170
headers: headers,
162-
logger: self.context.logger,
171+
logger: self.logger,
163172
userInfoRef: self.userInfoRef,
164173
closeFuture: self.context.closeFuture
165174
)

Sources/GRPC/GRPCServerPipelineConfigurator.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ final class GRPCServerPipelineConfigurator: ChannelInboundHandler, RemovableChan
142142
errorDelegate: self.configuration.errorDelegate,
143143
normalizeHeaders: normalizeHeaders,
144144
maximumReceiveMessageLength: self.configuration.maximumReceiveMessageLength,
145-
logger: logger
145+
logger: logger,
146+
traceIDExtractor: self.configuration.traceIDExtractor
146147
)
147148
}
148149

Sources/GRPC/GRPCServerRequestRoutingHandler.swift

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ public struct CallHandlerContext {
5959
internal var allocator: ByteBufferAllocator
6060
@usableFromInline
6161
internal var closeFuture: EventLoopFuture<Void>
62+
@usableFromInline
63+
internal var traceIDExtractor: Server.Configuration.TraceIDExtractor?
6264
}
6365

6466
/// A call URI split into components.

Sources/GRPC/HTTP2ToRawGRPCServerCodec.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal final class HTTP2ToRawGRPCServerCodec: ChannelInboundHandler, GRPCServe
2323
typealias OutboundOut = HTTP2Frame.FramePayload
2424

2525
private var logger: Logger
26+
private let traceIDExtractor: Server.Configuration.TraceIDExtractor?
2627
private var state: HTTP2ToRawGRPCStateMachine
2728
private let errorDelegate: ServerErrorDelegate?
2829
private var context: ChannelHandlerContext!
@@ -73,9 +74,11 @@ internal final class HTTP2ToRawGRPCServerCodec: ChannelInboundHandler, GRPCServe
7374
errorDelegate: ServerErrorDelegate?,
7475
normalizeHeaders: Bool,
7576
maximumReceiveMessageLength: Int,
76-
logger: Logger
77+
logger: Logger,
78+
traceIDExtractor: Server.Configuration.TraceIDExtractor?
7779
) {
7880
self.logger = logger
81+
self.traceIDExtractor = traceIDExtractor
7982
self.errorDelegate = errorDelegate
8083
self.servicesByName = servicesByName
8184
self.encoding = encoding
@@ -127,7 +130,8 @@ internal final class HTTP2ToRawGRPCServerCodec: ChannelInboundHandler, GRPCServe
127130
closeFuture: context.channel.closeFuture,
128131
services: self.servicesByName,
129132
encoding: self.encoding,
130-
normalizeHeaders: self.normalizeHeaders
133+
normalizeHeaders: self.normalizeHeaders,
134+
traceIDExtractor: self.traceIDExtractor
131135
)
132136

133137
switch receiveHeaders {

Sources/GRPC/HTTP2ToRawGRPCStateMachine.swift

+12-6
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ extension HTTP2ToRawGRPCStateMachine.State {
277277
closeFuture: EventLoopFuture<Void>,
278278
services: [Substring: CallHandlerProvider],
279279
encoding: ServerMessageEncoding,
280-
normalizeHeaders: Bool
280+
normalizeHeaders: Bool,
281+
traceIDExtractor: Server.Configuration.TraceIDExtractor?
281282
) -> HTTP2ToRawGRPCStateMachine.StateAndReceiveHeadersAction {
282283
// Extract and validate the content type. If it's nil we need to close.
283284
guard let contentType = self.extractContentType(from: headers) else {
@@ -326,7 +327,8 @@ extension HTTP2ToRawGRPCStateMachine.State {
326327
remoteAddress: remoteAddress,
327328
responseWriter: responseWriter,
328329
allocator: allocator,
329-
closeFuture: closeFuture
330+
closeFuture: closeFuture,
331+
traceIDExtractor: traceIDExtractor
330332
)
331333

332334
// We have a matching service, hopefully we have a provider for the method too.
@@ -865,7 +867,8 @@ extension HTTP2ToRawGRPCStateMachine {
865867
closeFuture: EventLoopFuture<Void>,
866868
services: [Substring: CallHandlerProvider],
867869
encoding: ServerMessageEncoding,
868-
normalizeHeaders: Bool
870+
normalizeHeaders: Bool,
871+
traceIDExtractor: Server.Configuration.TraceIDExtractor?
869872
) -> ReceiveHeadersAction {
870873
return self.withStateAvoidingCoWs { state in
871874
state.receive(
@@ -879,7 +882,8 @@ extension HTTP2ToRawGRPCStateMachine {
879882
closeFuture: closeFuture,
880883
services: services,
881884
encoding: encoding,
882-
normalizeHeaders: normalizeHeaders
885+
normalizeHeaders: normalizeHeaders,
886+
traceIDExtractor: traceIDExtractor
883887
)
884888
}
885889
}
@@ -974,7 +978,8 @@ extension HTTP2ToRawGRPCStateMachine.State {
974978
closeFuture: EventLoopFuture<Void>,
975979
services: [Substring: CallHandlerProvider],
976980
encoding: ServerMessageEncoding,
977-
normalizeHeaders: Bool
981+
normalizeHeaders: Bool,
982+
traceIDExtractor: Server.Configuration.TraceIDExtractor?
978983
) -> HTTP2ToRawGRPCStateMachine.ReceiveHeadersAction {
979984
switch self {
980985
// These are the only states in which we can receive headers. Everything else is invalid.
@@ -991,7 +996,8 @@ extension HTTP2ToRawGRPCStateMachine.State {
991996
closeFuture: closeFuture,
992997
services: services,
993998
encoding: encoding,
994-
normalizeHeaders: normalizeHeaders
999+
normalizeHeaders: normalizeHeaders,
1000+
traceIDExtractor: traceIDExtractor
9951001
)
9961002
self = stateAndAction.state
9971003
return stateAndAction.action

Sources/GRPC/Interceptor/ServerInterceptorPipeline.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal final class ServerInterceptorPipeline<Request, Response> {
3636

3737
/// A logger.
3838
@usableFromInline
39-
internal let logger: Logger
39+
internal var logger: Logger
4040

4141
/// A reference to a 'UserInfo'.
4242
@usableFromInline

0 commit comments

Comments
 (0)