Skip to content

Commit d4633f2

Browse files
authored
Allow loggers to be passed in (#902)
* Allow loggers to be passed in Motivation: The community recently discussed how logging should be done for the `AsyncHTTPClient` project. One of the outcomes was that frameworks should not log unless they are explicitly passed a logger. This allows applications to decide whether they care about logging for that component and have more control over the level at which that component logs at. Modifications: - Client allows for loggers in two places: one for background activity (connectivity state management and so on) and another for RPCs - Server allows a single logger to be specificed - By default all loggers are no-op loggers - Added 'none' option for 'CallOptions.RequestIDProvider' for cases where the request ID is already present on the logger Result: - gRPC does not log by default - Note: this includes tests, this will be addressed in a separate PR to keep PR size down
1 parent 8faf3c6 commit d4633f2

20 files changed

+184
-131
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"repositoryURL": "https://github.com/apple/swift-log.git",
77
"state": {
88
"branch": null,
9-
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
10-
"version": "1.2.0"
9+
"revision": "173f567a2dfec11d74588eea82cecea555bdc0bc",
10+
"version": "1.4.0"
1111
}
1212
},
1313
{

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let package = Package(
3939
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.9.0"),
4040

4141
// Logging API.
42-
.package(url: "https://github.com/apple/swift-log.git", from: "1.2.0"),
42+
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
4343
],
4444
targets: [
4545
// The main GRPC module.

Sources/GRPC/ClientCalls/ClientCallTransport.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,11 @@ internal class ChannelTransport<Request, Response> {
169169
switch result {
170170
case .success(let mux):
171171
mux.createStreamChannel(promise: streamPromise) { stream, streamID in
172-
stream.pipeline.addHandlers([
172+
var logger = logger
173+
logger[metadataKey: MetadataKey.streamID] = "\(streamID)"
174+
logger.trace("created http/2 stream")
175+
176+
return stream.pipeline.addHandlers([
173177
_GRPCClientChannelHandler(streamID: streamID, callType: callType, logger: logger),
174178
GRPCClientCodecHandler(serializer: serializer, deserializer: deserializer),
175179
GRPCClientCallHandler(call: call)

Sources/GRPC/ClientConnection.swift

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public class ClientConnection {
118118
self.authority = configuration.target.host
119119
self.connectionManager = ConnectionManager(
120120
configuration: configuration,
121-
logger: Logger(subsystem: .clientChannel)
121+
logger: configuration.backgroundActivityLogger
122122
)
123123
}
124124

@@ -127,19 +127,28 @@ public class ClientConnection {
127127
return self.connectionManager.shutdown()
128128
}
129129

130-
private func loggerWithRequestID(_ requestID: String) -> Logger {
131-
var logger = self.connectionManager.logger
132-
logger[metadataKey: MetadataKey.requestID] = "\(requestID)"
133-
return logger
130+
/// Extracts a logger and request ID from the call options and returns them. The logger will
131+
/// be populated with the request ID (if applicable) and any metadata from the connection manager.
132+
private func populatedLoggerAndRequestID(from callOptions: CallOptions) -> (Logger, String?) {
133+
var logger = callOptions.logger
134+
self.connectionManager.appendMetadata(to: &logger)
135+
136+
// Attach the request ID.
137+
let requestID = callOptions.requestIDProvider.requestID()
138+
if let requestID = requestID {
139+
logger[metadataKey: MetadataKey.requestID] = "\(requestID)"
140+
}
141+
142+
return (logger, requestID)
134143
}
135144

136-
private func makeRequestHead(path: String, options: CallOptions, requestID: String) -> _GRPCRequestHead {
145+
private func makeRequestHead(path: String, options: CallOptions, requestID: String?) -> _GRPCRequestHead {
137146
return _GRPCRequestHead(
138147
scheme: self.scheme,
139148
path: path,
140149
host: self.authority,
141-
requestID: requestID,
142-
options: options
150+
options: options,
151+
requestID: requestID
143152
)
144153
}
145154
}
@@ -154,8 +163,7 @@ extension ClientConnection: GRPCChannel {
154163
request: Serializer.Input,
155164
callOptions: CallOptions
156165
) -> UnaryCall<Serializer.Input, Deserializer.Output> {
157-
let requestID = callOptions.requestIDProvider.requestID()
158-
let logger = self.loggerWithRequestID(requestID)
166+
let (logger, requestID) = self.populatedLoggerAndRequestID(from: callOptions)
159167
logger.debug("starting rpc", metadata: ["path": "\(path)"])
160168

161169
let call = UnaryCall<Serializer.Input, Deserializer.Output>.makeOnHTTP2Stream(
@@ -212,8 +220,7 @@ extension ClientConnection {
212220
path: String,
213221
callOptions: CallOptions
214222
) -> ClientStreamingCall<Serializer.Input, Deserializer.Output> {
215-
let requestID = callOptions.requestIDProvider.requestID()
216-
let logger = self.loggerWithRequestID(requestID)
223+
let (logger, requestID) = self.populatedLoggerAndRequestID(from: callOptions)
217224
logger.debug("starting rpc", metadata: ["path": "\(path)"])
218225

219226
let call = ClientStreamingCall<Serializer.Input, Deserializer.Output>.makeOnHTTP2Stream(
@@ -268,8 +275,7 @@ extension ClientConnection {
268275
callOptions: CallOptions,
269276
handler: @escaping (Deserializer.Output) -> Void
270277
) -> ServerStreamingCall<Serializer.Input, Deserializer.Output> {
271-
let requestID = callOptions.requestIDProvider.requestID()
272-
let logger = self.loggerWithRequestID(requestID)
278+
let (logger, requestID) = self.populatedLoggerAndRequestID(from: callOptions)
273279
logger.debug("starting rpc", metadata: ["path": "\(path)"])
274280

275281
let call = ServerStreamingCall<Serializer.Input, Deserializer.Output>.makeOnHTTP2Stream(
@@ -332,8 +338,7 @@ extension ClientConnection {
332338
callOptions: CallOptions,
333339
handler: @escaping (Deserializer.Output) -> Void
334340
) -> BidirectionalStreamingCall<Serializer.Input, Deserializer.Output> {
335-
let requestID = callOptions.requestIDProvider.requestID()
336-
let logger = self.loggerWithRequestID(requestID)
341+
let (logger, requestID) = self.populatedLoggerAndRequestID(from: callOptions)
337342
logger.debug("starting rpc", metadata: ["path": "\(path)"])
338343

339344
let call = BidirectionalStreamingCall<Serializer.Input, Deserializer.Output>.makeOnHTTP2Stream(
@@ -507,6 +512,12 @@ extension ClientConnection {
507512
return self.tls == nil ? .http : .https
508513
}
509514

515+
/// A logger for background information (such as connectivity state). A separate logger for
516+
/// requests may be provided in the `CallOptions`.
517+
///
518+
/// Defaults to a no-op logger.
519+
public var backgroundActivityLogger: Logger
520+
510521
/// Create a `Configuration` with some pre-defined defaults. Prefer using
511522
/// `ClientConnection.secure(group:)` to build a connection secured with TLS or
512523
/// `ClientConnection.insecure(group:)` to build a plaintext connection.
@@ -525,6 +536,8 @@ extension ClientConnection {
525536
/// - Parameter callStartBehavior: The behavior used to determine when a call should start in
526537
/// relation to its underlying connection. Defaults to `waitsForConnectivity`.
527538
/// - Parameter httpTargetWindowSize: The HTTP/2 flow control target window size.
539+
/// - Parameter logger: A logger for background information (such as connectivity state).
540+
/// Defaults to a no-op logger.
528541
public init(
529542
target: ConnectionTarget,
530543
eventLoopGroup: EventLoopGroup,
@@ -536,7 +549,8 @@ extension ClientConnection {
536549
connectionKeepalive: ClientConnectionKeepalive = ClientConnectionKeepalive(),
537550
connectionIdleTimeout: TimeAmount = .minutes(5),
538551
callStartBehavior: CallStartBehavior = .waitsForConnectivity,
539-
httpTargetWindowSize: Int = 65535
552+
httpTargetWindowSize: Int = 65535,
553+
backgroundActivityLogger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() })
540554
) {
541555
self.target = target
542556
self.eventLoopGroup = eventLoopGroup
@@ -549,6 +563,7 @@ extension ClientConnection {
549563
self.connectionIdleTimeout = connectionIdleTimeout
550564
self.callStartBehavior = callStartBehavior
551565
self.httpTargetWindowSize = httpTargetWindowSize
566+
self.backgroundActivityLogger = backgroundActivityLogger
552567
}
553568
}
554569
}

Sources/GRPC/ClientOptions.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import struct Foundation.UUID
17+
import Logging
1718
import NIOHTTP1
1819
import NIOHTTP2
1920
import NIOHPACK
@@ -60,24 +61,33 @@ public struct CallOptions {
6061
/// messages associated with the call.
6162
public var requestIDHeader: String?
6263

64+
/// A logger used for the call. Defaults to a no-op logger.
65+
///
66+
/// If a `requestIDProvider` exists then a request ID will automatically attached to the logger's
67+
/// metadata using the 'grpc-request-id' key.
68+
public var logger: Logger
69+
6370
public init(
6471
customMetadata: HPACKHeaders = HPACKHeaders(),
6572
timeLimit: TimeLimit = .none,
6673
messageEncoding: ClientMessageEncoding = .disabled,
6774
requestIDProvider: RequestIDProvider = .autogenerated,
6875
requestIDHeader: String? = nil,
69-
cacheable: Bool = false
76+
cacheable: Bool = false,
77+
logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() })
7078
) {
7179
self.customMetadata = customMetadata
7280
self.messageEncoding = messageEncoding
7381
self.requestIDProvider = requestIDProvider
7482
self.requestIDHeader = requestIDHeader
7583
self.cacheable = false
7684
self.timeLimit = timeLimit
85+
self.logger = logger
7786
}
7887

7988
public struct RequestIDProvider {
8089
private enum RequestIDSource {
90+
case none
8191
case `static`(String)
8292
case generated(() -> String)
8393
}
@@ -87,15 +97,20 @@ public struct CallOptions {
8797
self.source = source
8898
}
8999

90-
internal func requestID() -> String {
100+
internal func requestID() -> String? {
91101
switch self.source {
102+
case .none:
103+
return nil
92104
case .static(let requestID):
93105
return requestID
94106
case .generated(let generator):
95107
return generator()
96108
}
97109
}
98110

111+
/// No request IDs are generated.
112+
public static let none = RequestIDProvider(.none)
113+
99114
/// Generate a new request ID for each RPC.
100115
public static let autogenerated = RequestIDProvider(.generated({ UUID().uuidString }))
101116

Sources/GRPC/ConnectionManager.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,10 @@ internal class ConnectionManager {
149149
self.monitor.updateState(to: .idle, logger: self.logger)
150150

151151
// Create a new id; it'll be used for the *next* channel we create.
152-
self.channelNumber &+= 1
153-
self.logger[metadataKey: MetadataKey.connectionID] = "\(self.connectionId)/\(self.channelNumber)"
152+
self.channelNumberLock.withLockVoid {
153+
self.channelNumber &+= 1
154+
}
155+
self.logger[metadataKey: MetadataKey.connectionID] = "\(self.connectionIDAndNumber)"
154156

155157
case .connecting:
156158
self.monitor.updateState(to: .connecting, logger: self.logger)
@@ -175,8 +177,19 @@ internal class ConnectionManager {
175177
internal let monitor: ConnectivityStateMonitor
176178
internal var logger: Logger
177179

178-
private let connectionId: String
180+
private let connectionID: String
179181
private var channelNumber: UInt64
182+
private var channelNumberLock = Lock()
183+
184+
private var connectionIDAndNumber: String {
185+
return self.channelNumberLock.withLock {
186+
return "\(self.connectionID)/\(self.channelNumber)"
187+
}
188+
}
189+
190+
internal func appendMetadata(to logger: inout Logger) {
191+
logger[metadataKey: MetadataKey.connectionID] = "\(self.connectionIDAndNumber)"
192+
}
180193

181194
// Only used for testing.
182195
private var channelProvider: (() -> EventLoopFuture<Channel>)?
@@ -205,9 +218,9 @@ internal class ConnectionManager {
205218
) {
206219
// Setup the logger.
207220
var logger = logger
208-
let connectionId = UUID().uuidString
221+
let connectionID = UUID().uuidString
209222
let channelNumber: UInt64 = 0
210-
logger[metadataKey: MetadataKey.connectionID] = "\(connectionId)/\(channelNumber)"
223+
logger[metadataKey: MetadataKey.connectionID] = "\(connectionID)/\(channelNumber)"
211224

212225
let eventLoop = configuration.eventLoopGroup.next()
213226
self.eventLoop = eventLoop
@@ -219,7 +232,7 @@ internal class ConnectionManager {
219232

220233
self.channelProvider = channelProvider
221234

222-
self.connectionId = connectionId
235+
self.connectionID = connectionID
223236
self.channelNumber = channelNumber
224237
self.logger = logger
225238
}

Sources/GRPC/FakeChannel.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class FakeChannel: GRPCChannel {
3333
/// A logger.
3434
public let logger: Logger
3535

36-
public init(logger: Logger = Logger(label: "io.grpc.testing")) {
36+
public init(logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() })) {
3737
self.responseStreams = [:]
3838
self.logger = logger
3939
}
@@ -217,8 +217,8 @@ extension FakeChannel {
217217
scheme: "http",
218218
path: path,
219219
host: "localhost",
220-
requestID: callOptions.requestIDProvider.requestID(),
221-
options: callOptions
220+
options: callOptions,
221+
requestID: nil
222222
)
223223
}
224224
}

Sources/GRPC/GRPCChannel/EmbeddedGRPCChannel.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ class EmbeddedGRPCChannel: GRPCChannel {
3636
return self.embeddedChannel.eventLoop
3737
}
3838

39-
init(errorDelegate: ClientErrorDelegate? = nil) {
39+
init(
40+
logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() }),
41+
errorDelegate: ClientErrorDelegate? = nil
42+
) {
4043
let embeddedChannel = EmbeddedChannel()
4144
self.embeddedChannel = embeddedChannel
42-
let logger = Logger(subsystem: .clientChannel)
4345
self.logger = logger
4446
self.multiplexer = embeddedChannel.configureGRPCClient(
4547
errorDelegate: errorDelegate,
@@ -57,8 +59,8 @@ class EmbeddedGRPCChannel: GRPCChannel {
5759
scheme: self.scheme,
5860
path: path,
5961
host: self.authority,
60-
requestID: options.requestIDProvider.requestID(),
61-
options: options
62+
options: options,
63+
requestID: nil
6264
)
6365
}
6466

Sources/GRPC/GRPCChannel/GRPCChannelBuilder.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import Dispatch
17+
import Logging
1718
import NIO
1819
import NIOSSL
1920

@@ -42,6 +43,7 @@ extension ClientConnection {
4243
private var connectionIdleTimeout: TimeAmount = .minutes(5)
4344
private var callStartBehavior: CallStartBehavior = .waitsForConnectivity
4445
private var httpTargetWindowSize: Int = 65535
46+
private var logger: Logger = Logger(label: "io.grpc", factory: { _ in SwiftLogNoOpLogHandler() })
4547

4648
fileprivate init(group: EventLoopGroup) {
4749
self.group = group
@@ -59,7 +61,8 @@ extension ClientConnection {
5961
connectionKeepalive: self.connectionKeepalive,
6062
connectionIdleTimeout: self.connectionIdleTimeout,
6163
callStartBehavior: self.callStartBehavior,
62-
httpTargetWindowSize: self.httpTargetWindowSize
64+
httpTargetWindowSize: self.httpTargetWindowSize,
65+
backgroundActivityLogger: self.logger
6366
)
6467
return ClientConnection(configuration: configuration)
6568
}
@@ -249,6 +252,19 @@ extension ClientConnection.Builder {
249252
}
250253
}
251254

255+
extension ClientConnection.Builder {
256+
/// Sets a logger to be used for background activity such as connection state changes. Defaults
257+
/// to a no-op logger if not explicitly set.
258+
///
259+
/// Note that individual RPCs will use the logger from `CallOptions`, not the logger specified
260+
/// here.
261+
@discardableResult
262+
public func withBackgroundActivityLogger(_ logger: Logger) -> Self {
263+
self.logger = logger
264+
return self
265+
}
266+
}
267+
252268
fileprivate extension Double {
253269
static func seconds(from amount: TimeAmount) -> Double {
254270
return Double(amount.nanoseconds) / 1_000_000_000

Sources/GRPC/GRPCServerRequestRoutingHandler.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,9 @@ extension GRPCServerRequestRoutingHandler: ChannelInboundHandler, RemovableChann
221221
self.logger.debug("making call handler", metadata: ["path": "\(requestHead.uri)"])
222222
let uriComponents = requestHead.uri.components(separatedBy: "/")
223223

224-
var logger = self.logger
225-
// Unset the channel handler: it shouldn't be used for downstream handlers.
226-
logger[metadataKey: MetadataKey.channelHandler] = nil
227-
228224
let context = CallHandlerContext(
229225
errorDelegate: self.errorDelegate,
230-
logger: logger,
226+
logger: self.logger,
231227
encoding: self.encoding
232228
)
233229

0 commit comments

Comments
 (0)