Skip to content

Commit 09563c1

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 09563c1

10 files changed

+551
-86
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

+56-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,43 +290,48 @@ 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.debug("reusing active connection \(connection)")
289300
waiter.promise.succeed(connection)
290301
} else {
302+
logger.debug("opening fresh connection (found \(connection) but we assume it seems inactive)")
291303
self.makeChannel(preference: waiter.preference).whenComplete { result in
292-
self.connect(result, waiter: waiter, replacing: connection)
304+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
293305
}
294306
}
295307
}
296308
case .create(let waiter):
309+
logger.debug("opening fresh connection (no connections to reuse available)")
297310
self.makeChannel(preference: waiter.preference).whenComplete { result in
298-
self.connect(result, waiter: waiter)
311+
self.connect(result, waiter: waiter, logger: logger)
299312
}
300313
case .replace(let connection, let waiter):
301314
connection.cancelIdleTimeout().flatMap {
302315
connection.close()
303316
}.whenComplete { _ in
304317
self.makeChannel(preference: waiter.preference).whenComplete { result in
305-
self.connect(result, waiter: waiter, replacing: connection)
318+
self.connect(result, waiter: waiter, replacing: connection, logger: logger)
306319
}
307320
}
308321
case .park(let connection):
309-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
322+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
323+
logger: logger.detachingRequestInformation(nil))
310324
case .closeProvider:
311325
self.closeAndDelete()
312326
case .none:
313327
break
314328
case .parkAnd(let connection, let action):
315-
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool)
316-
self.execute(action)
329+
connection.setIdleTimeout(timeout: self.configuration.maximumAllowedIdleTimeInConnectionPool,
330+
logger: logger.detachingRequestInformation(nil))
331+
self.execute(action, logger: logger)
317332
case .closeAnd(let connection, let action):
318333
connection.channel.close(promise: nil)
319-
self.execute(action)
334+
self.execute(action, logger: logger)
320335
case .cancel(let connection, let close):
321336
connection.cancel().whenComplete { _ in
322337
if close {
@@ -335,19 +350,25 @@ class HTTP1ConnectionProvider {
335350
}
336351
}
337352

338-
func getConnection(preference: HTTPClient.EventLoopPreference, setupComplete: EventLoopFuture<Void>) -> EventLoopFuture<Connection> {
353+
func getConnection(preference: HTTPClient.EventLoopPreference,
354+
setupComplete: EventLoopFuture<Void>,
355+
logger: Logger) -> EventLoopFuture<Connection> {
339356
let waiter = Waiter(promise: self.eventLoop.makePromise(), setupComplete: setupComplete, preference: preference)
340357

341358
let action: Action = self.lock.withLock {
342359
self.state.acquire(waiter: waiter)
343360
}
344361

345-
self.execute(action)
362+
logger.trace("getting a connection with waiter=\(waiter), action=\(action)")
363+
self.execute(action, logger: logger)
346364

347365
return waiter.promise.futureResult
348366
}
349367

350-
func connect(_ result: Result<Channel, Error>, waiter: Waiter, replacing closedConnection: Connection? = nil) {
368+
func connect(_ result: Result<Channel, Error>,
369+
waiter: Waiter,
370+
replacing closedConnection: Connection? = nil,
371+
logger: Logger) {
351372
let action: Action
352373
switch result {
353374
case .success(let channel):
@@ -366,11 +387,11 @@ class HTTP1ConnectionProvider {
366387
waiter.promise.fail(error)
367388
}
368389
waiter.setupComplete.whenComplete { _ in
369-
self.execute(action)
390+
self.execute(action, logger: logger)
370391
}
371392
}
372393

373-
func release(connection: Connection, closing: Bool) {
394+
func release(connection: Connection, closing: Bool, logger: Logger) {
374395
let action: Action = self.lock.withLock {
375396
self.state.release(connection: connection, closing: closing)
376397
}
@@ -381,31 +402,31 @@ class HTTP1ConnectionProvider {
381402
case .park, .closeProvider:
382403
// Since both `.park` and `.deleteProvider` are terminal in terms of execution,
383404
// we can execute them immediately
384-
self.execute(action)
405+
self.execute(action, logger: logger)
385406
case .cancel, .closeAnd, .create, .fail, .lease, .parkAnd, .replace:
386407
// This is needed to start a new stack, otherwise, since this is called on a previous
387408
// future completion handler chain, it will be growing indefinitely until the connection is closed.
388409
// We might revisit this when https://github.com/apple/swift-nio/issues/970 is resolved.
389410
connection.channel.eventLoop.execute {
390-
self.execute(action)
411+
self.execute(action, logger: logger)
391412
}
392413
}
393414
}
394415

395-
func remoteClosed(connection: Connection) {
416+
func remoteClosed(connection: Connection, logger: Logger) {
396417
let action: Action = self.lock.withLock {
397418
self.state.remoteClosed(connection: connection)
398419
}
399420

400-
self.execute(action)
421+
self.execute(action, logger: logger)
401422
}
402423

403-
func timeout(connection: Connection) {
424+
func timeout(connection: Connection, logger: Logger) {
404425
let action: Action = self.lock.withLock {
405426
self.state.timeout(connection: connection)
406427
}
407428

408-
self.execute(action)
429+
self.execute(action, logger: logger)
409430
}
410431

411432
private func closeAndDelete() {
@@ -510,25 +531,27 @@ class IdlePoolConnectionHandler: ChannelInboundHandler, RemovableChannelHandler
510531

511532
let connection: Connection
512533
var eventSent: Bool
534+
let logger: Logger
513535

514-
init(connection: Connection) {
536+
init(connection: Connection, logger: Logger) {
515537
self.connection = connection
516538
self.eventSent = false
539+
self.logger = logger
517540
}
518541

519542
// this is needed to detect when remote end closes connection while connection is in the pool idling
520543
func channelInactive(context: ChannelHandlerContext) {
521544
if !self.eventSent {
522545
self.eventSent = true
523-
self.connection.remoteClosed()
546+
self.connection.remoteClosed(logger: self.logger)
524547
}
525548
}
526549

527550
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
528551
if let idleEvent = event as? IdleStateHandler.IdleStateEvent, idleEvent == .write {
529552
if !self.eventSent {
530553
self.eventSent = true
531-
self.connection.timeout()
554+
self.connection.timeout(logger: self.logger)
532555
}
533556
} else {
534557
context.fireUserInboundEventTriggered(event)
+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)