Skip to content

Commit e57bd6f

Browse files
committed
logging support
Motivation: AsyncHTTPClient is not a simple piece of software and nowadays also quite stateful. To debug issues, the user may want logging. Modification: Support passing a logger to the request methods. Result: Debugging simplified.
1 parent 070c1e5 commit e57bd6f

12 files changed

+670
-87
lines changed

Package.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,18 @@ let package = Package(
2525
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.7.0"),
2626
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"),
2727
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"),
28+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
2829
],
2930
targets: [
3031
.target(
3132
name: "AsyncHTTPClient",
32-
dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression", "NIOFoundationCompat", "NIOTransportServices"]
33+
dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression",
34+
"NIOFoundationCompat", "NIOTransportServices", "Logging"]
3335
),
3436
.testTarget(
3537
name: "AsyncHTTPClientTests",
36-
dependencies: ["NIO", "NIOConcurrencyHelpers", "NIOSSL", "AsyncHTTPClient", "NIOFoundationCompat", "NIOTestUtils"]
38+
dependencies: ["NIO", "NIOConcurrencyHelpers", "NIOSSL", "AsyncHTTPClient", "NIOFoundationCompat",
39+
"NIOTestUtils", "Logging"]
3740
),
3841
]
3942
)

Sources/AsyncHTTPClient/ConnectionPool.swift

