Skip to content

Commit 149e20c

Browse files
authored
feat: add logging HTTP requests and responses (#52)
1 parent 976e116 commit 149e20c

File tree

7 files changed

+185
-28
lines changed

7 files changed

+185
-28
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 1.3.0 [unreleased]
22

3+
### Features
4+
1. [#52](https://github.com/influxdata/influxdb-client-swift/pull/52): Add logging for HTTP requests
5+
36
## 1.2.0 [2022-05-20]
47

58
### Features

Package.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ let package = Package(
1414
.package(name: "Gzip", url: "https://github.com/1024jp/GzipSwift", from: "5.1.1"),
1515
.package(name: "CSV.swift", url: "https://github.com/yaslab/CSV.swift", from: "2.4.2"),
1616
.package(name: "SwiftTestReporter", url: "https://github.com/allegro/swift-junit.git", from: "2.0.0"),
17+
.package(name: "swift-log", url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
1718
],
1819
targets: [
1920
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2021
// Targets can depend on other targets in this package, and on products in packages this package depends on.
21-
.target(name: "InfluxDBSwift", dependencies: ["Gzip", .product(name: "CSV", package: "CSV.swift")]),
22+
.target(name: "InfluxDBSwift", dependencies: [
23+
"Gzip",
24+
.product(name: "CSV", package: "CSV.swift"),
25+
.product(name: "Logging", package: "swift-log")
26+
]),
2227
.target(name: "InfluxDBSwiftApis", dependencies: ["InfluxDBSwift"]),
2328
.testTarget(name: "InfluxDBSwiftTests", dependencies: ["InfluxDBSwift", "SwiftTestReporter"]),
2429
.testTarget(name: "InfluxDBSwiftApisTests", dependencies: ["InfluxDBSwiftApis", "SwiftTestReporter"]),

README.md

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -106,14 +106,15 @@ client.close()
106106

107107
#### Client Options
108108

109-
| Option | Description | Type | Default |
110-
|---|---|---|---|
111-
| bucket | Default destination bucket for writes | String | none |
112-
| org | Default organization bucket for writes | String | none |
113-
| precision | Default precision for the unix timestamps within the body line-protocol | TimestampPrecision | ns |
114-
| timeoutIntervalForRequest | The timeout interval to use when waiting for additional data. | TimeInterval | 60 sec |
115-
| timeoutIntervalForResource | The maximum amount of time that a resource request should be allowed to take. | TimeInterval | 5 min |
116-
| enableGzip | Enable Gzip compression for HTTP requests. | Bool | false |
109+
| Option | Description | Type | Default |
110+
|----------------------------|-------------------------------------------------------------------------------|--------------------|---------|
111+
| bucket | Default destination bucket for writes | String | none |
112+
| org | Default organization bucket for writes | String | none |
113+
| precision | Default precision for the unix timestamps within the body line-protocol | TimestampPrecision | ns |
114+
| timeoutIntervalForRequest | The timeout interval to use when waiting for additional data. | TimeInterval | 60 sec |
115+
| timeoutIntervalForResource | The maximum amount of time that a resource request should be allowed to take. | TimeInterval | 5 min |
116+
| enableGzip | Enable Gzip compression for HTTP requests. | Bool | false |
117+
| debugging | Enable debugging for HTTP request/response. | Bool | false |
117118

118119
##### Configure default `Bucket`, `Organization` and `Precision`
119120

@@ -589,23 +590,23 @@ DeleteData.main()
589590

590591
The client supports following management API:
591592

592-
| | API docs |
593-
| --- |---------------------------------------------------------------------|
594-
| [**AuthorizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/AuthorizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Authorizations |
595-
| [**BucketsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/BucketsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Buckets |
596-
| [**DBRPsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/DBRPsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/DBRPs |
597-
| [**HealthAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/HealthAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Health |
598-
| [**PingAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/PingAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ping |
599-
| [**LabelsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/LabelsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Labels |
600-
| [**OrganizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/OrganizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Organizations |
601-
| [**ReadyAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ReadyAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ready |
602-
| [**ScraperTargetsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ScraperTargetsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/ScraperTargets |
603-
| [**SecretsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SecretsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Secrets |
604-
| [**SetupAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SetupAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
605-
| [**SourcesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SourcesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Sources |
606-
| [**TasksAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/TasksAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
607-
| [**UsersAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/UsersAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Users |
608-
| [**VariablesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/VariablesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Variables |
593+
| | API docs |
594+
|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
595+
| [**AuthorizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/AuthorizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Authorizations |
596+
| [**BucketsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/BucketsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Buckets |
597+
| [**DBRPsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/DBRPsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/DBRPs |
598+
| [**HealthAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/HealthAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Health |
599+
| [**PingAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/PingAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ping |
600+
| [**LabelsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/LabelsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Labels |
601+
| [**OrganizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/OrganizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Organizations |
602+
| [**ReadyAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ReadyAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ready |
603+
| [**ScraperTargetsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ScraperTargetsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/ScraperTargets |
604+
| [**SecretsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SecretsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Secrets |
605+
| [**SetupAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SetupAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
606+
| [**SourcesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SourcesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Sources |
607+
| [**TasksAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/TasksAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
608+
| [**UsersAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/UsersAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Users |
609+
| [**VariablesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/VariablesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Variables |
609610

610611

611612
The following example demonstrates how to use a InfluxDB 2.0 Management API to create new bucket. For further information see docs and [examples](/Examples).

Sources/InfluxDBSwift/InfluxDBClient.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public class InfluxDBClient {
3131
internal let token: String
3232
/// The options to configure client.
3333
internal let options: InfluxDBOptions
34+
/// Enable debugging for HTTP request/response.
35+
public let debugging: Bool
3436
/// Shared URLSession across the client.
3537
public let session: URLSession
3638

@@ -55,14 +57,20 @@ public class InfluxDBClient {
5557
/// - url: InfluxDB host and port.
5658
/// - token: Authentication token.
5759
/// - options: optional `InfluxDBOptions` to use for this client.
60+
/// - debugging: optional Enable debugging for HTTP request/response. Default `false`.
5861
/// - protocolClasses: optional array of extra protocol subclasses that handle requests.
5962
///
6063
/// - SeeAlso: https://docs.influxdata.com/influxdb/latest/reference/urls/#influxdb-oss-urls
6164
/// - SeeAlso: https://docs.influxdata.com/influxdb/latest/security/tokens/
62-
public init(url: String, token: String, options: InfluxDBOptions? = nil, protocolClasses: [AnyClass]? = nil) {
65+
public init(url: String,
66+
token: String,
67+
options: InfluxDBOptions? = nil,
68+
debugging: Bool? = nil,
69+
protocolClasses: [AnyClass]? = nil) {
6370
self.url = url.hasSuffix("/") ? String(url.dropLast(1)) : url
6471
self.token = token
6572
self.options = options ?? InfluxDBClient.InfluxDBOptions()
73+
self.debugging = debugging ?? false
6674

6775
var headers: [AnyHashable: Any] = [:]
6876
headers["Authorization"] = "Token \(token)"
@@ -309,6 +317,9 @@ extension InfluxDBClient {
309317
request.setValue("\(value)", forHTTPHeaderField: "\(key)")
310318
}
311319

320+
let logger = InfluxDBClient.HTTPLogger(debugging: debugging)
321+
logger.log(request)
322+
312323
let task = session.dataTask(with: request) { data, response, error in
313324
responseQueue.async {
314325
if let error = error {
@@ -329,6 +340,8 @@ extension InfluxDBClient {
329340
return
330341
}
331342

343+
logger.log(httpResponse, data)
344+
332345
guard Array(200..<300).contains(httpResponse.statusCode) else {
333346
completion(.failure(InfluxDBClient.InfluxDBError.error(
334347
httpResponse.statusCode,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// Created by Jakub Bednář on 13.07.2022.
3+
//
4+
5+
import Foundation
6+
#if canImport(FoundationNetworking)
7+
import FoundationNetworking
8+
#endif
9+
import Logging
10+
11+
extension InfluxDBClient {
12+
/// The logger for logging HTTP request/response.
13+
public class HTTPLogger {
14+
fileprivate var logger: Logger {
15+
var logger = Logger(label: "http-logger")
16+
logger.logLevel = .debug
17+
return logger
18+
}
19+
/// Enable debugging for HTTP request/response.
20+
internal let debugging: Bool
21+
22+
/// Create a new HTTPLogger.
23+
///
24+
/// - Parameters:
25+
/// - debugging: optional Enable debugging for HTTP request/response. Default `false`.
26+
public init(debugging: Bool? = nil) {
27+
self.debugging = debugging ?? false
28+
}
29+
30+
/// Log the HTTP request.
31+
///
32+
/// - Parameter request: to log
33+
public func log(_ request: URLRequest?) {
34+
if debugging {
35+
logger.debug(">>> Request: '\(request?.httpMethod ?? "") \(request?.url?.absoluteString ?? "")'")
36+
log_headers(headers: request?.allHTTPHeaderFields, prefix: ">>>")
37+
log_body(body: request?.httpBody, prefix: ">>>")
38+
}
39+
}
40+
41+
/// Log the HTTP response.
42+
///
43+
/// - Parameters:
44+
/// - response: to log
45+
/// - data: response data
46+
public func log(_ response: URLResponse?, _ data: Data?) {
47+
if debugging {
48+
let httpResponse = response as? HTTPURLResponse
49+
logger.debug("<<< Response: \(httpResponse?.statusCode ?? 0)")
50+
log_headers(headers: httpResponse?.allHeaderFields, prefix: "<<<")
51+
log_body(body: data, prefix: "<<<")
52+
}
53+
}
54+
55+
func log_body(body: Data?, prefix: String) {
56+
if let body = body {
57+
logger.debug("\(prefix) Body: \(String(decoding: body, as: UTF8.self))")
58+
}
59+
}
60+
61+
func log_headers(headers: [AnyHashable: Any]?, prefix: String) {
62+
headers?.forEach { key, value in
63+
var sanitized = value
64+
if "authorization" == String(describing: key).lowercased() {
65+
sanitized = "***"
66+
}
67+
logger.debug("\(prefix) \(key): \(sanitized)")
68+
}
69+
}
70+
}
71+
}

Sources/InfluxDBSwiftApis/Generated/URLSessionImplementations.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,15 @@ internal class URLSessionRequestBuilder<T>: RequestBuilder<T> {
109109

110110
do {
111111
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
112-
112+
113+
let logger = InfluxDBClient.HTTPLogger(debugging: influxDB2API.client.debugging)
114+
logger.log(request)
115+
113116
let dataTask = urlSession.dataTask(with: request) { [weak self] data, response, error in
114117

115118
guard let self = self else { return }
119+
120+
logger.log(response, data)
116121

117122
if let taskCompletionShouldRetry = self.taskCompletionShouldRetry {
118123

Tests/InfluxDBSwiftTests/InfluxDBClientTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Foundation
66
#if canImport(FoundationNetworking)
77
import FoundationNetworking
88
#endif
9+
import Logging
910

1011
@testable import InfluxDBSwift
1112
import XCTest
@@ -175,6 +176,33 @@ final class InfluxDBClientTests: XCTestCase {
175176

176177
waitForExpectations(timeout: 1, handler: nil)
177178
}
179+
180+
func testHTTPLogging() {
181+
TestLogHandler.content = ""
182+
let expectation = self.expectation(description: "Success response from API doesn't arrive")
183+
LoggingSystem.bootstrap(TestLogHandler.init)
184+
185+
client = InfluxDBClient(url: Self.dbURL(), token: "my-token", debugging: true)
186+
187+
MockURLProtocol.handler = { _, _ in
188+
expectation.fulfill()
189+
190+
let response = HTTPURLResponse(statusCode: 200)
191+
return (response, "csv".data(using: .utf8)!)
192+
}
193+
194+
client.queryAPI.query(query: "from(bucket:\"my-bucket\") |> range(start: -1h)", org: "my-org") { _, error in
195+
if let error = error {
196+
XCTFail("Error occurs: \(error)")
197+
}
198+
199+
expectation.fulfill()
200+
}
201+
202+
waitForExpectations(timeout: 1, handler: nil)
203+
204+
XCTAssertTrue(TestLogHandler.content.contains("Authorization: ***"), TestLogHandler.content)
205+
}
178206
}
179207

180208
final class InfluxDBErrorTests: XCTestCase {
@@ -199,3 +227,34 @@ extension XCTestCase {
199227
return "http://localhost:8086"
200228
}
201229
}
230+
231+
internal class TestLogHandler: LogHandler {
232+
var metadata = Logger.Metadata()
233+
var logLevel = Logger.Level.debug
234+
static var content = ""
235+
236+
init(label: String) {
237+
}
238+
239+
subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
240+
get {
241+
metadata[metadataKey]
242+
}
243+
set {
244+
metadata[metadataKey] = newValue
245+
}
246+
}
247+
248+
// swiftlint:disable function_parameter_count
249+
func log(level: Logger.Level,
250+
message: Logger.Message,
251+
metadata: Logger.Metadata?,
252+
source: String,
253+
file: String,
254+
function: String,
255+
line: UInt) {
256+
Self.content.append(message.description)
257+
Self.content.append("\n")
258+
}
259+
// swiftlint:enable function_parameter_count
260+
}

0 commit comments

Comments
 (0)