Skip to content

Commit fec94fd

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 fec94fd

12 files changed

+650
-87
lines changed

Package.swift

Lines changed: 5 additions & 2 deletions
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

Lines changed: 65 additions & 33 deletions
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,65 @@ 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")
289300
waiter.promise.succeed(connection)
290301
} else {
302+
logger.trace("opening fresh connection (found matching but inactive connection)",
303+
metadata: ["ahc-dead-connection": "\(connection)"])
291304
self.makeChannel(preference: waiter.preference).whenComplete { result in
292-
self.connect(result, waiter: waiter, replacing: connection)
305+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
293306
}
294307
}
295308
}
296309
case .create(let waiter):
310+
logger.trace("opening fresh connection (no connections to reuse available)")
297311
self.makeChannel(preference: waiter.preference).whenComplete { result in
298-
self.connect(result, waiter: waiter)
312+
self.connect(result, waiter: waiter, logger: logger)
299313
}
300314
case .replace(let connection, let waiter):
315+
logger.trace("replacing \(connection), \(waiter)")
301316
connection.cancelIdleTimeout().flatMap {
302317
connection.close()
303318
}.whenComplete { _ in
319+
logger.trace("opening fresh connection (replacing \(connection), \(waiter)")
304320
self.makeChannel(preference: waiter.preference).whenComplete { result in
305-
self.connect(result, waiter: waiter, replacing: connection)
321+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
306322
}
307323
}
308324
case .park(let connection):
309-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
325+
logger.trace("parking connection")
326+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
327+
logger: logger.detachingRequestInformation(nil))
310328
case .closeProvider:
329+
logger.debug("closing provider \(self)")
311330
self.closeAndDelete()
312331
case .none:
313332
break
314333
case .parkAnd(let connection, let action):
315-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
316-
self.execute(action)
334+
logger.trace("parking connection & doing further action",
335+
metadata: ["ahc-action": "\(action)"])
336+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
337+
logger: logger.detachingRequestInformation(nil))
338+
self.execute(action, logger: logger)
317339
case .closeAnd(let connection, let action):
340+
logger.debug("closing \(connection), doing \(action)")
318341
connection.channel.close(promise: nil)
319-
self.execute(action)
342+
self.execute(action, logger: logger)
320343
case .cancel(let connection, let close):
344+
logger.debug("cancelling \(connection), close=\(close)")
321345
connection.cancel().whenComplete { _ in
322346
if close {
323347
self.closeAndDelete()
324348
}
325349
}
326350
case .fail(let waiter, let error):
351+
logger.debug("failing \(waiter): \(error)")
327352
waiter.promise.fail(error)
328353
}
329354
}
@@ -335,19 +360,24 @@ class HTTP1ConnectionProvider {
335360
}
336361
}
337362

338-
func getConnection(preference: HTTPClient.EventLoopPreference, setupComplete: EventLoopFuture<Void>) -> EventLoopFuture<Connection> {
363+
func getConnection(preference: HTTPClient.EventLoopPreference,
364+
setupComplete: EventLoopFuture<Void>,
365+
logger: Logger) -> EventLoopFuture<Connection> {
339366
let waiter = Waiter(promise: self.eventLoop.makePromise(), setupComplete: setupComplete, preference: preference)
340367

341368
let action: Action = self.lock.withLock {
342369
self.state.acquire(waiter: waiter)
343370
}
344371

345-
self.execute(action)
372+
self.execute(action, logger: logger)
346373

347374
return waiter.promise.futureResult
348375
}
349376

350-
func connect(_ result: Result<Channel, Error>, waiter: Waiter, replacing closedConnection: Connection? = nil) {
377+
func connect(_ result: Result<Channel, Error>,
378+
waiter: Waiter,
379+
replacing closedConnection: Connection? = nil,
380+
logger: Logger) {
351381
let action: Action
352382
switch result {
353383
case .success(let channel):
@@ -366,11 +396,11 @@ class HTTP1ConnectionProvider {
366396
waiter.promise.fail(error)
367397
}
368398
waiter.setupComplete.whenComplete { _ in
369-
self.execute(action)
399+
self.execute(action, logger: logger)
370400
}
371401
}
372402

373-
func release(connection: Connection, closing: Bool) {
403+
func release(connection: Connection, closing: Bool, logger: Logger) {
374404
let action: Action = self.lock.withLock {
375405
self.state.release(connection: connection, closing: closing)
376406
}
@@ -381,31 +411,31 @@ class HTTP1ConnectionProvider {
381411
case .park, .closeProvider:
382412
// Since both `.park` and `.deleteProvider` are terminal in terms of execution,
383413
// we can execute them immediately
384-
self.execute(action)
414+
self.execute(action, logger: logger)
385415
case .cancel, .closeAnd, .create, .fail, .lease, .parkAnd, .replace:
386416
// This is needed to start a new stack, otherwise, since this is called on a previous
387417
// future completion handler chain, it will be growing indefinitely until the connection is closed.
388418
// We might revisit this when https://github.com/apple/swift-nio/issues/970 is resolved.
389419
connection.channel.eventLoop.execute {
390-
self.execute(action)
420+
self.execute(action, logger: logger)
391421
}
392422
}
393423
}
394424

395-
func remoteClosed(connection: Connection) {
425+
func remoteClosed(connection: Connection, logger: Logger) {
396426
let action: Action = self.lock.withLock {
397427
self.state.remoteClosed(connection: connection)
398428
}
399429

400-
self.execute(action)
430+
self.execute(action, logger: logger)
401431
}
402432

403-
func timeout(connection: Connection) {
433+
func timeout(connection: Connection, logger: Logger) {
404434
let action: Action = self.lock.withLock {
405435
self.state.timeout(connection: connection)
406436
}
407437

408-
self.execute(action)
438+
self.execute(action, logger: logger)
409439
}
410440

411441
private func closeAndDelete() {
@@ -510,25 +540,27 @@ class IdlePoolConnectionHandler: ChannelInboundHandler, RemovableChannelHandler
510540

511541
let connection: Connection
512542
var eventSent: Bool
543+
let logger: Logger
513544

514-
init(connection: Connection) {
545+
init(connection: Connection, logger: Logger) {
515546
self.connection = connection
516547
self.eventSent = false
548+
self.logger = logger
517549
}
518550

519551
// this is needed to detect when remote end closes connection while connection is in the pool idling
520552
func channelInactive(context: ChannelHandlerContext) {
521553
if !self.eventSent {
522554
self.eventSent = true
523-
self.connection.remoteClosed()
555+
self.connection.remoteClosed(logger: self.logger)
524556
}
525557
}
526558

527559
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
528560
if let idleEvent = event as? IdleStateHandler.IdleStateEvent, idleEvent == .write {
529561
if !self.eventSent {
530562
self.eventSent = true
531-
self.connection.timeout()
563+
self.connection.timeout(logger: self.logger)
532564
}
533565
} else {
534566
context.fireUserInboundEventTriggered(event)
Lines changed: 40 additions & 0 deletions
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)