+82-33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Foundation
16+
import Logging
1617
import NIO
1718
import NIOConcurrencyHelpers
1819
import NIOHTTP1
@@ -64,21 +65,29 @@ final class ConnectionPool {
6465
/// When the pool is asked for a new connection, it creates a `Key` from the url associated to the `request`. This key
6566
/// is used to determine if there already exists an associated `HTTP1ConnectionProvider` in `providers`.
6667
/// If there is, the connection provider then takes care of leasing a new connection. If a connection provider doesn't exist, it is created.
67-
func getConnection(for request: HTTPClient.Request, preference: HTTPClient.EventLoopPreference, on eventLoop: EventLoop, deadline: NIODeadline?, setupComplete: EventLoopFuture<Void>) -> EventLoopFuture<Connection> {
68+
func getConnection(_ request: HTTPClient.Request,
69+
preference: HTTPClient.EventLoopPreference,
70+
taskEventLoop: EventLoop,
71+
deadline: NIODeadline?,
72+
setupComplete: EventLoopFuture<Void>,
73+
logger: Logger) -> EventLoopFuture<Connection> {
6874
let key = Key(request)
6975

7076
let provider: HTTP1ConnectionProvider = self.lock.withLock {
7177
if let existing = self.providers[key], existing.enqueue() {
7278
return existing
7379
} else {
7480
// Connection provider will be created with `pending = 1`
75-
let provider = HTTP1ConnectionProvider(key: key, eventLoop: eventLoop, configuration: self.configuration, pool: self)
81+
let provider = HTTP1ConnectionProvider(key: key,
82+
eventLoop: taskEventLoop,
83+
configuration: self.configuration,
84+
pool: self)
7685
self.providers[key] = provider
7786
return provider
7887
}
7988
}
8089

81-
return provider.getConnection(preference: preference, setupComplete: setupComplete)
90+
return provider.getConnection(preference: preference, setupComplete: setupComplete, logger: logger)
8291
}
8392

8493
func delete(_ provider: HTTP1ConnectionProvider) {
@@ -167,21 +176,21 @@ class Connection {
167176
/// Release this `Connection` to its associated `HTTP1ConnectionProvider`.
168177
///
169178
/// - Warning: This only releases the connection and doesn't take care of cleaning handlers in the `Channel` pipeline.
170-
func release(closing: Bool) {
179+
func release(closing: Bool, logger: Logger) {
171180
assert(self.channel.eventLoop.inEventLoop)
172-
self.provider.release(connection: self, closing: closing)
181+
self.provider.release(connection: self, closing: closing, logger: logger)
173182
}
174183

175184
/// Called when channel exceeds idle time in pool.
176-
func timeout() {
185+
func timeout(logger: Logger) {
177186
assert(self.channel.eventLoop.inEventLoop)
178-
self.provider.timeout(connection: self)
187+
self.provider.timeout(connection: self, logger: logger)
179188
}
180189

181190
/// Called when channel goes inactive while in the pool.
182-
func remoteClosed() {
191+
func remoteClosed(logger: Logger) {
183192
assert(self.channel.eventLoop.inEventLoop)
184-
self.provider.remoteClosed(connection: self)
193+
self.provider.remoteClosed(connection: self, logger: logger)
185194
}
186195

187196
func cancel() -> EventLoopFuture<Void> {
@@ -194,9 +203,10 @@ class Connection {
194203
}
195204

196205
/// Sets idle timeout handler and channel inactivity listener.
197-
func setIdleTimeout(timeout: TimeAmount?) {
206+
func setIdleTimeout(timeout: TimeAmount?, logger: Logger) {
198207
_ = self.channel.pipeline.addHandler(IdleStateHandler(writeTimeout: timeout), position: .first).flatMap { _ in
199-
self.channel.pipeline.addHandler(IdlePoolConnectionHandler(connection: self))
208+
self.channel.pipeline.addHandler(IdlePoolConnectionHandler(connection: self,
209+
logger: logger))
200210
}
201211
}
202212

@@ -280,50 +290,76 @@ class HTTP1ConnectionProvider {
280290
self.state.assertInvariants()
281291
}
282292

283-
private func execute(_ action: Action) {
293+
private func execute(_ action: Action, logger: Logger) {
284294
switch action {
285295
case .lease(let connection, let waiter):
286296
// if connection is became inactive, we create a new one.
287297
connection.cancelIdleTimeout().whenComplete { _ in
288298
if connection.isActiveEstimation {
299+
logger.trace("leasing existing connection",
300+
metadata: ["ahc-connection": "\(connection)"])
289301
waiter.promise.succeed(connection)
290302
} else {
303+
logger.trace("opening fresh connection (found matching but inactive connection)",
304+
metadata: ["ahc-dead-connection": "\(connection)"])
291305
self.makeChannel(preference: waiter.preference).whenComplete { result in
292-
self.connect(result, waiter: waiter, replacing: connection)
306+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
293307
}
294308
}
295309
}
296310
case .create(let waiter):
311+
logger.trace("opening fresh connection (no connections to reuse available)")
297312
self.makeChannel(preference: waiter.preference).whenComplete { result in
298-
self.connect(result, waiter: waiter)
313+
self.connect(result, waiter: waiter, logger: logger)
299314
}
300315
case .replace(let connection, let waiter):
301316
connection.cancelIdleTimeout().flatMap {
302317
connection.close()
303318
}.whenComplete { _ in
319+
logger.trace("opening fresh connection (replacing exising connection)",
320+
metadata: ["ahc-old-connection": "\(connection)",
321+
"ahc-waiter": "\(waiter)"])
304322
self.makeChannel(preference: waiter.preference).whenComplete { result in
305-
self.connect(result, waiter: waiter, replacing: connection)
323+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
306324
}
307325
}
308326
case .park(let connection):
309-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
327+
logger.trace("parking connection",
328+
metadata: ["ahc-connection": "\(connection)"])
329+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
330+
logger: logger.detachingRequestInformation())
310331
case .closeProvider:
332+
logger.debug("closing provider",
333+
metadata: ["ahc-provider": "\(self)"])
311334
self.closeAndDelete()
312335
case .none:
313336
break
314337
case .parkAnd(let connection, let action):
315-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
316-
self.execute(action)
338+
logger.trace("parking connection & doing further action",
339+
metadata: ["ahc-connection": "\(connection)",
340+
"ahc-action": "\(action)"])
341+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
342+
logger: logger.detachingRequestInformation())
343+
self.execute(action, logger: logger)
317344
case .closeAnd(let connection, let action):
345+
logger.trace("closing connection & doing further action",
346+
metadata: ["ahc-connection": "\(connection)",
347+
"ahc-action": "\(action)"])
318348
connection.channel.close(promise: nil)
319-
self.execute(action)
349+
self.execute(action, logger: logger)
320350
case .cancel(let connection, let close):
351+
logger.trace("cancelling connection",
352+
metadata: ["ahc-connection": "\(connection)",
353+
"ahc-close": "\(close)"])
321354
connection.cancel().whenComplete { _ in
322355
if close {
323356
self.closeAndDelete()
324357
}
325358
}
326359
case .fail(let waiter, let error):
360+
logger.debug("failing connection for waiter",
361+
metadata: ["ahc-waiter": "\(waiter)",
362+
"ahc-error": "\(error)"])
327363
waiter.promise.fail(error)
328364
}
329365
}
@@ -335,22 +371,29 @@ class HTTP1ConnectionProvider {
335371
}
336372
}
337373

338-
func getConnection(preference: HTTPClient.EventLoopPreference, setupComplete: EventLoopFuture<Void>) -> EventLoopFuture<Connection> {
374+
func getConnection(preference: HTTPClient.EventLoopPreference,
375+
setupComplete: EventLoopFuture<Void>,
376+
logger: Logger) -> EventLoopFuture<Connection> {
339377
let waiter = Waiter(promise: self.eventLoop.makePromise(), setupComplete: setupComplete, preference: preference)
340378

341379
let action: Action = self.lock.withLock {
342380
self.state.acquire(waiter: waiter)
343381
}
344382

345-
self.execute(action)
383+
self.execute(action, logger: logger)
346384

347385
return waiter.promise.futureResult
348386
}
349387

350-
func connect(_ result: Result<Channel, Error>, waiter: Waiter, replacing closedConnection: Connection? = nil) {
388+
func connect(_ result: Result<Channel, Error>,
389+
waiter: Waiter,
390+
replacing closedConnection: Connection? = nil,
391+
logger: Logger) {
351392
let action: Action
352393
switch result {
353394
case .success(let channel):
395+
logger.trace("successfully created connection",
396+
metadata: ["ahc-connection": "\(channel)"])
354397
let connection = Connection(channel: channel, provider: self)
355398
action = self.lock.withLock {
356399
if let closedConnection = closedConnection {
@@ -360,17 +403,21 @@ class HTTP1ConnectionProvider {
360403
}
361404
waiter.promise.succeed(connection)
362405
case .failure(let error):
406+
logger.debug("connection attempt failed",
407+
metadata: ["ahc-error": "\(error)"])
363408
action = self.lock.withLock {
364409
self.state.connectFailed()
365410
}
366411
waiter.promise.fail(error)
367412
}
368413
waiter.setupComplete.whenComplete { _ in
369-
self.execute(action)
414+
self.execute(action, logger: logger)
370415
}
371416
}
372417

373-
func release(connection: Connection, closing: Bool) {
418+
func release(connection: Connection, closing: Bool, logger: Logger) {
419+
logger.debug("releasing connection, request complete",
420+
metadata: ["ahc-closing": "\(closing)"])
374421
let action: Action = self.lock.withLock {
375422
self.state.release(connection: connection, closing: closing)
376423
}
@@ -381,31 +428,31 @@ class HTTP1ConnectionProvider {
381428
case .park, .closeProvider:
382429
// Since both `.park` and `.deleteProvider` are terminal in terms of execution,
383430
// we can execute them immediately
384-
self.execute(action)
431+
self.execute(action, logger: logger)
385432
case .cancel, .closeAnd, .create, .fail, .lease, .parkAnd, .replace:
386433
// This is needed to start a new stack, otherwise, since this is called on a previous
387434
// future completion handler chain, it will be growing indefinitely until the connection is closed.
388435
// We might revisit this when https://github.com/apple/swift-nio/issues/970 is resolved.
389436
connection.channel.eventLoop.execute {
390-
self.execute(action)
437+
self.execute(action, logger: logger)
391438
}
392439
}
393440
}
394441

395-
func remoteClosed(connection: Connection) {
442+
func remoteClosed(connection: Connection, logger: Logger) {
396443
let action: Action = self.lock.withLock {
397444
self.state.remoteClosed(connection: connection)
398445
}
399446

400-
self.execute(action)
447+
self.execute(action, logger: logger)
401448
}
402449

403-
func timeout(connection: Connection) {
450+
func timeout(connection: Connection, logger: Logger) {
404451
let action: Action = self.lock.withLock {
405452
self.state.timeout(connection: connection)
406453
}
407454

408-
self.execute(action)
455+
self.execute(action, logger: logger)
409456
}
410457

411458
private func closeAndDelete() {
@@ -510,25 +557,27 @@ class IdlePoolConnectionHandler: ChannelInboundHandler, RemovableChannelHandler
510557

511558
let connection: Connection
512559
var eventSent: Bool
560+
let logger: Logger
513561

514-
init(connection: Connection) {
562+
init(connection: Connection, logger: Logger) {
515563
self.connection = connection
516564
self.eventSent = false
565+
self.logger = logger
517566
}
518567

519568
// this is needed to detect when remote end closes connection while connection is in the pool idling
520569
func channelInactive(context: ChannelHandlerContext) {
521570
if !self.eventSent {
522571
self.eventSent = true
523-
self.connection.remoteClosed()
572+
self.connection.remoteClosed(logger: self.logger)
524573
}
525574
}
526575

527576
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
528577
if let idleEvent = event as? IdleStateHandler.IdleStateEvent, idleEvent == .write {
529578
if !self.eventSent {
530579
self.eventSent = true
531-
self.connection.timeout()
580+
self.connection.timeout(logger: self.logger)
532581
}
533582
} else {
534583
context.fireUserInboundEventTriggered(event)

0 commit comments

Comments
 (0)