Skip to content

Commit

Permalink
feat: add logging HTTP requests and responses (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
bednar authored Jul 14, 2022
1 parent 976e116 commit 149e20c
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 1.3.0 [unreleased]

### Features
1. [#52](https://github.com/influxdata/influxdb-client-swift/pull/52): Add logging for HTTP requests

## 1.2.0 [2022-05-20]

### Features
Expand Down
7 changes: 6 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ let package = Package(
.package(name: "Gzip", url: "https://github.com/1024jp/GzipSwift", from: "5.1.1"),
.package(name: "CSV.swift", url: "https://github.com/yaslab/CSV.swift", from: "2.4.2"),
.package(name: "SwiftTestReporter", url: "https://github.com/allegro/swift-junit.git", from: "2.0.0"),
.package(name: "swift-log", url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(name: "InfluxDBSwift", dependencies: ["Gzip", .product(name: "CSV", package: "CSV.swift")]),
.target(name: "InfluxDBSwift", dependencies: [
"Gzip",
.product(name: "CSV", package: "CSV.swift"),
.product(name: "Logging", package: "swift-log")
]),
.target(name: "InfluxDBSwiftApis", dependencies: ["InfluxDBSwift"]),
.testTarget(name: "InfluxDBSwiftTests", dependencies: ["InfluxDBSwift", "SwiftTestReporter"]),
.testTarget(name: "InfluxDBSwiftApisTests", dependencies: ["InfluxDBSwiftApis", "SwiftTestReporter"]),
Expand Down
51 changes: 26 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ client.close()

#### Client Options

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

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

Expand Down Expand Up @@ -589,23 +590,23 @@ DeleteData.main()

The client supports following management API:

| | API docs |
| --- |---------------------------------------------------------------------|
| [**AuthorizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/AuthorizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Authorizations |
| [**BucketsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/BucketsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Buckets |
| [**DBRPsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/DBRPsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/DBRPs |
| [**HealthAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/HealthAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Health |
| [**PingAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/PingAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ping |
| [**LabelsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/LabelsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Labels |
| [**OrganizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/OrganizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Organizations |
| [**ReadyAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ReadyAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ready |
| [**ScraperTargetsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ScraperTargetsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/ScraperTargets |
| [**SecretsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SecretsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Secrets |
| [**SetupAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SetupAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
| [**SourcesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SourcesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Sources |
| [**TasksAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/TasksAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
| [**UsersAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/UsersAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Users |
| [**VariablesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/VariablesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Variables |
| | API docs |
|-------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
| [**AuthorizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/AuthorizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Authorizations |
| [**BucketsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/BucketsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Buckets |
| [**DBRPsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/DBRPsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/DBRPs |
| [**HealthAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/HealthAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Health |
| [**PingAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/PingAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ping |
| [**LabelsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/LabelsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Labels |
| [**OrganizationsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/OrganizationsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Organizations |
| [**ReadyAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ReadyAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Ready |
| [**ScraperTargetsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/ScraperTargetsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/ScraperTargets |
| [**SecretsAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SecretsAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Secrets |
| [**SetupAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SetupAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
| [**SourcesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/SourcesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Sources |
| [**TasksAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/TasksAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Tasks |
| [**UsersAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/UsersAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Users |
| [**VariablesAPI**](https://influxdata.github.io/influxdb-client-swift/Classes/InfluxDB2API/VariablesAPI.html) | https://docs.influxdata.com/influxdb/latest/api/#tag/Variables |


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).
Expand Down
15 changes: 14 additions & 1 deletion Sources/InfluxDBSwift/InfluxDBClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class InfluxDBClient {
internal let token: String
/// The options to configure client.
internal let options: InfluxDBOptions
/// Enable debugging for HTTP request/response.
public let debugging: Bool
/// Shared URLSession across the client.
public let session: URLSession

Expand All @@ -55,14 +57,20 @@ public class InfluxDBClient {
/// - url: InfluxDB host and port.
/// - token: Authentication token.
/// - options: optional `InfluxDBOptions` to use for this client.
/// - debugging: optional Enable debugging for HTTP request/response. Default `false`.
/// - protocolClasses: optional array of extra protocol subclasses that handle requests.
///
/// - SeeAlso: https://docs.influxdata.com/influxdb/latest/reference/urls/#influxdb-oss-urls
/// - SeeAlso: https://docs.influxdata.com/influxdb/latest/security/tokens/
public init(url: String, token: String, options: InfluxDBOptions? = nil, protocolClasses: [AnyClass]? = nil) {
public init(url: String,
token: String,
options: InfluxDBOptions? = nil,
debugging: Bool? = nil,
protocolClasses: [AnyClass]? = nil) {
self.url = url.hasSuffix("/") ? String(url.dropLast(1)) : url
self.token = token
self.options = options ?? InfluxDBClient.InfluxDBOptions()
self.debugging = debugging ?? false

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

let logger = InfluxDBClient.HTTPLogger(debugging: debugging)
logger.log(request)

let task = session.dataTask(with: request) { data, response, error in
responseQueue.async {
if let error = error {
Expand All @@ -329,6 +340,8 @@ extension InfluxDBClient {
return
}

logger.log(httpResponse, data)

guard Array(200..<300).contains(httpResponse.statusCode) else {
completion(.failure(InfluxDBClient.InfluxDBError.error(
httpResponse.statusCode,
Expand Down
71 changes: 71 additions & 0 deletions Sources/InfluxDBSwift/Internal/LoggingHelper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Created by Jakub Bednář on 13.07.2022.
//

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
import Logging

extension InfluxDBClient {
/// The logger for logging HTTP request/response.
public class HTTPLogger {
fileprivate var logger: Logger {
var logger = Logger(label: "http-logger")
logger.logLevel = .debug
return logger
}
/// Enable debugging for HTTP request/response.
internal let debugging: Bool

/// Create a new HTTPLogger.
///
/// - Parameters:
/// - debugging: optional Enable debugging for HTTP request/response. Default `false`.
public init(debugging: Bool? = nil) {
self.debugging = debugging ?? false
}

/// Log the HTTP request.
///
/// - Parameter request: to log
public func log(_ request: URLRequest?) {
if debugging {
logger.debug(">>> Request: '\(request?.httpMethod ?? "") \(request?.url?.absoluteString ?? "")'")
log_headers(headers: request?.allHTTPHeaderFields, prefix: ">>>")
log_body(body: request?.httpBody, prefix: ">>>")
}
}

/// Log the HTTP response.
///
/// - Parameters:
/// - response: to log
/// - data: response data
public func log(_ response: URLResponse?, _ data: Data?) {
if debugging {
let httpResponse = response as? HTTPURLResponse
logger.debug("<<< Response: \(httpResponse?.statusCode ?? 0)")
log_headers(headers: httpResponse?.allHeaderFields, prefix: "<<<")
log_body(body: data, prefix: "<<<")
}
}

func log_body(body: Data?, prefix: String) {
if let body = body {
logger.debug("\(prefix) Body: \(String(decoding: body, as: UTF8.self))")
}
}

func log_headers(headers: [AnyHashable: Any]?, prefix: String) {
headers?.forEach { key, value in
var sanitized = value
if "authorization" == String(describing: key).lowercased() {
sanitized = "***"
}
logger.debug("\(prefix) \(key): \(sanitized)")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,15 @@ internal class URLSessionRequestBuilder<T>: RequestBuilder<T> {

do {
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)


let logger = InfluxDBClient.HTTPLogger(debugging: influxDB2API.client.debugging)
logger.log(request)

let dataTask = urlSession.dataTask(with: request) { [weak self] data, response, error in

guard let self = self else { return }

logger.log(response, data)

if let taskCompletionShouldRetry = self.taskCompletionShouldRetry {

Expand Down
59 changes: 59 additions & 0 deletions Tests/InfluxDBSwiftTests/InfluxDBClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
import Logging

@testable import InfluxDBSwift
import XCTest
Expand Down Expand Up @@ -175,6 +176,33 @@ final class InfluxDBClientTests: XCTestCase {

waitForExpectations(timeout: 1, handler: nil)
}

func testHTTPLogging() {
TestLogHandler.content = ""
let expectation = self.expectation(description: "Success response from API doesn't arrive")
LoggingSystem.bootstrap(TestLogHandler.init)

client = InfluxDBClient(url: Self.dbURL(), token: "my-token", debugging: true)

MockURLProtocol.handler = { _, _ in
expectation.fulfill()

let response = HTTPURLResponse(statusCode: 200)
return (response, "csv".data(using: .utf8)!)
}

client.queryAPI.query(query: "from(bucket:\"my-bucket\") |> range(start: -1h)", org: "my-org") { _, error in
if let error = error {
XCTFail("Error occurs: \(error)")
}

expectation.fulfill()
}

waitForExpectations(timeout: 1, handler: nil)

XCTAssertTrue(TestLogHandler.content.contains("Authorization: ***"), TestLogHandler.content)
}
}

final class InfluxDBErrorTests: XCTestCase {
Expand All @@ -199,3 +227,34 @@ extension XCTestCase {
return "http://localhost:8086"
}
}

internal class TestLogHandler: LogHandler {
var metadata = Logger.Metadata()
var logLevel = Logger.Level.debug
static var content = ""

init(label: String) {
}

subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
metadata[metadataKey]
}
set {
metadata[metadataKey] = newValue
}
}

// swiftlint:disable function_parameter_count
func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
source: String,
file: String,
function: String,
line: UInt) {
Self.content.append(message.description)
Self.content.append("\n")
}
// swiftlint:enable function_parameter_count
}

0 comments on commit 149e20c

Please sign in to comment.