Skip to content

Commit 0396669

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 0396669

12 files changed

+666
-87
lines changed

Diff for: 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
)

Diff for: Sources/AsyncHTTPClient/ConnectionPool.swift

+78-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,19 +371,24 @@ 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):
@@ -366,11 +407,13 @@ class HTTP1ConnectionProvider {
366407
waiter.promise.fail(error)
367408
}
368409
waiter.setupComplete.whenComplete { _ in
369-
self.execute(action)
410+
self.execute(action, logger: logger)
370411
}
371412
}
372413

373-
func release(connection: Connection, closing: Bool) {
414+
func release(connection: Connection, closing: Bool, logger: Logger) {
415+
logger.debug("releasing connection, request complete",
416+
metadata: ["ahc-closing": "\(closing)"])
374417
let action: Action = self.lock.withLock {
375418
self.state.release(connection: connection, closing: closing)
376419
}
@@ -381,31 +424,31 @@ class HTTP1ConnectionProvider {
381424
case .park, .closeProvider:
382425
// Since both `.park` and `.deleteProvider` are terminal in terms of execution,
383426
// we can execute them immediately
384-
self.execute(action)
427+
self.execute(action, logger: logger)
385428
case .cancel, .closeAnd, .create, .fail, .lease, .parkAnd, .replace:
386429
// This is needed to start a new stack, otherwise, since this is called on a previous
387430
// future completion handler chain, it will be growing indefinitely until the connection is closed.
388431
// We might revisit this when https://github.com/apple/swift-nio/issues/970 is resolved.
389432
connection.channel.eventLoop.execute {
390-
self.execute(action)
433+
self.execute(action, logger: logger)
391434
}
392435
}
393436
}
394437

395-
func remoteClosed(connection: Connection) {
438+
func remoteClosed(connection: Connection, logger: Logger) {
396439
let action: Action = self.lock.withLock {
397440
self.state.remoteClosed(connection: connection)
398441
}
399442

400-
self.execute(action)
443+
self.execute(action, logger: logger)
401444
}
402445

403-
func timeout(connection: Connection) {
446+
func timeout(connection: Connection, logger: Logger) {
404447
let action: Action = self.lock.withLock {
405448
self.state.timeout(connection: connection)
406449
}
407450

408-
self.execute(action)
451+
self.execute(action, logger: logger)
409452
}
410453

411454
private func closeAndDelete() {
@@ -510,25 +553,27 @@ class IdlePoolConnectionHandler: ChannelInboundHandler, RemovableChannelHandler
510553

511554
let connection: Connection
512555
var eventSent: Bool
556+
let logger: Logger
513557

514-
init(connection: Connection) {
558+
init(connection: Connection, logger: Logger) {
515559
self.connection = connection
516560
self.eventSent = false
561+
self.logger = logger
517562
}
518563

519564
// this is needed to detect when remote end closes connection while connection is in the pool idling
520565
func channelInactive(context: ChannelHandlerContext) {
521566
if !self.eventSent {
522567
self.eventSent = true
523-
self.connection.remoteClosed()
568+
self.connection.remoteClosed(logger: self.logger)
524569
}
525570
}
526571

527572
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
528573
if let idleEvent = event as? IdleStateHandler.IdleStateEvent, idleEvent == .write {
529574
if !self.eventSent {
530575
self.eventSent = true
531-
self.connection.timeout()
576+
self.connection.timeout(logger: self.logger)
532577
}
533578
} else {
534579
context.fireUserInboundEventTriggered(event)

Diff for: Sources/AsyncHTTPClient/DoNotLogger.swift

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Logging
16+
17+
internal struct DoNotLogger: LogHandler {
18+
func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, file: String, function: String, line: UInt) {}
19+
20+
subscript(metadataKey _: String) -> Logger.Metadata.Value? {
21+
get {
22+
return nil
23+
}
24+
set {}
25+
}
26+
27+
var metadata: Logger.Metadata {
28+
get {
29+
return [:]
30+
}
31+
set {}
32+
}
33+
34+
var logLevel: Logger.Level {
35+
get {
36+
return .critical
37+
}
38+
set {}
39+
}
40+
}

0 commit comments

Comments
 (0)