From e6bbd063fae4f2b53bbb1584c2c48ec4a534af5f Mon Sep 17 00:00:00 2001 From: Michal A Date: Thu, 27 Aug 2020 17:10:01 +0800 Subject: [PATCH 01/12] Instrument AWSClient WIP --- Package.swift | 7 + Sources/AWSSDKSwiftCore/AWSClient.swift | 69 +++++++-- Sources/AWSTestUtils/TestTracer.swift | 139 ++++++++++++++++++ Tests/AWSSDKSwiftCoreTests/TracingTests.swift | 81 ++++++++++ 4 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 Sources/AWSTestUtils/TestTracer.swift create mode 100644 Tests/AWSSDKSwiftCoreTests/TracingTests.swift diff --git a/Package.swift b/Package.swift index e8e273265a..0f4d14e9cd 100644 --- a/Package.swift +++ b/Package.swift @@ -27,6 +27,9 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio-ssl.git", .upToNextMajor(from: "2.7.2")), .package(url: "https://github.com/apple/swift-nio-transport-services.git", .upToNextMajor(from: "1.0.0")), .package(url: "https://github.com/swift-server/async-http-client.git", .upToNextMajor(from: "1.2.0")), + .package(url: "https://github.com/slashmo/gsoc-swift-baggage-context.git", .upToNextMinor(from: "0.3.0")), + // TODO: use version when released + .package(url: "https://github.com/slashmo/gsoc-swift-tracing.git", .revision("fe80d764ad225b1dfd06dcb57d08b5e3485662f9")), ], targets: [ .target(name: "AWSSDKSwiftCore", dependencies: [ @@ -41,6 +44,8 @@ let package = Package( .product(name: "NIOSSL", package: "swift-nio-ssl"), .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), .product(name: "NIOFoundationCompat", package: "swift-nio"), + .product(name: "Baggage", package: "swift-baggage-context"), + .product(name: "TracingInstrumentation", package: "gsoc-swift-tracing"), ]), .target(name: "AWSCrypto", dependencies: []), .target(name: "AWSSignerV4", dependencies: [ @@ -53,6 +58,8 @@ let package = Package( .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), .product(name: "NIOTestUtils", package: "swift-nio"), + .product(name: "Baggage", package: "swift-baggage-context"), + .product(name: "TracingInstrumentation", package: "gsoc-swift-tracing"), ]), .target(name: "AWSXML", dependencies: [ .byName(name: "CAWSExpat"), diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 47dd7e25d0..3b00637e3c 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -15,23 +15,42 @@ import AsyncHTTPClient import AWSSignerV4 import AWSXML +import Baggage import Dispatch import struct Foundation.Data import class Foundation.JSONDecoder import class Foundation.JSONSerialization import struct Foundation.URL import struct Foundation.URLQueryItem +import Instrumentation import Logging import Metrics import NIO import NIOConcurrencyHelpers import NIOHTTP1 import NIOTransportServices +import TracingInstrumentation + +// TODO: instrument initialization +// TODO: create internal spans for signing, parsing? + +// TODO: remove and use optional parameter or dont provide default value +public struct AWSClientContext: AWSClient.Context { + public static var empty: AWSClientContext = .init(baggage: .init()) + + public var baggage: BaggageContext +} /// This is the workhorse of aws-sdk-swift-core. You provide it with a `AWSShape` Input object, it converts it to `AWSRequest` which is then converted /// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a `AWSResponse` /// which is then decoded to generate a `AWSShape` Output object. If it is not successful then `AWSClient` will throw an `AWSErrorType`. public final class AWSClient { + /// AWS operation context including trace context + public typealias Context = Baggage.BaggageContextCarrier + // TODO: import BaggageLogging and add confirmance to LoggingBaggageContextCarrier + // when naming and conventions are confirmed https://github.com/slashmo/gsoc-swift-baggage-context/issues/23 + // remove logger parameter (it will be provided in the context) + /// Errors returned by AWSClient code public struct ClientError: Swift.Error, Equatable { enum Error { @@ -185,7 +204,12 @@ public final class AWSClient { // invoker extension AWSClient { - fileprivate func invoke(with serviceConfig: AWSServiceConfig, logger: Logger, _ request: @escaping () -> EventLoopFuture) -> EventLoopFuture { + fileprivate func invoke( + with serviceConfig: AWSServiceConfig, + logger: Logger, + context: Context, + _ request: @escaping () -> EventLoopFuture + ) -> EventLoopFuture { let eventloop = self.eventLoopGroup.next() let promise = eventloop.makePromise(of: AWSHTTPResponse.self) @@ -222,15 +246,28 @@ extension AWSClient { } /// invoke HTTP request - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { - return invoke(with: serviceConfig, logger: logger) { + fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger, context: Context) -> EventLoopFuture { + // TODO: what should be the operation name? + let operationName: String = httpRequest.url.path + var span = InstrumentationSystem.tracingInstrument.startSpan(named: operationName, context: context, ofKind: .client, at: .now()) + return invoke(with: serviceConfig, logger: logger, context: context) { + // TODO: record retries and set HTTP attributes using instrumented HTTP client +// let httpClientContext = span.context return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, logger: logger) } + // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 + .always { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() + } } /// invoke HTTP request with response streaming - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger, stream: @escaping AWSHTTPClient.ResponseStream) -> EventLoopFuture { - return invoke(with: serviceConfig, logger: logger) { + fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger, context: Context, stream: @escaping AWSHTTPClient.ResponseStream) -> EventLoopFuture { + // TODO: instrument + return invoke(with: serviceConfig, logger: logger, context: context) { return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, logger: logger, stream: stream) } } @@ -258,6 +295,7 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, + context: Context = AWSClientContext.empty, on eventLoop: EventLoop? = nil, logger: Logger = AWSClient.loggingDisabled ) -> EventLoopFuture { @@ -276,7 +314,7 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger) + return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) }.map { _ in return } @@ -296,6 +334,7 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, + context: Context = AWSClientContext.empty, on eventLoop: EventLoop? = nil, logger: Logger = AWSClient.loggingDisabled ) -> EventLoopFuture { @@ -314,7 +353,7 @@ extension AWSClient { .createHTTPRequest(signer: signer) }.flatMap { request -> EventLoopFuture in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger) + return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) }.map { _ in return } @@ -334,6 +373,7 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, + context: Context = AWSClientContext.empty, on eventLoop: EventLoop? = nil, logger: Logger = AWSClient.loggingDisabled ) -> EventLoopFuture { @@ -351,7 +391,7 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger) + return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } @@ -373,6 +413,7 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, + context: Context = AWSClientContext.empty, on eventLoop: EventLoop? = nil, logger: Logger = AWSClient.loggingDisabled ) -> EventLoopFuture { @@ -391,7 +432,7 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger) + return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } @@ -413,6 +454,7 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, + context: Context = AWSClientContext.empty, on eventLoop: EventLoop? = nil, logger: Logger = AWSClient.loggingDisabled, stream: @escaping AWSHTTPClient.ResponseStream @@ -432,7 +474,14 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, stream: stream) + return self.invoke( + request, + with: serviceConfig, + on: eventLoop, + logger: logger, + context: context, + stream: stream + ) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } diff --git a/Sources/AWSTestUtils/TestTracer.swift b/Sources/AWSTestUtils/TestTracer.swift new file mode 100644 index 0000000000..49b048ea34 --- /dev/null +++ b/Sources/AWSTestUtils/TestTracer.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AWSSDKSwift open source project +// +// Copyright (c) 2017-2020 the AWSSDKSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AWSSDKSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Baggage +import Instrumentation +import TracingInstrumentation + +private extension String { + static func random64() -> String { + String(UInt64.random(in: UInt64.min...UInt64.max) | 1 << 63, radix: 16, uppercase: false) + } +} + +public class TestTracer: TracingInstrument { + public struct Context { + let traceId: String + var parentId: String? + var sampled: Bool = true + + public init(parentId: String? = nil, sampled: Bool = true) { + self.traceId = .random64() + self.parentId = parentId + self.sampled = sampled + } + } + + public class TestSpan: Span { + public let _test_id: String + public let _test_operationName: String + public let _test_kind: SpanKind + public let _test_startTimestamp: Timestamp + public private(set) var _test_endTimestamp: Timestamp? + public private(set) var _test_errors = [Error]() + + public let context: BaggageContext + + public var attributes: SpanAttributes { + get { [:] } + set {} + } + + public var isRecording: Bool { true } + + // TODO: may be removed, see https://github.com/slashmo/gsoc-swift-tracing/issues/134 + public func setStatus(_: SpanStatus) {} + + public func addEvent(_: SpanEvent) {} + + public func recordError(_ error: Error) { + self._test_errors.append(error) + } + + public func addLink(_: SpanLink) {} + + public func end(at timestamp: Timestamp) { + self._test_endTimestamp = timestamp + } + + init(operationName: String, kind: SpanKind, startTimestamp: Timestamp, context: BaggageContext) { + let spanId = String.random64() + self._test_id = spanId + self._test_operationName = operationName + self._test_kind = kind + self._test_startTimestamp = startTimestamp + // update context + // TODO: handle missing context, expected behaviour not defined and is tracer implementation specific + var context = context + context.test?.parentId = spanId + self.context = context + } + } + + public private(set) var _test_recordedSpans = [TestSpan]() + + public init() {} + + public func extract( + _ carrier: Carrier, + into context: inout BaggageContext, + using extractor: Extractor + ) where Carrier == Extractor.Carrier, Extractor: ExtractorProtocol { + // not needed + } + + public func inject( + _ context: BaggageContext, + into carrier: inout Carrier, + using injector: Injector + ) where Carrier == Injector.Carrier, Injector: InjectorProtocol { + // not needed + } + + public func startSpan( + named operationName: String, + context: BaggageContextCarrier, + ofKind kind: SpanKind, + at timestamp: Timestamp + ) -> Span { + let span = TestSpan( + operationName: operationName, + kind: kind, + startTimestamp: timestamp, + context: context.baggage + ) + self._test_recordedSpans.append(span) + return span + } + + public func forceFlush() {} +} + +// MARK: - Baggage + +private enum TestKey: BaggageContextKey { + typealias Value = TestTracer.Context + var name: String { "Test" } +} + +extension BaggageContext { + public var test: TestTracer.Context? { + get { + self[TestKey.self] + } + set { + self[TestKey.self] = newValue + } + } +} diff --git a/Tests/AWSSDKSwiftCoreTests/TracingTests.swift b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift new file mode 100644 index 0000000000..fe2c60fec3 --- /dev/null +++ b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AWSSDKSwift open source project +// +// Copyright (c) 2017-2020 the AWSSDKSwift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AWSSDKSwift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import AWSSDKSwiftCore +import AWSTestUtils +import Baggage +import Instrumentation +import NIO +import NIOHTTP1 +import XCTest + +class TracingTests: XCTestCase { + private struct TestContext: AWSClient.Context { + public var baggage: BaggageContext + + init(traceContext: TestTracer.Context) { + var baggage = BaggageContext() + baggage.test = traceContext + self.baggage = baggage + } + } + + func testTracingDownstreamCall() { + // bootstrap tracer + let tracer = TestTracer() + InstrumentationSystem.bootstrap(tracer) + + // create new trace + // TODO: not possible using TracingInstrument API, see https://github.com/slashmo/gsoc-swift-tracing/issues/137 + let context = TestContext(traceContext: .init()) + + let awsServer = AWSTestServer(serviceProtocol: .json) + let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try awsServer.stop()) + XCTAssertNoThrow(try elg.syncShutdownGracefully()) + } + let config = createServiceConfig( + serviceProtocol: .json(version: "1.1"), + endpoint: awsServer.address + ) + let client = createAWSClient( + credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar"), + httpClientProvider: .createNew + ) + defer { + XCTAssertNoThrow(try client.syncShutdown()) + } + + // execute the query + let response: EventLoopFuture = client.execute( + operation: "test", + path: "/", + httpMethod: .POST, + serviceConfig: config, + context: context, + logger: TestEnvironment.logger + ) + XCTAssertNoThrow(try awsServer.httpBin()) + XCTAssertNoThrow(try response.wait()) + + // flush the tracer and check what have been recorded + tracer.forceFlush() + XCTAssertGreaterThan(tracer._test_recordedSpans.count, 0) + let span = tracer._test_recordedSpans.first + XCTAssertNotNil(span) + XCTAssertNotNil(span?._test_endTimestamp) + XCTAssertEqual(span?._test_errors.count, 0) + } +} From 114a5ea05ccce20ee0143744d1ce8a271d1ed58d Mon Sep 17 00:00:00 2001 From: Michal A Date: Thu, 27 Aug 2020 21:03:28 +0800 Subject: [PATCH 02/12] Use instrumented HTTP client, pass logger in the context WIP --- Package.swift | 5 +- .../AWSSDKSwiftCore/AWSClient+Paginate.swift | 6 +- Sources/AWSSDKSwiftCore/AWSClient.swift | 144 ++++++++++-------- Sources/AWSSDKSwiftCore/AWSService.swift | 4 +- .../ConfigFileCredentialProvider.swift | 2 +- .../Credential/CredentialProvider.swift | 13 +- .../DeferredCredentialProvider.swift | 4 +- .../MetaDataCredentialProvider.swift | 41 ++--- .../Credential/NullCredentialProvider.swift | 2 +- .../RotatingCredentialProvider.swift | 16 +- .../RuntimeSelectorCredentialProvider.swift | 8 +- .../StaticCredential+CredentialProvider.swift | 2 +- .../AWSSDKSwiftCore/HTTP/AWSHTTPClient.swift | 6 +- .../HTTP/AsyncHTTPClient.swift | 12 +- Sources/AWSTestUtils/TestUtils.swift | 21 +++ .../AWSSDKSwiftCoreTests/AWSClientTests.swift | 40 ++--- .../ConfigFileCredentialProviderTests.swift | 8 +- .../Credential/CredentialProviderTests.swift | 23 +-- .../MetaDataCredentialProviderTests.swift | 6 +- .../RotatingCredentialProviderTests.swift | 18 +-- ...ntimeSelectorCredentialProviderTests.swift | 20 +-- Tests/AWSSDKSwiftCoreTests/LoggingTests.swift | 17 ++- .../AWSSDKSwiftCoreTests/PaginateTests.swift | 16 +- Tests/AWSSDKSwiftCoreTests/PayloadTests.swift | 4 +- .../PerformanceTests.swift | 2 +- Tests/AWSSDKSwiftCoreTests/TracingTests.swift | 18 +-- 26 files changed, 252 insertions(+), 206 deletions(-) diff --git a/Package.swift b/Package.swift index 0f4d14e9cd..0968e8c0f4 100644 --- a/Package.swift +++ b/Package.swift @@ -26,7 +26,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.16.1")), .package(url: "https://github.com/apple/swift-nio-ssl.git", .upToNextMajor(from: "2.7.2")), .package(url: "https://github.com/apple/swift-nio-transport-services.git", .upToNextMajor(from: "1.0.0")), - .package(url: "https://github.com/swift-server/async-http-client.git", .upToNextMajor(from: "1.2.0")), + // .package(url: "https://github.com/swift-server/async-http-client.git", .upToNextMajor(from: "1.2.0")), + .package(url: "https://github.com/pokryfka/async-http-client.git", .branch("feature/instrumentation")), .package(url: "https://github.com/slashmo/gsoc-swift-baggage-context.git", .upToNextMinor(from: "0.3.0")), // TODO: use version when released .package(url: "https://github.com/slashmo/gsoc-swift-tracing.git", .revision("fe80d764ad225b1dfd06dcb57d08b5e3485662f9")), @@ -45,6 +46,7 @@ let package = Package( .product(name: "NIOTransportServices", package: "swift-nio-transport-services"), .product(name: "NIOFoundationCompat", package: "swift-nio"), .product(name: "Baggage", package: "swift-baggage-context"), + .product(name: "BaggageLogging", package: "swift-baggage-context"), .product(name: "TracingInstrumentation", package: "gsoc-swift-tracing"), ]), .target(name: "AWSCrypto", dependencies: []), @@ -59,6 +61,7 @@ let package = Package( .product(name: "NIOFoundationCompat", package: "swift-nio"), .product(name: "NIOTestUtils", package: "swift-nio"), .product(name: "Baggage", package: "swift-baggage-context"), + .product(name: "BaggageLogging", package: "swift-baggage-context"), .product(name: "TracingInstrumentation", package: "gsoc-swift-tracing"), ]), .target(name: "AWSXML", dependencies: [ diff --git a/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift b/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift index 220f8f61a1..f4ea5b79a5 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift @@ -34,17 +34,17 @@ extension AWSClient { /// - onPage: closure called with each block of entries public func paginate( input: Input, - command: @escaping (Input, EventLoop?, Logger) -> EventLoopFuture, + command: @escaping (Input, EventLoop?, AWSClient.Context) -> EventLoopFuture, tokenKey: KeyPath, + context: AWSClient.Context, on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled, onPage: @escaping (Output, EventLoop) -> EventLoopFuture ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() let promise = eventLoop.makePromise(of: Void.self) func paginatePart(input: Input) { - let responseFuture = command(input, eventLoop, logger) + let responseFuture = command(input, eventLoop, context) .flatMap { response in return onPage(response, eventLoop) .map { (rt) -> Void in diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 3b00637e3c..4f2fa50726 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -16,6 +16,7 @@ import AsyncHTTPClient import AWSSignerV4 import AWSXML import Baggage +import BaggageLogging import Dispatch import struct Foundation.Data import class Foundation.JSONDecoder @@ -34,22 +35,12 @@ import TracingInstrumentation // TODO: instrument initialization // TODO: create internal spans for signing, parsing? -// TODO: remove and use optional parameter or dont provide default value -public struct AWSClientContext: AWSClient.Context { - public static var empty: AWSClientContext = .init(baggage: .init()) - - public var baggage: BaggageContext -} - /// This is the workhorse of aws-sdk-swift-core. You provide it with a `AWSShape` Input object, it converts it to `AWSRequest` which is then converted /// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a `AWSResponse` /// which is then decoded to generate a `AWSShape` Output object. If it is not successful then `AWSClient` will throw an `AWSErrorType`. public final class AWSClient { /// AWS operation context including trace context - public typealias Context = Baggage.BaggageContextCarrier - // TODO: import BaggageLogging and add confirmance to LoggingBaggageContextCarrier - // when naming and conventions are confirmed https://github.com/slashmo/gsoc-swift-baggage-context/issues/23 - // remove logger parameter (it will be provided in the context) + public typealias Context = BaggageLogging.LoggingBaggageContextCarrier /// Errors returned by AWSClient code public struct ClientError: Swift.Error, Equatable { @@ -116,7 +107,8 @@ public final class AWSClient { retryPolicy retryPolicyFactory: RetryPolicyFactory = .default, middlewares: [AWSServiceMiddleware] = [], httpClientProvider: HTTPClientProvider, - logger clientLogger: Logger = AWSClient.loggingDisabled + logger clientLogger: Logger = AWSClient.loggingDisabled, + baggage: BaggageContext = .init() // TODO: empty baggage? ) { // setup httpClient self.httpClientProvider = httpClientProvider @@ -130,7 +122,8 @@ public final class AWSClient { self.credentialProvider = credentialProviderFactory.createProvider(context: .init( httpClient: httpClient, eventLoop: httpClient.eventLoopGroup.next(), - logger: clientLogger + logger: clientLogger, + baggage: baggage )) self.middlewares = middlewares @@ -206,7 +199,6 @@ public final class AWSClient { extension AWSClient { fileprivate func invoke( with serviceConfig: AWSServiceConfig, - logger: Logger, context: Context, _ request: @escaping () -> EventLoopFuture ) -> EventLoopFuture { @@ -224,7 +216,7 @@ extension AWSClient { .flatMapErrorThrowing { (error) -> Void in // If I get a retry wait time for this error then attempt to retry request if case .retry(let retryTime) = self.retryPolicy.getRetryWaitTime(error: error, attempt: attempt) { - logger.info("Retrying request", metadata: [ + context.logger.info("Retrying request", metadata: [ "aws-retry-time": "\(Double(retryTime.nanoseconds) / 1_000_000_000)", ]) // schedule task for retrying AWS request @@ -233,7 +225,7 @@ extension AWSClient { } } else if let responseError = error as? HTTPResponseError { // if there was no retry and error was a response status code then attempt to convert to AWS error - promise.fail(self.createError(for: responseError.response, serviceConfig: serviceConfig, logger: logger)) + promise.fail(self.createError(for: responseError.response, serviceConfig: serviceConfig, logger: context.logger)) } else { promise.fail(error) } @@ -246,14 +238,20 @@ extension AWSClient { } /// invoke HTTP request - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger, context: Context) -> EventLoopFuture { + fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context) -> EventLoopFuture { // TODO: what should be the operation name? let operationName: String = httpRequest.url.path var span = InstrumentationSystem.tracingInstrument.startSpan(named: operationName, context: context, ofKind: .client, at: .now()) - return invoke(with: serviceConfig, logger: logger, context: context) { - // TODO: record retries and set HTTP attributes using instrumented HTTP client -// let httpClientContext = span.context - return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, logger: logger) + return invoke(with: serviceConfig, context: context) { + // TODO: change Span interface to return carrier? + var carrier = context + carrier.baggage = span.context + return self.httpClient.execute( + request: httpRequest, + timeout: serviceConfig.timeout, + on: eventLoop, + context: carrier + ) } // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 .always { result in @@ -265,10 +263,22 @@ extension AWSClient { } /// invoke HTTP request with response streaming - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, logger: Logger, context: Context, stream: @escaping AWSHTTPClient.ResponseStream) -> EventLoopFuture { - // TODO: instrument - return invoke(with: serviceConfig, logger: logger, context: context) { - return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, logger: logger, stream: stream) + fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context, stream: @escaping AWSHTTPClient.ResponseStream) -> EventLoopFuture { + // TODO: what should be the operation name? + let operationName: String = httpRequest.url.path + var span = InstrumentationSystem.tracingInstrument.startSpan(named: operationName, context: context, ofKind: .client, at: .now()) + return invoke(with: serviceConfig, context: context) { + // TODO: change Span interface to return carrier? + var carrier = context + carrier.baggage = span.context + return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, context: carrier, stream: stream) + } + // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 + .always { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() } } @@ -295,13 +305,14 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context = AWSClientContext.empty, - on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled + context: Context, + on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, logger: logger).flatMapThrowing { credential in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) + let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) let awsRequest = try AWSRequest( operation: operationName, @@ -314,11 +325,11 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) + return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) }.map { _ in return } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: logger) + return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) } /// execute an empty request and return a future with an empty response @@ -334,13 +345,14 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, - context: Context = AWSClientContext.empty, - on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled + context: Context, + on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, logger: logger).flatMapThrowing { credential -> AWSHTTPRequest in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) + let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential -> AWSHTTPRequest in let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) let awsRequest = try AWSRequest( operation: operationName, @@ -353,11 +365,11 @@ extension AWSClient { .createHTTPRequest(signer: signer) }.flatMap { request -> EventLoopFuture in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) + return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) }.map { _ in return } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: logger) + return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) } /// execute an empty request and return a future with the output object generated from the response @@ -373,13 +385,14 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, - context: Context = AWSClientContext.empty, - on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled + context: Context, + on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, logger: logger).flatMapThrowing { credential in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) + let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) let awsRequest = try AWSRequest( operation: operationName, @@ -391,11 +404,11 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) + return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: logger) + return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) } /// execute a request with an input object and return a future with the output object generated from the response @@ -413,13 +426,14 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context = AWSClientContext.empty, - on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled + context: Context, + on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, logger: logger).flatMapThrowing { credential in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) + let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) let awsRequest = try AWSRequest( operation: operationName, @@ -432,11 +446,11 @@ extension AWSClient { .applyMiddlewares(serviceConfig.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, logger: logger, context: context) + return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: logger) + return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) } /// execute a request with an input object and return a future with the output object generated from the response @@ -454,14 +468,15 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context = AWSClientContext.empty, + context: Context, on eventLoop: EventLoop? = nil, - logger: Logger = AWSClient.loggingDisabled, stream: @escaping AWSHTTPClient.ResponseStream ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, logger: logger).flatMapThrowing { credential in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) + let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) let awsRequest = try AWSRequest( operation: operationName, @@ -478,14 +493,13 @@ extension AWSClient { request, with: serviceConfig, on: eventLoop, - logger: logger, context: context, stream: stream ) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: logger) + return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) } /// generate a signed URL @@ -501,16 +515,18 @@ extension AWSClient { httpMethod: String, expires: Int = 86400, serviceConfig: AWSServiceConfig, - logger: Logger = AWSClient.loggingDisabled + context: CredentialProvider.Context ) -> EventLoopFuture { - let logger = logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) - return createSigner(serviceConfig: serviceConfig, logger: logger).map { signer in + // TODO: update logger via baggage + var context = context + context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) + return createSigner(serviceConfig: serviceConfig, context: context).map { signer in signer.signURL(url: url, method: HTTPMethod(rawValue: httpMethod), expires: expires) } } - func createSigner(serviceConfig: AWSServiceConfig, logger: Logger) -> EventLoopFuture { - return credentialProvider.getCredential(on: eventLoopGroup.next(), logger: logger).map { credential in + func createSigner(serviceConfig: AWSServiceConfig, context: CredentialProvider.Context) -> EventLoopFuture { + return credentialProvider.getCredential(on: eventLoopGroup.next(), context: context).map { credential in return AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) } } diff --git a/Sources/AWSSDKSwiftCore/AWSService.swift b/Sources/AWSSDKSwiftCore/AWSService.swift index e31b0db3cc..60e827fc5f 100644 --- a/Sources/AWSSDKSwiftCore/AWSService.swift +++ b/Sources/AWSSDKSwiftCore/AWSService.swift @@ -36,7 +36,7 @@ extension AWSService { /// - expires: How long before the signed URL expires /// - returns: /// A signed URL - public func signURL(url: URL, httpMethod: String, expires: Int = 86400, logger: Logger = AWSClient.loggingDisabled) -> EventLoopFuture { - return self.client.signURL(url: url, httpMethod: httpMethod, expires: expires, serviceConfig: self.config, logger: logger) + public func signURL(url: URL, httpMethod: String, expires: Int = 86400, context: AWSClient.Context) -> EventLoopFuture { + return self.client.signURL(url: url, httpMethod: httpMethod, expires: expires, serviceConfig: self.config, context: context) } } diff --git a/Sources/AWSSDKSwiftCore/Credential/ConfigFileCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/ConfigFileCredentialProvider.swift index 5cad95e996..17586e0144 100644 --- a/Sources/AWSSDKSwiftCore/Credential/ConfigFileCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/ConfigFileCredentialProvider.swift @@ -43,7 +43,7 @@ struct AWSConfigFileCredentialProvider: CredentialProvider { self.profile = profile ?? Environment["AWS_PROFILE"] ?? "default" } - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { return AWSConfigFileCredentialProvider.fromSharedCredentials(credentialsFilePath: self.credentialsFilePath, profile: self.profile, on: eventLoop) .map { $0 } } diff --git a/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift index c764c7e11b..e3afd70451 100644 --- a/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift @@ -13,13 +13,16 @@ //===----------------------------------------------------------------------===// import AWSSignerV4 -import Logging +import Baggage +import BaggageLogging import NIO import NIOConcurrencyHelpers /// Protocol providing future holding a credential public protocol CredentialProvider: CustomStringConvertible { - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture + typealias Context = BaggageLogging.LoggingBaggageContextCarrier + + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture func shutdown(on eventLoop: EventLoop) -> EventLoopFuture } @@ -34,13 +37,15 @@ extension CredentialProvider { /// A helper struct to defer the creation of a `CredentialProvider` until after the AWSClient has been created. public struct CredentialProviderFactory { /// The initialization context for a `ContextProvider` - public struct Context { + public struct Context: BaggageLogging.LoggingBaggageContextCarrier { /// The `AWSClient`s internal `HTTPClient` public let httpClient: AWSHTTPClient /// The `EventLoop` that the `CredentialProvider` should use for credential refreshs public let eventLoop: EventLoop /// The `Logger` attached to the AWSClient - public let logger: Logger + public var logger: Logger // TODO: should not need to be mutable + /// The context baggage. + public var baggage: BaggageContext } private let cb: (Context) -> CredentialProvider diff --git a/Sources/AWSSDKSwiftCore/Credential/DeferredCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/DeferredCredentialProvider.swift index 313582def4..8392ead93b 100644 --- a/Sources/AWSSDKSwiftCore/Credential/DeferredCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/DeferredCredentialProvider.swift @@ -38,7 +38,7 @@ public class DeferredCredentialProvider: CredentialProvider { public init(context: CredentialProviderFactory.Context, provider: CredentialProvider) { self.startupPromise = context.eventLoop.makePromise(of: Credential.self) self.provider = provider - provider.getCredential(on: context.eventLoop, logger: context.logger) + provider.getCredential(on: context.eventLoop, context: context) .flatMapErrorThrowing { _ in throw CredentialProviderError.noProvider } .map { credential in self.internalCredential = credential @@ -59,7 +59,7 @@ public class DeferredCredentialProvider: CredentialProvider { /// otherwise return credentials store in class /// - Parameter eventLoop: EventLoop to run off /// - Returns: EventLoopFuture that will hold credentials - public func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + public func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { if let credential = self.credential { return eventLoop.makeSucceededFuture(credential) } diff --git a/Sources/AWSSDKSwiftCore/Credential/MetaDataCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/MetaDataCredentialProvider.swift index 6936b0c11d..0db6fc044c 100644 --- a/Sources/AWSSDKSwiftCore/Credential/MetaDataCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/MetaDataCredentialProvider.swift @@ -21,7 +21,7 @@ import struct Foundation.TimeZone import struct Foundation.URL import AWSSignerV4 -import Logging +// import Logging import NIO import NIOConcurrencyHelpers import NIOHTTP1 @@ -30,12 +30,12 @@ import NIOHTTP1 protocol MetaDataClient: CredentialProvider { associatedtype MetaData: ExpiringCredential & Decodable - func getMetaData(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture + func getMetaData(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture } extension MetaDataClient { - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { - self.getMetaData(on: eventLoop, logger: logger).map { metaData in + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { + self.getMetaData(on: eventLoop, context: context).map { metaData in metaData } } @@ -107,8 +107,8 @@ struct ECSMetaDataClient: MetaDataClient { self.endpointURL = "\(host)\(relativeURL)" } - func getMetaData(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { - return request(url: endpointURL, timeout: 2, on: eventLoop, logger: logger) + func getMetaData(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { + return request(url: endpointURL, timeout: 2, on: eventLoop, context: context) .flatMapThrowing { response in guard let body = response.body else { throw MetaDataClientError.missingMetaData @@ -117,9 +117,14 @@ struct ECSMetaDataClient: MetaDataClient { } } - private func request(url: String, timeout: TimeInterval, on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + private func request( + url: String, + timeout: TimeInterval, + on eventLoop: EventLoop, + context: CredentialProvider.Context + ) -> EventLoopFuture { let request = AWSHTTPRequest(url: URL(string: url)!, method: .GET, headers: [:], body: .empty) - return httpClient.execute(request: request, timeout: TimeAmount.seconds(2), on: eventLoop, logger: logger) + return httpClient.execute(request: request, timeout: TimeAmount.seconds(2), on: eventLoop, context: context) } } @@ -180,17 +185,17 @@ struct InstanceMetaDataClient: MetaDataClient { self.host = host } - func getMetaData(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { - return getToken(on: eventLoop, logger: logger) + func getMetaData(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { + return getToken(on: eventLoop, context: context) .map { token in - logger.info("Found IMDSv2 token") + context.logger.info("Found IMDSv2 token") return HTTPHeaders([(Self.TokenHeaderName, token)]) } .flatMapErrorThrowing { _ in // If we didn't find a session key then assume we are running IMDSv1. // (we could be running from a Docker container and the hop count for the PUT // request is still set to 1) - logger.info("Did not find IMDSv2 token, use IMDSv1") + context.logger.info("Did not find IMDSv2 token, use IMDSv1") return HTTPHeaders() } .flatMap { (headers) -> EventLoopFuture<(AWSHTTPResponse, HTTPHeaders)> in @@ -200,7 +205,7 @@ struct InstanceMetaDataClient: MetaDataClient { method: .GET, headers: headers, on: eventLoop, - logger: logger + context: context ).map { ($0, headers) } } .flatMapThrowing { (response, headers) -> (String, HTTPHeaders) in @@ -218,7 +223,7 @@ struct InstanceMetaDataClient: MetaDataClient { .flatMap { (roleName, headers) -> EventLoopFuture in // request credentials with the rolename let url = self.credentialURL.appendingPathComponent(roleName) - return self.request(url: url, headers: headers, on: eventLoop, logger: logger) + return self.request(url: url, headers: headers, on: eventLoop, context: context) } .flatMapThrowing { response in // decode the repsonse payload into the metadata object @@ -230,13 +235,13 @@ struct InstanceMetaDataClient: MetaDataClient { } } - func getToken(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getToken(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { return request( url: self.tokenURL, method: .PUT, headers: HTTPHeaders([Self.TokenTimeToLiveHeader]), timeout: .seconds(2), on: eventLoop, - logger: logger + context: context ).flatMapThrowing { response in guard response.status == .ok else { throw MetaDataClientError.unexpectedTokenResponseStatus(status: response.status) @@ -255,9 +260,9 @@ struct InstanceMetaDataClient: MetaDataClient { headers: HTTPHeaders = .init(), timeout: TimeAmount = .seconds(2), on eventLoop: EventLoop, - logger: Logger + context: CredentialProvider.Context ) -> EventLoopFuture { let request = AWSHTTPRequest(url: url, method: method, headers: headers, body: .empty) - return httpClient.execute(request: request, timeout: timeout, on: eventLoop, logger: logger) + return httpClient.execute(request: request, timeout: timeout, on: eventLoop, context: context) } } diff --git a/Sources/AWSSDKSwiftCore/Credential/NullCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/NullCredentialProvider.swift index 603c490cbf..62d70d7108 100644 --- a/Sources/AWSSDKSwiftCore/Credential/NullCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/NullCredentialProvider.swift @@ -18,7 +18,7 @@ import NIO /// Credential provider that always fails public struct NullCredentialProvider: CredentialProvider { - public func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + public func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { return eventLoop.makeFailedFuture(CredentialProviderError.noProvider) } } diff --git a/Sources/AWSSDKSwiftCore/Credential/RotatingCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/RotatingCredentialProvider.swift index c2851f179a..c5ba19eb90 100644 --- a/Sources/AWSSDKSwiftCore/Credential/RotatingCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/RotatingCredentialProvider.swift @@ -33,7 +33,7 @@ public final class RotatingCredentialProvider: CredentialProvider { public init(context: CredentialProviderFactory.Context, provider: CredentialProvider, remainingTokenLifetimeForUse: TimeInterval? = nil) { self.provider = provider self.remainingTokenLifetimeForUse = remainingTokenLifetimeForUse ?? 3 * 60 - _ = refreshCredentials(on: context.eventLoop, logger: context.logger) + _ = refreshCredentials(on: context.eventLoop, context: context) } public func shutdown(on eventLoop: EventLoop) -> EventLoopFuture { @@ -45,18 +45,18 @@ public final class RotatingCredentialProvider: CredentialProvider { } } - public func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + public func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { self.lock.lock() let cred = credential self.lock.unlock() switch cred { case .none: - return self.refreshCredentials(on: eventLoop, logger: logger) + return self.refreshCredentials(on: eventLoop, context: context) case .some(let cred as ExpiringCredential): if cred.isExpiring(within: remainingTokenLifetimeForUse) { // the credentials are expiring... let's refresh - return self.refreshCredentials(on: eventLoop, logger: logger) + return self.refreshCredentials(on: eventLoop, context: context) } return eventLoop.makeSucceededFuture(cred) @@ -66,7 +66,7 @@ public final class RotatingCredentialProvider: CredentialProvider { } } - private func refreshCredentials(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + private func refreshCredentials(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { self.lock.lock() defer { self.lock.unlock() } @@ -80,15 +80,15 @@ public final class RotatingCredentialProvider: CredentialProvider { return future } - logger.info("Refeshing AWS credentials", metadata: ["aws-credential-provider": .string("\(self)")]) + context.logger.info("Refeshing AWS credentials", metadata: ["aws-credential-provider": .string("\(self)")]) - credentialFuture = self.provider.getCredential(on: eventLoop, logger: logger) + credentialFuture = self.provider.getCredential(on: eventLoop, context: context) .map { (credential) -> (Credential) in // update the internal credential locked self.lock.withLock { self.credentialFuture = nil self.credential = credential - logger.info("AWS credentials ready", metadata: ["aws-credential-provider": .string("\(self)")]) + context.logger.info("AWS credentials ready", metadata: ["aws-credential-provider": .string("\(self)")]) } return credential } diff --git a/Sources/AWSSDKSwiftCore/Credential/RuntimeSelectorCredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/RuntimeSelectorCredentialProvider.swift index 16396907e3..ec1afcdb6b 100644 --- a/Sources/AWSSDKSwiftCore/Credential/RuntimeSelectorCredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/RuntimeSelectorCredentialProvider.swift @@ -43,13 +43,13 @@ class RuntimeSelectorCredentialProvider: CredentialProvider { return self.startupPromise.futureResult.map { _ in }.hop(to: eventLoop) } - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { if let provider = internalProvider { - return provider.getCredential(on: eventLoop, logger: logger) + return provider.getCredential(on: eventLoop, context: context) } return self.startupPromise.futureResult.hop(to: eventLoop).flatMap { provider in - return provider.getCredential(on: eventLoop, logger: logger) + return provider.getCredential(on: eventLoop, context: context) } } @@ -63,7 +63,7 @@ class RuntimeSelectorCredentialProvider: CredentialProvider { } let providerFactory = providers[index] let provider = providerFactory.createProvider(context: context) - provider.getCredential(on: context.eventLoop, logger: context.logger).whenComplete { result in + provider.getCredential(on: context.eventLoop, context: context).whenComplete { result in switch result { case .success: context.logger.info("Select credential provider", metadata: ["aws-credential-provider": .string("\(provider)")]) diff --git a/Sources/AWSSDKSwiftCore/Credential/StaticCredential+CredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/StaticCredential+CredentialProvider.swift index b83869934d..022e4cb25c 100644 --- a/Sources/AWSSDKSwiftCore/Credential/StaticCredential+CredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/StaticCredential+CredentialProvider.swift @@ -16,7 +16,7 @@ import AWSSignerV4 import Logging extension StaticCredential: CredentialProvider { - public func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + public func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { eventLoop.makeSucceededFuture(self) } } diff --git a/Sources/AWSSDKSwiftCore/HTTP/AWSHTTPClient.swift b/Sources/AWSSDKSwiftCore/HTTP/AWSHTTPClient.swift index 69b0471101..5bdf76474d 100644 --- a/Sources/AWSSDKSwiftCore/HTTP/AWSHTTPClient.swift +++ b/Sources/AWSSDKSwiftCore/HTTP/AWSHTTPClient.swift @@ -44,10 +44,10 @@ public protocol AWSHTTPClient { typealias ResponseStream = (ByteBuffer, EventLoop) -> EventLoopFuture /// Execute HTTP request and return a future holding a HTTP Response - func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture + func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, context: AWSClient.Context) -> EventLoopFuture /// Execute an HTTP request with a streamed response - func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, logger: Logger, stream: @escaping ResponseStream) -> EventLoopFuture + func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, context: AWSClient.Context, stream: @escaping ResponseStream) -> EventLoopFuture /// This should be called before an HTTP Client can be de-initialised func shutdown(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) @@ -58,7 +58,7 @@ public protocol AWSHTTPClient { extension AWSHTTPClient { /// Execute an HTTP request with a streamed response - public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, logger: Logger, stream: @escaping ResponseStream) -> EventLoopFuture { + public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, context: AWSClient.Context, stream: @escaping ResponseStream) -> EventLoopFuture { preconditionFailure("\(type(of: self)) does not support response streaming") } } diff --git a/Sources/AWSSDKSwiftCore/HTTP/AsyncHTTPClient.swift b/Sources/AWSSDKSwiftCore/HTTP/AsyncHTTPClient.swift index d7ab260f6e..fb0d5d7b6a 100644 --- a/Sources/AWSSDKSwiftCore/HTTP/AsyncHTTPClient.swift +++ b/Sources/AWSSDKSwiftCore/HTTP/AsyncHTTPClient.swift @@ -25,7 +25,7 @@ extension AsyncHTTPClient.HTTPClient: AWSHTTPClient { /// - timeout: If execution is idle for longer than timeout then throw error /// - eventLoop: eventLoop to run request on /// - Returns: EventLoopFuture that will be fulfilled with request response - public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, context: AWSClient.Context) -> EventLoopFuture { let requestBody: AsyncHTTPClient.HTTPClient.Body? var requestHeaders = request.headers @@ -50,15 +50,15 @@ extension AsyncHTTPClient.HTTPClient: AWSHTTPClient { return self.execute( request: asyncRequest, eventLoop: .delegate(on: eventLoop), - deadline: .now() + timeout, - logger: logger + context: context, + deadline: .now() + timeout ).map { $0 } } catch { return eventLoopGroup.next().makeFailedFuture(error) } } - public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, logger: Logger, stream: @escaping ResponseStream) -> EventLoopFuture { + public func execute(request: AWSHTTPRequest, timeout: TimeAmount, on eventLoop: EventLoop, context: AWSClient.Context, stream: @escaping ResponseStream) -> EventLoopFuture { let requestBody: AsyncHTTPClient.HTTPClient.Body? if case .byteBuffer(let body) = request.body.payload { requestBody = .byteBuffer(body) @@ -77,8 +77,8 @@ extension AsyncHTTPClient.HTTPClient: AWSHTTPClient { request: asyncRequest, delegate: delegate, eventLoop: .delegate(on: eventLoop), - deadline: .now() + timeout, - logger: logger + context: context, + deadline: .now() + timeout ).futureResult // temporarily wait on delegate response finishing while AHC does not do this for us. See https://github.com/swift-server/async-http-client/issues/274 .flatMap { response in diff --git a/Sources/AWSTestUtils/TestUtils.swift b/Sources/AWSTestUtils/TestUtils.swift index 68bc1f357e..67912593c1 100644 --- a/Sources/AWSTestUtils/TestUtils.swift +++ b/Sources/AWSTestUtils/TestUtils.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @testable import AWSSDKSwiftCore +import Baggage import Foundation import Logging @@ -98,11 +99,17 @@ public func createRandomBuffer(_ w: UInt, _ z: UInt, size: Int) -> [UInt8] { /// Provide various test environment variables public struct TestEnvironment { + private struct TestContext: AWSClient.Context { + var baggage: BaggageContext + var logger = Logger(label: "test") // TODO: store logged messages for further inspection + } + /// current list of middleware public static var middlewares: [AWSServiceMiddleware] { return (Environment["AWS_ENABLE_LOGGING"] == "true") ? [AWSLoggingMiddleware()] : [] } + // TODO: make private and get via context? public static var logger: Logger = { if let loggingLevel = Environment["AWS_LOG_LEVEL"] { if let logLevel = Logger.Level(rawValue: loggingLevel.lowercased()) { @@ -113,4 +120,18 @@ public struct TestEnvironment { } return AWSClient.loggingDisabled }() + + public static var context: AWSClient.Context = { + let traceContext = TestTracer.Context() + var baggage = BaggageContext() + baggage.test = traceContext + return TestContext(baggage: baggage, logger: Self.logger) + }() + + public static func contextWith(logger: Logging.Logger) -> AWSClient.Context { + let traceContext = TestTracer.Context() + var baggage = BaggageContext() + baggage.test = traceContext + return TestContext(baggage: baggage, logger: logger) + } } diff --git a/Tests/AWSSDKSwiftCoreTests/AWSClientTests.swift b/Tests/AWSSDKSwiftCoreTests/AWSClientTests.swift index f75abae64d..ecc144b3d0 100644 --- a/Tests/AWSSDKSwiftCoreTests/AWSClientTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/AWSClientTests.swift @@ -31,7 +31,7 @@ class AWSClientTests: XCTestCase { } do { - let credentialForSignature = try client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).wait() + let credentialForSignature = try client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).wait() XCTAssertEqual(credentialForSignature.accessKeyId, "key") XCTAssertEqual(credentialForSignature.secretAccessKey, "secret") } catch { @@ -47,7 +47,7 @@ class AWSClientTests: XCTestCase { } do { - let credentials = try client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).wait() + let credentials = try client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).wait() print(credentials) } catch let error as CredentialProviderError where error == .noProvider { // credentials request should fail. One possible error is a connectTimerout @@ -95,7 +95,7 @@ class AWSClientTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } - let response: EventLoopFuture = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response: EventLoopFuture = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) XCTAssertNoThrow(try awsServer.httpBin()) var httpBinResponse: AWSTestServer.HTTPBinResponse? @@ -119,7 +119,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try awsServer.stop()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) try awsServer.processRaw { _ in let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) @@ -151,7 +151,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let input = Input(e: .second, i: [1, 2, 4, 8]) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, context: TestEnvironment.context) try awsServer.processRaw { request in let receivedInput = try JSONDecoder().decode(Input.self, from: request.body) @@ -180,7 +180,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try awsServer.stop()) } - let response: EventLoopFuture = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response: EventLoopFuture = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) try awsServer.processRaw { _ in let output = Output(s: "TestOutputString", i: 547) @@ -217,7 +217,7 @@ class AWSClientTests: XCTestCase { return eventLoop.makeSucceededFuture(.byteBuffer(buffer)) } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, context: TestEnvironment.context) try server.processRaw { request in let bytes = request.body.getBytes(at: 0, length: request.body.readableBytes) @@ -290,7 +290,7 @@ class AWSClientTests: XCTestCase { return eventLoop.makeSucceededFuture(.byteBuffer(buffer)) } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, context: TestEnvironment.context) try response.wait() } catch let error as HTTPClientError where error == .bodyLengthMismatch { } catch { @@ -335,7 +335,7 @@ class AWSClientTests: XCTestCase { } let input = Input(payload: .fileHandle(fileHandle, size: bufferSize, fileIO: fileIO) { size in print(size) }) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, context: TestEnvironment.context) try awsServer.processRaw { request in XCTAssertNil(request.headers["transfer-encoding"]) @@ -387,7 +387,7 @@ class AWSClientTests: XCTestCase { } } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, context: TestEnvironment.context) try awsServer.processRaw { request in let bytes = request.body.getBytes(at: 0, length: request.body.readableBytes) @@ -416,7 +416,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) try awsServer.processRaw { _ in let response = AWSTestServer.Response(httpStatus: .temporaryRedirect, headers: ["Location": awsServer.address], body: nil) @@ -452,7 +452,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) var count = 0 try awsServer.processRaw { _ in @@ -496,7 +496,7 @@ class AWSClientTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - logger: TestEnvironment.logger + context: TestEnvironment.context ) var count = 0 @@ -552,7 +552,7 @@ class AWSClientTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - logger: TestEnvironment.logger + context: TestEnvironment.context ) try response.wait() @@ -582,7 +582,7 @@ class AWSClientTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - logger: TestEnvironment.logger + context: TestEnvironment.context ) try awsServer.processRaw { _ in @@ -618,8 +618,8 @@ class AWSClientTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - on: eventLoop, - logger: TestEnvironment.logger + context: TestEnvironment.context, + on: eventLoop ) try awsServer.processRaw { _ in @@ -660,7 +660,7 @@ class AWSClientTests: XCTestCase { httpMethod: .GET, serviceConfig: config, input: Input(), - logger: TestEnvironment.logger + context: TestEnvironment.context ) { (payload: ByteBuffer, eventLoop: EventLoop) in let payloadSize = payload.readableBytes let slice = Data(data[count..<(count + payloadSize)]) @@ -713,7 +713,7 @@ class AWSClientTests: XCTestCase { httpMethod: .GET, serviceConfig: config, input: Input(), - logger: TestEnvironment.logger + context: TestEnvironment.context ) { (_: ByteBuffer, eventLoop: EventLoop) in lock.withLock { count += 1 } return eventLoop.scheduleTask(in: .milliseconds(200)) { @@ -749,7 +749,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, context: TestEnvironment.context) XCTAssertNoThrow(try awsServer.processRaw { request in XCTAssertEqual(request.uri, "/test") diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift index 801dc033bd..a7e549aa5f 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift @@ -183,10 +183,10 @@ class ConfigFileCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger)) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) var credential: Credential? - XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) XCTAssertEqual(credential?.accessKeyId, "AWSACCESSKEYID") XCTAssertEqual(credential?.secretAccessKey, "AWSSECRETACCESSKEY") } @@ -202,9 +202,9 @@ class ConfigFileCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger)) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) - XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) { error in + XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) { error in print("\(error)") XCTAssertEqual(error as? CredentialProviderError, .noProvider) } diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift index 9fdcdd3266..0b994bdbbd 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift @@ -27,7 +27,7 @@ class CredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let loop = group.next() var returned: Credential? - XCTAssertNoThrow(returned = try cred.getCredential(on: loop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(returned = try cred.getCredential(on: loop, context: TestEnvironment.context).wait()) XCTAssertEqual(returned as? StaticCredential, cred) } @@ -36,7 +36,7 @@ class CredentialProviderTests: XCTestCase { func testDeferredCredentialProvider() { class MyCredentialProvider: CredentialProvider { var alreadyCalled = false - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { if self.alreadyCalled == false { self.alreadyCalled = true return eventLoop.makeSucceededFuture(StaticCredential(accessKeyId: "ACCESSKEYID", secretAccessKey: "SECRETACCESSKET")) @@ -50,10 +50,10 @@ class CredentialProviderTests: XCTestCase { let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let eventLoop = eventLoopGroup.next() - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init()) let deferredProvider = DeferredCredentialProvider(context: context, provider: MyCredentialProvider()) - XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) - XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, context: context).wait()) + XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, context: context).wait()) } func testConfigFileSuccess() { @@ -74,10 +74,15 @@ class CredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger)) + let provider = factory.createProvider(context: .init( + httpClient: httpClient, + eventLoop: eventLoop, + logger: TestEnvironment.logger, + baggage: .init() + )) var credential: Credential? - XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) XCTAssertEqual(credential?.accessKeyId, "AWSACCESSKEYID") XCTAssertEqual(credential?.secretAccessKey, "AWSSECRETACCESSKEY") } @@ -93,9 +98,9 @@ class CredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger)) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) - XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) { error in + XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) { error in print("\(error)") XCTAssertEqual(error as? CredentialProviderError, .noProvider) } diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/MetaDataCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/MetaDataCredentialProviderTests.swift index bc93428f52..f72e010da4 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/MetaDataCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/MetaDataCredentialProviderTests.swift @@ -36,7 +36,7 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { Environment.unset(name: ECSMetaDataClient.RelativeURIEnvironmentName) } let client = ECSMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client!.getMetaData(on: loop, logger: TestEnvironment.logger) + let future = client!.getMetaData(on: loop, context: TestEnvironment.context) XCTAssertNoThrow(try testServer.ecsMetadataServer(path: path)) @@ -85,7 +85,7 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { Environment.unset(name: ECSMetaDataClient.RelativeURIEnvironmentName) } let client = InstanceMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client.getMetaData(on: loop, logger: TestEnvironment.logger) + let future = client.getMetaData(on: loop, context: TestEnvironment.context) XCTAssertNoThrow(try testServer.ec2MetadataServer(version: .v2)) @@ -112,7 +112,7 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try testServer.stop()) } let client = InstanceMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client.getMetaData(on: loop, logger: TestEnvironment.logger) + let future = client.getMetaData(on: loop, context: TestEnvironment.context) XCTAssertNoThrow(try testServer.ec2MetadataServer(version: .v1)) diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift index ecfa0dd08b..6facb87638 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift @@ -28,7 +28,7 @@ class RotatingCredentialProviderTests: XCTestCase { self.callback = callback } - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getCredential(on eventLoop: EventLoop, context: CredentialProvider.Context) -> EventLoopFuture { eventLoop.flatSubmit { self.callback(eventLoop).map { $0 } } @@ -54,12 +54,12 @@ class RotatingCredentialProviderTests: XCTestCase { hitCount += 1 return $0.makeSucceededFuture(cred) } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: Logger(label: "aws-sdk-swift")) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: Logger(label: "aws-sdk-swift"), baggage: .init()) let provider = RotatingCredentialProvider(context: context, provider: client) // get credentials for first time var returned: Credential? - XCTAssertNoThrow(returned = try provider.getCredential(on: loop, logger: Logger(label: "aws-sdk-swift")).wait()) + XCTAssertNoThrow(returned = try provider.getCredential(on: loop, context: context).wait()) XCTAssertEqual(returned?.accessKeyId, cred.accessKeyId) XCTAssertEqual(returned?.secretAccessKey, cred.secretAccessKey) @@ -67,7 +67,7 @@ class RotatingCredentialProviderTests: XCTestCase { XCTAssertEqual((returned as? TestExpiringCredential)?.expiration, cred.expiration) // get credentials a second time, callback must not be hit - XCTAssertNoThrow(returned = try provider.getCredential(on: loop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(returned = try provider.getCredential(on: loop, context: context).wait()) XCTAssertEqual(returned?.accessKeyId, cred.accessKeyId) XCTAssertEqual(returned?.secretAccessKey, cred.secretAccessKey) XCTAssertEqual(returned?.sessionToken, cred.sessionToken) @@ -98,7 +98,7 @@ class RotatingCredentialProviderTests: XCTestCase { hitCount += 1 return promise.futureResult } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger, baggage: .init()) let provider = RotatingCredentialProvider(context: context, provider: client) var resultFutures = [EventLoopFuture]() @@ -115,7 +115,7 @@ class RotatingCredentialProviderTests: XCTestCase { setupPromise.succeed(()) } - return provider.getCredential(on: loop, logger: TestEnvironment.logger).map { returned in + return provider.getCredential(on: loop, context: TestEnvironment.context).map { returned in // this should be executed after the promise is fulfilled. XCTAssertEqual(returned.accessKeyId, cred.accessKeyId) XCTAssertEqual(returned.secretAccessKey, cred.secretAccessKey) @@ -159,14 +159,14 @@ class RotatingCredentialProviderTests: XCTestCase { ) return eventLoop.makeSucceededFuture(cred) } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger, baggage: .init()) let provider = RotatingCredentialProvider(context: context, provider: client) - XCTAssertNoThrow(_ = try provider.getCredential(on: loop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(_ = try provider.getCredential(on: loop, context: TestEnvironment.context).wait()) hitCount = 0 let iterations = 100 for _ in 0..<100 { - XCTAssertNoThrow(_ = try provider.getCredential(on: loop, logger: TestEnvironment.logger).wait()) + XCTAssertNoThrow(_ = try provider.getCredential(on: loop, context: TestEnvironment.context).wait()) } // ensure callback was only hit once diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/RuntimeSelectorCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/RuntimeSelectorCredentialProviderTests.swift index efd7bdc4c5..674bbb14b9 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/RuntimeSelectorCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/RuntimeSelectorCredentialProviderTests.swift @@ -21,7 +21,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { func testSetupFail() { let client = createAWSClient(credentialProvider: .selector(.custom { _ in return NullCredentialProvider() })) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger) + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context) XCTAssertThrowsError(try futureResult.wait()) { error in switch error { case let error as CredentialProviderError where error == CredentialProviderError.noProvider: @@ -48,7 +48,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let client = createAWSClient(credentialProvider: .selector(.environment, .empty)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { credential in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { credential in XCTAssertEqual(credential.accessKeyId, accessKeyId) XCTAssertEqual(credential.secretAccessKey, secretAccessKey) XCTAssertEqual(credential.sessionToken, sessionToken) @@ -68,7 +68,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let provider: CredentialProviderFactory = .selector(.environment) let client = createAWSClient(credentialProvider: provider) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger) + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context) XCTAssertThrowsError(try futureResult.wait()) { error in switch error { case let error as CredentialProviderError where error == CredentialProviderError.noProvider: @@ -83,7 +83,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let provider: CredentialProviderFactory = .selector(.empty, .environment) let client = createAWSClient(credentialProvider: provider) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { credential in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { credential in XCTAssertEqual(credential.accessKeyId, "") XCTAssertEqual(credential.secretAccessKey, "") XCTAssertEqual(credential.sessionToken, nil) @@ -97,7 +97,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let provider: CredentialProviderFactory = .selector(.empty) let client = createAWSClient(credentialProvider: provider) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { credential in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { credential in XCTAssert(credential.isEmpty()) XCTAssert(client.credentialProvider is StaticCredential) } @@ -123,7 +123,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let provider: CredentialProviderFactory = .selector(.environment, customECS, .empty) let client = createAWSClient(credentialProvider: provider) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { credential in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { credential in XCTAssertEqual(credential.accessKeyId, AWSTestServer.ECSMetaData.default.accessKeyId) XCTAssertEqual(credential.secretAccessKey, AWSTestServer.ECSMetaData.default.secretAccessKey) XCTAssertEqual(credential.sessionToken, AWSTestServer.ECSMetaData.default.token) @@ -143,7 +143,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let provider: CredentialProviderFactory = .selector(.ecs) let client = createAWSClient(credentialProvider: provider) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger) + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context) XCTAssertThrowsError(try futureResult.wait()) { error in switch error { case let error as CredentialProviderError where error == CredentialProviderError.noProvider: @@ -166,7 +166,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } let futureResult = client.credentialProvider.getCredential( on: client.eventLoopGroup.next(), - logger: TestEnvironment.logger + context: TestEnvironment.context ).flatMapThrowing { credential in XCTAssertEqual(credential.accessKeyId, AWSTestServer.EC2InstanceMetaData.default.accessKeyId) XCTAssertEqual(credential.secretAccessKey, AWSTestServer.EC2InstanceMetaData.default.secretAccessKey) @@ -193,7 +193,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { let client = createAWSClient(credentialProvider: .selector(.configFile(credentialsFilePath: filename), .empty)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { credential in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { credential in XCTAssertEqual(credential.accessKeyId, "AWSACCESSKEYID") XCTAssertEqual(credential.secretAccessKey, "AWSSECRETACCESSKEY") XCTAssertEqual(credential.sessionToken, nil) @@ -206,7 +206,7 @@ class RuntimeSelectorCredentialProviderTests: XCTestCase { func testConfigFileProviderFail() { let client = createAWSClient(credentialProvider: .selector(.configFile(credentialsFilePath: "nonExistentCredentialFile"), .empty)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).flatMapThrowing { _ in + let futureResult = client.credentialProvider.getCredential(on: client.eventLoopGroup.next(), context: TestEnvironment.context).flatMapThrowing { _ in let internalProvider = try XCTUnwrap((client.credentialProvider as? RuntimeSelectorCredentialProvider)?.internalProvider) XCTAssert(internalProvider is StaticCredential) XCTAssert((internalProvider as? StaticCredential)?.isEmpty() == true) diff --git a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift index 45ca679fc7..f3ab1d2d77 100644 --- a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift @@ -22,6 +22,7 @@ class LoggingTests: XCTestCase { func testRequestIdIncrements() { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) + let context = TestEnvironment.contextWith(logger: logger) let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -35,8 +36,8 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) - let response2 = client.execute(operation: "test2", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, context: context) + let response2 = client.execute(operation: "test2", path: "/", httpMethod: .GET, serviceConfig: config, context: context) var count = 0 XCTAssertNoThrow(try server.processRaw { _ in @@ -62,6 +63,7 @@ class LoggingTests: XCTestCase { let logCollection = LoggingCollector.Logs() var logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) logger.logLevel = .trace + let context = TestEnvironment.contextWith(logger: logger) let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -76,7 +78,7 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + let response = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, context: context) XCTAssertNoThrow(try server.processRaw { _ in return .result(.ok, continueProcessing: false) @@ -96,6 +98,7 @@ class LoggingTests: XCTestCase { func testAWSError() { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) + let context = TestEnvironment.contextWith(logger: logger) let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -109,7 +112,7 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + let response = client.execute(operation: "test", path: "/", httpMethod: .GET, serviceConfig: config, context: context) XCTAssertNoThrow(try server.processRaw { _ in return .error(.accessDenied, continueProcessing: false) @@ -123,6 +126,7 @@ class LoggingTests: XCTestCase { func testRetryRequest() { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) + let context = TestEnvironment.contextWith(logger: logger) let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -136,7 +140,7 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, context: context) var count = 0 XCTAssertNoThrow(try server.processRaw { _ in @@ -157,6 +161,7 @@ class LoggingTests: XCTestCase { func testNoCredentialProvider() { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) + let context = TestEnvironment.contextWith(logger: logger) let client = createAWSClient(credentialProvider: .selector(.custom { _ in return NullCredentialProvider() })) defer { XCTAssertNoThrow(try client.syncShutdown()) } let serviceConfig = createServiceConfig() @@ -165,7 +170,7 @@ class LoggingTests: XCTestCase { path: "/", httpMethod: .GET, serviceConfig: serviceConfig, - logger: logger + context: context ).wait()) XCTAssertNotNil(logCollection.filter(metadata: "aws-error-message", with: "No credential provider found").first) } diff --git a/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift b/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift index 43506fd48a..be922ba410 100644 --- a/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift @@ -66,15 +66,15 @@ class PaginateTests: XCTestCase { let outputToken: Int? } - func counter(_ input: CounterInput, on eventLoop: EventLoop?, logger: Logger) -> EventLoopFuture { + func counter(_ input: CounterInput, on eventLoop: EventLoop?, context: AWSClient.Context) -> EventLoopFuture { return self.client.execute( operation: "TestOperation", path: "/", httpMethod: .POST, serviceConfig: self.config, input: input, - on: eventLoop, - logger: logger + context: TestEnvironment.context, + on: eventLoop ) } @@ -83,7 +83,7 @@ class PaginateTests: XCTestCase { input: input, command: self.counter, tokenKey: \CounterOutput.outputToken, - logger: TestEnvironment.logger, + context: TestEnvironment.context, onPage: onPage ) } @@ -144,15 +144,15 @@ class PaginateTests: XCTestCase { let outputToken: String? } - func stringList(_ input: StringListInput, on eventLoop: EventLoop? = nil, logger: Logger) -> EventLoopFuture { + func stringList(_ input: StringListInput, on eventLoop: EventLoop? = nil, context: AWSClient.Context) -> EventLoopFuture { return self.client.execute( operation: "TestOperation", path: "/", httpMethod: .POST, serviceConfig: self.config, input: input, - on: eventLoop, - logger: logger + context: context, + on: eventLoop ) } @@ -161,8 +161,8 @@ class PaginateTests: XCTestCase { input: input, command: self.stringList, tokenKey: \StringListOutput.outputToken, + context: TestEnvironment.context, on: eventLoop, - logger: TestEnvironment.logger, onPage: onPage ) } diff --git a/Tests/AWSSDKSwiftCoreTests/PayloadTests.swift b/Tests/AWSSDKSwiftCoreTests/PayloadTests.swift index b0686c3f3f..ac1c8c2f2e 100644 --- a/Tests/AWSSDKSwiftCoreTests/PayloadTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/PayloadTests.swift @@ -40,7 +40,7 @@ class PayloadTests: XCTestCase { httpMethod: .POST, serviceConfig: config, input: input, - logger: TestEnvironment.logger + context: TestEnvironment.context ) try awsServer.processRaw { request in @@ -87,7 +87,7 @@ class PayloadTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - logger: TestEnvironment.logger + context: TestEnvironment.context ) try awsServer.processRaw { _ in diff --git a/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift b/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift index 34c6f2f333..cc62047e97 100644 --- a/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift @@ -238,7 +238,7 @@ class PerformanceTests: XCTestCase { configuration: config ).applyMiddlewares(config.middlewares + client.middlewares) - let signer = try! client.createSigner(serviceConfig: config, logger: AWSClient.loggingDisabled).wait() + let signer = try! client.createSigner(serviceConfig: config, context: TestEnvironment.context).wait() measure { for _ in 0..<1000 { _ = awsRequest.createHTTPRequest(signer: signer) diff --git a/Tests/AWSSDKSwiftCoreTests/TracingTests.swift b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift index fe2c60fec3..5803aa337f 100644 --- a/Tests/AWSSDKSwiftCoreTests/TracingTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift @@ -16,30 +16,17 @@ import AWSTestUtils import Baggage import Instrumentation +import Logging import NIO import NIOHTTP1 import XCTest class TracingTests: XCTestCase { - private struct TestContext: AWSClient.Context { - public var baggage: BaggageContext - - init(traceContext: TestTracer.Context) { - var baggage = BaggageContext() - baggage.test = traceContext - self.baggage = baggage - } - } - func testTracingDownstreamCall() { // bootstrap tracer let tracer = TestTracer() InstrumentationSystem.bootstrap(tracer) - // create new trace - // TODO: not possible using TracingInstrument API, see https://github.com/slashmo/gsoc-swift-tracing/issues/137 - let context = TestContext(traceContext: .init()) - let awsServer = AWSTestServer(serviceProtocol: .json) let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { @@ -64,8 +51,7 @@ class TracingTests: XCTestCase { path: "/", httpMethod: .POST, serviceConfig: config, - context: context, - logger: TestEnvironment.logger + context: TestEnvironment.context ) XCTAssertNoThrow(try awsServer.httpBin()) XCTAssertNoThrow(try response.wait()) From a397d51afd511653d65533637595a794a1c8487f Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 10:20:02 +0800 Subject: [PATCH 03/12] Change operationName and arguments order based on review, create emptyContext factory --- .../AWSSDKSwiftCore/AWSClient+Paginate.swift | 4 +- Sources/AWSSDKSwiftCore/AWSClient.swift | 59 +++++++++++++------ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift b/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift index f4ea5b79a5..8cc6c7ce0e 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient+Paginate.swift @@ -34,7 +34,7 @@ extension AWSClient { /// - onPage: closure called with each block of entries public func paginate( input: Input, - command: @escaping (Input, EventLoop?, AWSClient.Context) -> EventLoopFuture, + command: @escaping (Input, AWSClient.Context, EventLoop?) -> EventLoopFuture, tokenKey: KeyPath, context: AWSClient.Context, on eventLoop: EventLoop? = nil, @@ -44,7 +44,7 @@ extension AWSClient { let promise = eventLoop.makePromise(of: Void.self) func paginatePart(input: Input) { - let responseFuture = command(input, eventLoop, context) + let responseFuture = command(input, context, eventLoop) .flatMap { response in return onPage(response, eventLoop) .map { (rt) -> Void in diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 9dc49ecbae..418d458695 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -239,18 +239,13 @@ extension AWSClient { /// invoke HTTP request fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context) -> EventLoopFuture { - // TODO: what should be the operation name? - let operationName: String = httpRequest.url.path - var span = InstrumentationSystem.tracingInstrument.startSpan(named: operationName, context: context, ofKind: .client, at: .now()) + var span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) return invoke(with: serviceConfig, context: context) { - // TODO: change Span interface to return carrier? - var carrier = context - carrier.baggage = span.context return self.httpClient.execute( request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, - context: carrier + context: context.with(baggage: span.context) ) } // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 @@ -263,15 +258,10 @@ extension AWSClient { } /// invoke HTTP request with response streaming - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context, stream: @escaping AWSHTTPClient.ResponseStream) -> EventLoopFuture { - // TODO: what should be the operation name? - let operationName: String = httpRequest.url.path - var span = InstrumentationSystem.tracingInstrument.startSpan(named: operationName, context: context, ofKind: .client, at: .now()) + fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, stream: @escaping AWSHTTPClient.ResponseStream, context: Context) -> EventLoopFuture { + var span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) return invoke(with: serviceConfig, context: context) { - // TODO: change Span interface to return carrier? - var carrier = context - carrier.baggage = span.context - return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, context: carrier, stream: stream) + return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, context: context.with(baggage: span.context), stream: stream) } // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 .always { result in @@ -493,8 +483,8 @@ extension AWSClient { request, with: serviceConfig, on: eventLoop, - context: context, - stream: stream + stream: stream, + context: context ) }.flatMapThrowing { response in return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) @@ -621,3 +611,38 @@ extension Logger { return logger } } + +// MARK: DefaultContext + +// TODO: revisit, see https://github.com/slashmo/gsoc-swift-baggage-context/issues/23 + +extension AWSClient { + internal struct DefaultContext: AWSClient.Context { + private let _logger: Logger = .init(label: "test") + var logger: Logger { + get { + self._logger.with(context: self.baggage) + } + set { + // TODO: remove + } + } + + var baggage: BaggageContext = .init() + } +} + +extension AWSClient { + /// Creates empty context. + public static func emptyContext() -> AWSClient.Context { + AWSClient.DefaultContext() + } +} + +extension AWSClient.Context { + public func with(baggage: BaggageContext) -> AWSClient.Context { + var copy = self + copy.baggage = baggage + return copy + } +} From b500d2c1f1060711f42afd62d2df1290072e37c3 Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 10:40:14 +0800 Subject: [PATCH 04/12] Create convenience function to endSpan --- Sources/AWSSDKSwiftCore/AWSClient.swift | 35 ++++++++++++++----------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 418d458695..f695f46f34 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -239,7 +239,7 @@ extension AWSClient { /// invoke HTTP request fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context) -> EventLoopFuture { - var span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) + let span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) return invoke(with: serviceConfig, context: context) { return self.httpClient.execute( request: httpRequest, @@ -248,28 +248,16 @@ extension AWSClient { context: context.with(baggage: span.context) ) } - // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 - .always { result in - if case Result.failure(let error) = result { - span.recordError(error) - } - span.end() - } + .endSpan(span) } /// invoke HTTP request with response streaming fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, stream: @escaping AWSHTTPClient.ResponseStream, context: Context) -> EventLoopFuture { - var span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) + let span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) return invoke(with: serviceConfig, context: context) { return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, context: context.with(baggage: span.context), stream: stream) } - // TODO: use NIO helpers, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 - .always { result in - if case Result.failure(let error) = result { - span.recordError(error) - } - span.end() - } + .endSpan(span) } /// create HTTPClient @@ -646,3 +634,18 @@ extension AWSClient.Context { return copy } } + +// TODO: temporary?, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 + +private extension EventLoopFuture { + func endSpan(_ span: TracingInstrumentation.Span) -> EventLoopFuture { + var span = span // TODO: see https://github.com/slashmo/gsoc-swift-tracing/issues/119 + whenComplete { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() + } + return self + } +} From d1d5ac4a4840f6720dce83efb4126e52e86530b6 Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 11:20:31 +0800 Subject: [PATCH 05/12] Instrument execute and signURL, getCredential, createRequest, processResponse --- Sources/AWSSDKSwiftCore/AWSClient.swift | 397 +++++++++++++++--------- 1 file changed, 245 insertions(+), 152 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index f695f46f34..8a6e55c85b 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -33,7 +33,6 @@ import NIOTransportServices import TracingInstrumentation // TODO: instrument initialization -// TODO: create internal spans for signing, parsing? /// This is the workhorse of aws-sdk-swift-core. You provide it with a `AWSShape` Input object, it converts it to `AWSRequest` which is then converted /// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a `AWSResponse` @@ -200,14 +199,15 @@ extension AWSClient { fileprivate func invoke( with serviceConfig: AWSServiceConfig, context: Context, - _ request: @escaping () -> EventLoopFuture + _ request: @escaping (Context) -> EventLoopFuture ) -> EventLoopFuture { let eventloop = self.eventLoopGroup.next() let promise = eventloop.makePromise(of: AWSHTTPResponse.self) + let span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context) func execute(attempt: Int) { // execute HTTP request - _ = request() + _ = request(context.with(baggage: span.context)) .flatMapThrowing { (response) throws -> Void in // if it returns an HTTP status code outside 2xx then throw an error guard (200..<300).contains(response.status.code) else { throw HTTPResponseError(response: response) } @@ -234,30 +234,7 @@ extension AWSClient { execute(attempt: 0) - return promise.futureResult - } - - /// invoke HTTP request - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, context: Context) -> EventLoopFuture { - let span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) - return invoke(with: serviceConfig, context: context) { - return self.httpClient.execute( - request: httpRequest, - timeout: serviceConfig.timeout, - on: eventLoop, - context: context.with(baggage: span.context) - ) - } - .endSpan(span) - } - - /// invoke HTTP request with response streaming - fileprivate func invoke(_ httpRequest: AWSHTTPRequest, with serviceConfig: AWSServiceConfig, on eventLoop: EventLoop, stream: @escaping AWSHTTPClient.ResponseStream, context: Context) -> EventLoopFuture { - let span = InstrumentationSystem.tracingInstrument.startSpan(named: "invoke", context: context, ofKind: .client, at: .now()) - return invoke(with: serviceConfig, context: context) { - return self.httpClient.execute(request: httpRequest, timeout: serviceConfig.timeout, on: eventLoop, context: context.with(baggage: span.context), stream: stream) - } - .endSpan(span) + return promise.futureResult.endSpan(span) } /// create HTTPClient @@ -273,8 +250,9 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - serviceConfig: AWS service configuration used in request creation and signing /// - input: Input object + /// - config: AWS service configuration used in request creation and signing + /// - context: additional context for call /// - returns: /// Empty Future that completes when response is received public func execute( @@ -283,31 +261,30 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context, + context: AWSClient.Context, on eventLoop: EventLoop? = nil ) -> EventLoopFuture { - let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in - let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) - let awsRequest = try AWSRequest( - operation: operationName, - path: path, - httpMethod: httpMethod, - input: input, - configuration: serviceConfig - ) - return try awsRequest - .applyMiddlewares(serviceConfig.middlewares + self.middlewares) - .createHTTPRequest(signer: signer) - }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) - }.map { _ in - return - } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) + return execute( + operation: operationName, + createRequest: { _ in + try AWSRequest( + operation: operationName, + path: path, + httpMethod: httpMethod, + input: input, + configuration: serviceConfig + ) + }, + execute: { request, eventLoop, context in + return self.httpClient.execute(request: request, timeout: serviceConfig.timeout, on: eventLoop, context: context) + }, + processResponse: { _ in + return + }, + config: serviceConfig, + context: context, + on: eventLoop + ) } /// execute an empty request and return a future with an empty response @@ -315,7 +292,8 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - serviceConfig: AWS service configuration used in request creation and signing + /// - config: AWS service configuration used in request creation and signing + /// - context: additional context for call /// - returns: /// Empty Future that completes when response is received public func execute( @@ -323,31 +301,29 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, - context: Context, + context: AWSClient.Context, on eventLoop: EventLoop? = nil ) -> EventLoopFuture { - let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential -> AWSHTTPRequest in - let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) - let awsRequest = try AWSRequest( - operation: operationName, - path: path, - httpMethod: httpMethod, - configuration: serviceConfig - ) - return try awsRequest - .applyMiddlewares(serviceConfig.middlewares + self.middlewares) - .createHTTPRequest(signer: signer) - - }.flatMap { request -> EventLoopFuture in - return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) - }.map { _ in - return - } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) + return execute( + operation: operationName, + createRequest: { _ in + try AWSRequest( + operation: operationName, + path: path, + httpMethod: httpMethod, + configuration: serviceConfig + ) + }, + execute: { request, eventLoop, context in + return self.httpClient.execute(request: request, timeout: serviceConfig.timeout, on: eventLoop, context: context) + }, + processResponse: { _ in + return + }, + config: serviceConfig, + context: context, + on: eventLoop + ) } /// execute an empty request and return a future with the output object generated from the response @@ -355,7 +331,8 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - serviceConfig: AWS service configuration used in request creation and signing + /// - config: AWS service configuration used in request creation and signing + /// - context: additional context for call /// - returns: /// Future containing output object that completes when response is received public func execute( @@ -363,30 +340,29 @@ extension AWSClient { path: String, httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, - context: Context, + context: AWSClient.Context, on eventLoop: EventLoop? = nil ) -> EventLoopFuture { - let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in - let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) - let awsRequest = try AWSRequest( - operation: operationName, - path: path, - httpMethod: httpMethod, - configuration: serviceConfig - ) - return try awsRequest - .applyMiddlewares(serviceConfig.middlewares + self.middlewares) - .createHTTPRequest(signer: signer) - }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) - }.flatMapThrowing { response in - return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) - } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) + return execute( + operation: operationName, + createRequest: { _ in + try AWSRequest( + operation: operationName, + path: path, + httpMethod: httpMethod, + configuration: serviceConfig + ) + }, + execute: { request, eventLoop, context in + return self.httpClient.execute(request: request, timeout: serviceConfig.timeout, on: eventLoop, context: context) + }, + processResponse: { response in + return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) + }, + config: serviceConfig, + context: context, + on: eventLoop + ) } /// execute a request with an input object and return a future with the output object generated from the response @@ -394,8 +370,9 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - serviceConfig: AWS service configuration used in request creation and signing /// - input: Input object + /// - config: AWS service configuration used in request creation and signing + /// - context: additional context for call /// - returns: /// Future containing output object that completes when response is received public func execute( @@ -404,31 +381,30 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context, + context: AWSClient.Context, on eventLoop: EventLoop? = nil ) -> EventLoopFuture { - let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in - let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) - let awsRequest = try AWSRequest( - operation: operationName, - path: path, - httpMethod: httpMethod, - input: input, - configuration: serviceConfig - ) - return try awsRequest - .applyMiddlewares(serviceConfig.middlewares + self.middlewares) - .createHTTPRequest(signer: signer) - }.flatMap { request in - return self.invoke(request, with: serviceConfig, on: eventLoop, context: context) - }.flatMapThrowing { response in - return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) - } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) + return execute( + operation: operationName, + createRequest: { _ in + try AWSRequest( + operation: operationName, + path: path, + httpMethod: httpMethod, + input: input, + configuration: serviceConfig + ) + }, + execute: { request, eventLoop, context in + return self.httpClient.execute(request: request, timeout: serviceConfig.timeout, on: eventLoop, context: context) + }, + processResponse: { response in + return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) + }, + config: serviceConfig, + context: context, + on: eventLoop + ) } /// execute a request with an input object and return a future with the output object generated from the response @@ -436,8 +412,9 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - serviceConfig: AWS service configuration used in request creation and signing /// - input: Input object + /// - config: AWS service configuration used in request creation and signing + /// - context: additional context for call /// - returns: /// Future containing output object that completes when response is received public func execute( @@ -446,38 +423,74 @@ extension AWSClient { httpMethod: HTTPMethod, serviceConfig: AWSServiceConfig, input: Input, - context: Context, + context: AWSClient.Context, on eventLoop: EventLoop? = nil, stream: @escaping AWSHTTPClient.ResponseStream + ) -> EventLoopFuture { + return execute( + operation: operationName, + createRequest: { _ in + try AWSRequest( + operation: operationName, + path: path, + httpMethod: httpMethod, + input: input, + configuration: serviceConfig + ) + }, + execute: { request, eventLoop, context in + return self.httpClient.execute(request: request, timeout: serviceConfig.timeout, on: eventLoop, context: context, stream: stream) + }, + processResponse: { response in + return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) + }, + config: serviceConfig, + context: context, + on: eventLoop + ) + } + + /// internal version of execute + internal func execute( + operation operationName: String, + createRequest: @escaping (AWSSigner) throws -> AWSRequest, + execute: @escaping (AWSHTTPRequest, EventLoop, AWSClient.Context) -> EventLoopFuture, + processResponse: @escaping (AWSHTTPResponse) throws -> Output, + config: AWSServiceConfig, + context: AWSClient.Context, + on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) - let future: EventLoopFuture = credentialProvider.getCredential(on: eventLoop, context: context).flatMapThrowing { credential in - let signer = AWSSigner(credentials: credential, name: serviceConfig.signingName, region: serviceConfig.region.rawValue) - let awsRequest = try AWSRequest( - operation: operationName, - path: path, - httpMethod: httpMethod, - input: input, - configuration: serviceConfig - ) + // TODO: set metadata via baggage +// let logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: config.service) + let span = InstrumentationSystem.tracingInstrument.startSpan( + named: "\(config.service):\(operationName)", // TODO: or just "execute"? + context: context + ) + let context = context.with(baggage: span.context) + let future: EventLoopFuture = InstrumentationSystem.tracingInstrument.span(named: "getCredential", context: context) { span in + credentialProvider.getCredential(on: eventLoop, context: context.with(baggage: span.context)) + } + .flatMapThrowing { credential in + let signer = AWSSigner(credentials: credential, name: config.signingName, region: config.region.rawValue) + let awsRequest = try InstrumentationSystem.tracingInstrument.span(named: "createRequest", context: context) { _ in + try createRequest(signer) + } return try awsRequest - .applyMiddlewares(serviceConfig.middlewares + self.middlewares) + .applyMiddlewares(config.middlewares + self.middlewares) .createHTTPRequest(signer: signer) }.flatMap { request in - return self.invoke( - request, - with: serviceConfig, - on: eventLoop, - stream: stream, - context: context - ) + return self.invoke(with: config, context: context) { context in + execute(request, eventLoop, context) + } }.flatMapThrowing { response in - return try self.validate(operation: operationName, response: response, serviceConfig: serviceConfig) + try InstrumentationSystem.tracingInstrument.span(named: "processResponse", context: context) { _ in + try processResponse(response) + } } - return recordRequest(future, service: serviceConfig.service, operation: operationName, logger: context.logger) + .endSpan(span) + // TODO: recordRequest duplicates some tracing functionality, pass context not jsut logger + return recordRequest(future, service: config.service, operation: operationName, logger: context.logger) } /// generate a signed URL @@ -495,11 +508,12 @@ extension AWSClient { serviceConfig: AWSServiceConfig, context: CredentialProvider.Context ) -> EventLoopFuture { - // TODO: update logger via baggage - var context = context - context.logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) - return createSigner(serviceConfig: serviceConfig, context: context).map { signer in - signer.signURL(url: url, method: HTTPMethod(rawValue: httpMethod), expires: expires) + // TODO: set metadata via baggage +// let logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) + return InstrumentationSystem.tracingInstrument.span(named: "signURL", context: context) { span in + createSigner(serviceConfig: serviceConfig, context: context.with(baggage: span.context)).map { signer in + signer.signURL(url: url, method: HTTPMethod(rawValue: httpMethod), expires: expires) + } } } @@ -635,7 +649,86 @@ extension AWSClient.Context { } } -// TODO: temporary?, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 +// TODO: extensions based on XRaySDK API, temporary, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 + +private extension TracingInstrument { + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) throws -> T + ) + rethrows -> T + { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + defer { + span.end() + } + do { + return try body(span) + } catch { + span.recordError(error) + throw error + } + } + + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) throws -> Result + ) + rethrows -> Result + { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + defer { + span.end() + } + do { + let result = try body(span) + if case Result.failure(let error) = result { + span.recordError(error) + } + return result + } catch { + span.recordError(error) + throw error + } + } + + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) -> EventLoopFuture + ) -> EventLoopFuture { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + return body(span).always { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() + } + } +} private extension EventLoopFuture { func endSpan(_ span: TracingInstrumentation.Span) -> EventLoopFuture { From 35386b47d4edb79c2e8f01db7818d2aadd133b2e Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 12:48:36 +0800 Subject: [PATCH 06/12] Propagate request metadata in context baggage --- Sources/AWSSDKSwiftCore/AWSClient.swift | 79 +++++++++++++++++++------ 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 8a6e55c85b..ce79589253 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -72,11 +72,10 @@ public final class AWSClient { case createNew } + // TODO: make part of the context /// default logger that logs nothing public static let loggingDisabled = Logger(label: "AWS-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) - static let globalRequestID = NIOAtomic.makeAtomic(value: 0) - /// AWS credentials provider public let credentialProvider: CredentialProvider /// middleware code to be applied to requests and responses @@ -461,13 +460,13 @@ extension AWSClient { on eventLoop: EventLoop? = nil ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() - // TODO: set metadata via baggage -// let logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: operationName, service: config.service) + // TODO: discuss hot to update the context (and its baggage) + var context = context.withRequest(.create(operation: operationName, serviceConfig: config)) let span = InstrumentationSystem.tracingInstrument.startSpan( named: "\(config.service):\(operationName)", // TODO: or just "execute"? context: context ) - let context = context.with(baggage: span.context) + context = context.with(baggage: span.context) let future: EventLoopFuture = InstrumentationSystem.tracingInstrument.span(named: "getCredential", context: context) { span in credentialProvider.getCredential(on: eventLoop, context: context.with(baggage: span.context)) } @@ -489,7 +488,7 @@ extension AWSClient { } } .endSpan(span) - // TODO: recordRequest duplicates some tracing functionality, pass context not jsut logger + // TODO: recordRequest duplicates some tracing functionality, pass context not just logger return recordRequest(future, service: config.service, operation: operationName, logger: context.logger) } @@ -508,8 +507,7 @@ extension AWSClient { serviceConfig: AWSServiceConfig, context: CredentialProvider.Context ) -> EventLoopFuture { - // TODO: set metadata via baggage -// let logger = context.logger.attachingRequestId(Self.globalRequestID.add(1), operation: "signURL", service: serviceConfig.service) + let context = context.withRequest(.create(operation: "signURL", serviceConfig: serviceConfig)) return InstrumentationSystem.tracingInstrument.span(named: "signURL", context: context) { span in createSigner(serviceConfig: serviceConfig, context: context.with(baggage: span.context)).map { signer in signer.signURL(url: url, method: HTTPMethod(rawValue: httpMethod), expires: expires) @@ -604,16 +602,6 @@ extension AWSClient { } } -extension Logger { - func attachingRequestId(_ id: Int, operation: String, service: String) -> Logger { - var logger = self - logger[metadataKey: "aws-service"] = .string(service) - logger[metadataKey: "aws-operation"] = .string(operation) - logger[metadataKey: "aws-request-id"] = "\(id)" - return logger - } -} - // MARK: DefaultContext // TODO: revisit, see https://github.com/slashmo/gsoc-swift-baggage-context/issues/23 @@ -626,7 +614,7 @@ extension AWSClient { self._logger.with(context: self.baggage) } set { - // TODO: remove + // TODO: will not be required in next release, see https://github.com/slashmo/gsoc-swift-baggage-context/pull/31 } } @@ -649,6 +637,59 @@ extension AWSClient.Context { } } +// MARK: RequestMetadata + +// extension Logger { +// func attachingRequestId(_ id: Int, operation: String, service: String) -> Logger { +// var logger = self +// logger[metadataKey: "aws-service"] = .string(service) +// logger[metadataKey: "aws-operation"] = .string(operation) +// logger[metadataKey: "aws-request-id"] = "\(id)" +// return logger +// } +// } + +private extension AWSClient { + struct RequestMetadata: CustomStringConvertible { + static let globalRequestID = NIOAtomic.makeAtomic(value: 0) + + static func create( + operation: String, + serviceConfig: AWSServiceConfig, + requestId: Int = Self.globalRequestID.add(1) + ) -> RequestMetadata { + RequestMetadata(requestId: requestId, service: serviceConfig.service, operation: operation) + } + +// static func create(serviceConfig: AWSServiceConfig) -> (String) -> RequestMetadata { +// { operation in +// Self.create(operation: operation, serviceConfig: serviceConfig) +// } +// } + + var requestId: Int + var service: String + var operation: String + + var description: String { + "request-id=\(requestId),service=\(service),operation=\(operation)" + } + } + + enum RequestKey: BaggageContextKey { + typealias Value = RequestMetadata + var name: String { "aws-sdk" } + } +} + +private extension AWSClient.Context { + func withRequest(_ value: AWSClient.RequestMetadata) -> AWSClient.Context { + var copy = self + copy.baggage[AWSClient.RequestKey.self] = value + return copy + } +} + // TODO: extensions based on XRaySDK API, temporary, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 private extension TracingInstrument { From 5d38cf7d8b3194e7ed85c200e4ae3dc049a3cdd9 Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 13:33:21 +0800 Subject: [PATCH 07/12] Instrument initialization? --- Sources/AWSSDKSwiftCore/AWSClient.swift | 48 ++++++++++++++----------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index ce79589253..8b8b496f01 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -32,13 +32,11 @@ import NIOHTTP1 import NIOTransportServices import TracingInstrumentation -// TODO: instrument initialization - /// This is the workhorse of aws-sdk-swift-core. You provide it with a `AWSShape` Input object, it converts it to `AWSRequest` which is then converted /// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a `AWSResponse` /// which is then decoded to generate a `AWSShape` Output object. If it is not successful then `AWSClient` will throw an `AWSErrorType`. public final class AWSClient { - /// AWS operation context including trace context + /// AWS client context, carries trace context and logger. public typealias Context = BaggageLogging.LoggingBaggageContextCarrier /// Errors returned by AWSClient code @@ -72,10 +70,6 @@ public final class AWSClient { case createNew } - // TODO: make part of the context - /// default logger that logs nothing - public static let loggingDisabled = Logger(label: "AWS-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) - /// AWS credentials provider public let credentialProvider: CredentialProvider /// middleware code to be applied to requests and responses @@ -88,8 +82,8 @@ public final class AWSClient { public var eventLoopGroup: EventLoopGroup { return httpClient.eventLoopGroup } /// Retry policy specifying what to do when a request fails public let retryPolicy: RetryPolicy - /// Logger used for non-request based output - let clientLogger: Logger + + private let context: AWSClient.Context private let isShutdown = NIOAtomic.makeAtomic(value: false) @@ -99,34 +93,40 @@ public final class AWSClient { /// - retryPolicy: Object returning whether retries should be attempted. Possible options are NoRetry(), ExponentialRetry() or JitterRetry() /// - middlewares: Array of middlewares to apply to requests and responses /// - httpClientProvider: HTTPClient to use. Use `.createNew` if you want the client to manage its own HTTPClient. - /// - logger: Logger used to log background AWSClient events public init( credentialProvider credentialProviderFactory: CredentialProviderFactory = .default, retryPolicy retryPolicyFactory: RetryPolicyFactory = .default, middlewares: [AWSServiceMiddleware] = [], httpClientProvider: HTTPClientProvider, - logger clientLogger: Logger = AWSClient.loggingDisabled, - baggage: BaggageContext = .init() // TODO: empty baggage? + context: AWSClient.Context = AWSClient.emptyContext() ) { + // TODO: not sure if it makes sense as most resources are created lazily + var span = InstrumentationSystem.tracingInstrument.startSpan(named: "AWSClient.init", context: context) + defer { + span.end() + } + // setup httpClient self.httpClientProvider = httpClientProvider switch httpClientProvider { case .shared(let providedHTTPClient): self.httpClient = providedHTTPClient case .createNew: - self.httpClient = AWSClient.createHTTPClient() + self.httpClient = InstrumentationSystem.tracingInstrument.span(named: "createHTTPClient", context: context.with(baggage: span.context)) { _ in + AWSClient.createHTTPClient() + } } self.credentialProvider = credentialProviderFactory.createProvider(context: .init( httpClient: httpClient, eventLoop: httpClient.eventLoopGroup.next(), - logger: clientLogger, - baggage: baggage + logger: context.logger, + baggage: context.baggage )) self.middlewares = middlewares self.retryPolicy = retryPolicyFactory.retryPolicy - self.clientLogger = clientLogger + self.context = context } deinit { @@ -179,7 +179,7 @@ public final class AWSClient { case .createNew: self.httpClient.shutdown(queue: queue) { error in if let error = error { - self.clientLogger.error("Error shutting down HTTP client", metadata: [ + self.context.logger.error("Error shutting down HTTP client", metadata: [ "aws-error": "\(error)", ]) } @@ -608,7 +608,7 @@ extension AWSClient { extension AWSClient { internal struct DefaultContext: AWSClient.Context { - private let _logger: Logger = .init(label: "test") + private let _logger: Logger var logger: Logger { get { self._logger.with(context: self.baggage) @@ -619,13 +619,19 @@ extension AWSClient { } var baggage: BaggageContext = .init() + + internal init(logger: Logger, baggage: BaggageContext = .init()) { + self._logger = logger + self.baggage = baggage + } } } extension AWSClient { - /// Creates empty context. - public static func emptyContext() -> AWSClient.Context { - AWSClient.DefaultContext() + private static let loggingDisabled = Logger(label: "AWS-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) + + public static func emptyContext(logger: Logging.Logger? = nil, baggage: BaggageContext = .init()) -> AWSClient.Context { + AWSClient.DefaultContext(logger: logger ?? Self.loggingDisabled, baggage: baggage) } } From 78c1c31757d47d891f66ff79776d078c612e20fa Mon Sep 17 00:00:00 2001 From: Michal A Date: Fri, 28 Aug 2020 13:40:11 +0800 Subject: [PATCH 08/12] Cosmetic, update tests --- Sources/AWSSDKSwiftCore/AWSClient.swift | 50 +++++++++++-------- .../Credential/CredentialProvider.swift | 7 +++ Sources/AWSTestUtils/TestUtils.swift | 18 +++---- .../AWSResponseTests.swift | 8 +-- .../ConfigFileCredentialProviderTests.swift | 4 +- .../Credential/CredentialProviderTests.swift | 7 ++- .../RotatingCredentialProviderTests.swift | 6 +-- Tests/AWSSDKSwiftCoreTests/LoggingTests.swift | 22 +++++--- .../AWSSDKSwiftCoreTests/PaginateTests.swift | 4 +- Tests/AWSSDKSwiftCoreTests/TracingTests.swift | 10 ++-- 10 files changed, 77 insertions(+), 59 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 8b8b496f01..eded7b2ea6 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -120,8 +120,7 @@ public final class AWSClient { self.credentialProvider = credentialProviderFactory.createProvider(context: .init( httpClient: httpClient, eventLoop: httpClient.eventLoopGroup.next(), - logger: context.logger, - baggage: context.baggage + context: context )) self.middlewares = middlewares @@ -461,7 +460,8 @@ extension AWSClient { ) -> EventLoopFuture { let eventLoop = eventLoop ?? eventLoopGroup.next() // TODO: discuss hot to update the context (and its baggage) - var context = context.withRequest(.create(operation: operationName, serviceConfig: config)) + var context = context + context.setRequest(.init(operation: operationName, serviceConfig: config)) let span = InstrumentationSystem.tracingInstrument.startSpan( named: "\(config.service):\(operationName)", // TODO: or just "execute"? context: context @@ -488,8 +488,7 @@ extension AWSClient { } } .endSpan(span) - // TODO: recordRequest duplicates some tracing functionality, pass context not just logger - return recordRequest(future, service: config.service, operation: operationName, logger: context.logger) + return recordRequest(future, service: config.service, operation: operationName, context: context) } /// generate a signed URL @@ -507,7 +506,8 @@ extension AWSClient { serviceConfig: AWSServiceConfig, context: CredentialProvider.Context ) -> EventLoopFuture { - let context = context.withRequest(.create(operation: "signURL", serviceConfig: serviceConfig)) + var context = context + context.setRequest(.init(operation: "signURL", serviceConfig: serviceConfig)) return InstrumentationSystem.tracingInstrument.span(named: "signURL", context: context) { span in createSigner(serviceConfig: serviceConfig, context: context.with(baggage: span.context)).map { signer in signer.signURL(url: url, method: HTTPMethod(rawValue: httpMethod), expires: expires) @@ -573,7 +573,9 @@ extension AWSClient.ClientError: CustomStringConvertible { extension AWSClient { /// Record request in swift-metrics, and swift-log - func recordRequest(_ future: EventLoopFuture, service: String, operation: String, logger: Logger) -> EventLoopFuture { + func recordRequest(_ future: EventLoopFuture, service: String, operation: String, context: AWSClient.Context) -> EventLoopFuture { + let logger = context.logger + let dimensions: [(String, String)] = [("aws-service", service), ("aws-operation", operation)] let startTime = DispatchTime.now().uptimeNanoseconds @@ -607,7 +609,7 @@ extension AWSClient { // TODO: revisit, see https://github.com/slashmo/gsoc-swift-baggage-context/issues/23 extension AWSClient { - internal struct DefaultContext: AWSClient.Context { + private struct DefaultContext: AWSClient.Context { private let _logger: Logger var logger: Logger { get { @@ -625,9 +627,7 @@ extension AWSClient { self.baggage = baggage } } -} -extension AWSClient { private static let loggingDisabled = Logger(label: "AWS-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) public static func emptyContext(logger: Logging.Logger? = nil, baggage: BaggageContext = .init()) -> AWSClient.Context { @@ -636,6 +636,7 @@ extension AWSClient { } extension AWSClient.Context { + // TODO: discuss how to update context baggage public func with(baggage: BaggageContext) -> AWSClient.Context { var copy = self copy.baggage = baggage @@ -659,13 +660,13 @@ private extension AWSClient { struct RequestMetadata: CustomStringConvertible { static let globalRequestID = NIOAtomic.makeAtomic(value: 0) - static func create( - operation: String, - serviceConfig: AWSServiceConfig, - requestId: Int = Self.globalRequestID.add(1) - ) -> RequestMetadata { - RequestMetadata(requestId: requestId, service: serviceConfig.service, operation: operation) - } +// static func create( +// operation: String, +// serviceConfig: AWSServiceConfig, +// requestId: Int = Self.globalRequestID.add(1) +// ) -> RequestMetadata { +// RequestMetadata(requestId: requestId, service: serviceConfig.service, operation: operation) +// } // static func create(serviceConfig: AWSServiceConfig) -> (String) -> RequestMetadata { // { operation in @@ -678,21 +679,26 @@ private extension AWSClient { var operation: String var description: String { - "request-id=\(requestId),service=\(service),operation=\(operation)" + "aws-request-id=\(requestId),aws-service=\(service),aws-operation=\(operation)" + } + + init(operation: String, serviceConfig: AWSServiceConfig, requestId: Int = Self.globalRequestID.add(1)) { + self.requestId = requestId + self.service = serviceConfig.service + self.operation = operation } } enum RequestKey: BaggageContextKey { typealias Value = RequestMetadata + // TODO: the name is not logged as the logger metadata key, check/report/fix var name: String { "aws-sdk" } } } private extension AWSClient.Context { - func withRequest(_ value: AWSClient.RequestMetadata) -> AWSClient.Context { - var copy = self - copy.baggage[AWSClient.RequestKey.self] = value - return copy + mutating func setRequest(_ value: AWSClient.RequestMetadata) { + baggage[AWSClient.RequestKey.self] = value } } diff --git a/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift b/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift index e3afd70451..2dfa4fe7ee 100644 --- a/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift +++ b/Sources/AWSSDKSwiftCore/Credential/CredentialProvider.swift @@ -46,6 +46,13 @@ public struct CredentialProviderFactory { public var logger: Logger // TODO: should not need to be mutable /// The context baggage. public var baggage: BaggageContext + + public init(httpClient: AWSHTTPClient, eventLoop: EventLoop, context: BaggageLogging.LoggingBaggageContextCarrier) { + self.httpClient = httpClient + self.eventLoop = eventLoop + self.logger = context.logger + self.baggage = context.baggage + } } private let cb: (Context) -> CredentialProvider diff --git a/Sources/AWSTestUtils/TestUtils.swift b/Sources/AWSTestUtils/TestUtils.swift index 67912593c1..65a2b9603b 100644 --- a/Sources/AWSTestUtils/TestUtils.swift +++ b/Sources/AWSTestUtils/TestUtils.swift @@ -37,14 +37,14 @@ public func createAWSClient( retryPolicy: RetryPolicyFactory = .noRetry, middlewares: [AWSServiceMiddleware] = TestEnvironment.middlewares, httpClientProvider: AWSClient.HTTPClientProvider = .createNew, - logger: Logger = TestEnvironment.logger + context: AWSClient.Context = TestEnvironment.context ) -> AWSClient { return AWSClient( credentialProvider: credentialProvider, retryPolicy: retryPolicy, middlewares: middlewares, httpClientProvider: httpClientProvider, - logger: logger + context: context ) } @@ -99,18 +99,12 @@ public func createRandomBuffer(_ w: UInt, _ z: UInt, size: Int) -> [UInt8] { /// Provide various test environment variables public struct TestEnvironment { - private struct TestContext: AWSClient.Context { - var baggage: BaggageContext - var logger = Logger(label: "test") // TODO: store logged messages for further inspection - } - /// current list of middleware public static var middlewares: [AWSServiceMiddleware] { return (Environment["AWS_ENABLE_LOGGING"] == "true") ? [AWSLoggingMiddleware()] : [] } - // TODO: make private and get via context? - public static var logger: Logger = { + private static var logger: Logger = { if let loggingLevel = Environment["AWS_LOG_LEVEL"] { if let logLevel = Logger.Level(rawValue: loggingLevel.lowercased()) { var logger = Logger(label: "aws-sdk-swift") @@ -118,20 +112,20 @@ public struct TestEnvironment { return logger } } - return AWSClient.loggingDisabled + return Logger(label: "AWS-do-not-log", factory: { _ in SwiftLogNoOpLogHandler() }) }() public static var context: AWSClient.Context = { let traceContext = TestTracer.Context() var baggage = BaggageContext() baggage.test = traceContext - return TestContext(baggage: baggage, logger: Self.logger) + return AWSClient.emptyContext(logger: Self.logger, baggage: baggage) }() public static func contextWith(logger: Logging.Logger) -> AWSClient.Context { let traceContext = TestTracer.Context() var baggage = BaggageContext() baggage.test = traceContext - return TestContext(baggage: baggage, logger: logger) + return AWSClient.emptyContext(logger: logger, baggage: baggage) } } diff --git a/Tests/AWSSDKSwiftCoreTests/AWSResponseTests.swift b/Tests/AWSSDKSwiftCoreTests/AWSResponseTests.swift index 69a8e236b3..14e2a6ab26 100644 --- a/Tests/AWSSDKSwiftCoreTests/AWSResponseTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/AWSResponseTests.swift @@ -214,7 +214,7 @@ class AWSResponseTests: XCTestCase { var awsResponse: AWSResponse? XCTAssertNoThrow(awsResponse = try AWSResponse(from: response, serviceProtocol: .json(version: "1.1"), raw: false)) - let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.logger) + let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.context.logger) XCTAssertEqual(error as? ServiceErrorType, .resourceNotFoundException(message: "Donald Where's Your Troosers?")) } @@ -228,7 +228,7 @@ class AWSResponseTests: XCTestCase { var awsResponse: AWSResponse? XCTAssertNoThrow(awsResponse = try AWSResponse(from: response, serviceProtocol: .restxml, raw: false)) - let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.logger) + let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.context.logger) XCTAssertEqual(error as? ServiceErrorType, .noSuchKey(message: "It doesn't exist")) } @@ -242,7 +242,7 @@ class AWSResponseTests: XCTestCase { var awsResponse: AWSResponse? XCTAssertNoThrow(awsResponse = try AWSResponse(from: response, serviceProtocol: .query, raw: false)) - let error = awsResponse?.generateError(serviceConfig: queryService, logger: TestEnvironment.logger) + let error = awsResponse?.generateError(serviceConfig: queryService, logger: TestEnvironment.context.logger) XCTAssertEqual(error as? ServiceErrorType, .messageRejected(message: "Don't like it")) } @@ -256,7 +256,7 @@ class AWSResponseTests: XCTestCase { var awsResponse: AWSResponse? XCTAssertNoThrow(awsResponse = try AWSResponse(from: response, serviceProtocol: .ec2, raw: false)) - let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.logger) as? AWSResponseError + let error = awsResponse?.generateError(serviceConfig: service, logger: TestEnvironment.context.logger) as? AWSResponseError XCTAssertEqual(error?.errorCode, "NoSuchKey") XCTAssertEqual(error?.message, "It doesn't exist") } diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift index a7e549aa5f..bce80c3be2 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/ConfigFileCredentialProviderTests.swift @@ -183,7 +183,7 @@ class ConfigFileCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, context: TestEnvironment.context)) var credential: Credential? XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) @@ -202,7 +202,7 @@ class ConfigFileCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, context: TestEnvironment.context)) XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) { error in print("\(error)") diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift index 0b994bdbbd..fe426e5498 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/CredentialProviderTests.swift @@ -50,7 +50,7 @@ class CredentialProviderTests: XCTestCase { let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let eventLoop = eventLoopGroup.next() - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init()) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, context: TestEnvironment.context) let deferredProvider = DeferredCredentialProvider(context: context, provider: MyCredentialProvider()) XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, context: context).wait()) XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, context: context).wait()) @@ -77,8 +77,7 @@ class CredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init( httpClient: httpClient, eventLoop: eventLoop, - logger: TestEnvironment.logger, - baggage: .init() + context: TestEnvironment.context )) var credential: Credential? @@ -98,7 +97,7 @@ class CredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let factory = CredentialProviderFactory.configFile(credentialsFilePath: filenameURL.path) - let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, baggage: .init())) + let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, context: TestEnvironment.context)) XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, context: TestEnvironment.context).wait()) { error in print("\(error)") diff --git a/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift b/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift index 6facb87638..ce092bc8b9 100644 --- a/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/Credential/RotatingCredentialProviderTests.swift @@ -54,7 +54,7 @@ class RotatingCredentialProviderTests: XCTestCase { hitCount += 1 return $0.makeSucceededFuture(cred) } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: Logger(label: "aws-sdk-swift"), baggage: .init()) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, context: TestEnvironment.context) let provider = RotatingCredentialProvider(context: context, provider: client) // get credentials for first time @@ -98,7 +98,7 @@ class RotatingCredentialProviderTests: XCTestCase { hitCount += 1 return promise.futureResult } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger, baggage: .init()) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, context: TestEnvironment.context) let provider = RotatingCredentialProvider(context: context, provider: client) var resultFutures = [EventLoopFuture]() @@ -159,7 +159,7 @@ class RotatingCredentialProviderTests: XCTestCase { ) return eventLoop.makeSucceededFuture(cred) } - let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger, baggage: .init()) + let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, context: TestEnvironment.context) let provider = RotatingCredentialProvider(context: context, provider: client) XCTAssertNoThrow(_ = try provider.getCredential(on: loop, context: TestEnvironment.context).wait()) hitCount = 0 diff --git a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift index f3ab1d2d77..c0ccf95d78 100644 --- a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift @@ -14,11 +14,20 @@ @testable import AWSSDKSwiftCore import AWSTestUtils +import Instrumentation import Logging import NIOConcurrencyHelpers import XCTest class LoggingTests: XCTestCase { + override func setUp() { + super.setUp() + + // NoOpTracingInstrument does not propagate baggage + // TODO: discuss, see https://github.com/slashmo/gsoc-swift-tracing/issues/126#issuecomment-674631978 + InstrumentationSystem.bootstrap(TestTracer()) + } + func testRequestIdIncrements() { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) @@ -28,7 +37,7 @@ class LoggingTests: XCTestCase { let client = AWSClient( credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar"), httpClientProvider: .createNew, - logger: logger + context: context ) defer { XCTAssertNoThrow(try client.syncShutdown()) } let config = createServiceConfig( @@ -52,6 +61,7 @@ class LoggingTests: XCTestCase { XCTAssertNoThrow(_ = try response.wait()) XCTAssertNoThrow(_ = try response2.wait()) + // TODO: metadata is concattenated, example: "aws-request-id=1,aws-service=test,aws-operation=test1" let requestId1 = logCollection.filter(metadata: "aws-operation", with: "test1").first?.metadata["aws-request-id"] let requestId2 = logCollection.filter(metadata: "aws-operation", with: "test2").first?.metadata["aws-request-id"] XCTAssertNotNil(requestId1) @@ -61,15 +71,14 @@ class LoggingTests: XCTestCase { func testAWSRequestResponse() throws { let logCollection = LoggingCollector.Logs() - var logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) - logger.logLevel = .trace + let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) let context = TestEnvironment.contextWith(logger: logger) let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar"), httpClientProvider: .createNew, - logger: logger + context: context ) defer { XCTAssertNoThrow(try client.syncShutdown()) } let config = createServiceConfig( @@ -87,6 +96,7 @@ class LoggingTests: XCTestCase { XCTAssertNoThrow(_ = try response.wait()) let requestEntry = try XCTUnwrap(logCollection.filter(message: "AWS Request").first) XCTAssertEqual(requestEntry.level, .info) + // TODO: metadata is concattenated XCTAssertEqual(requestEntry.metadata["aws-operation"], "TestOperation") XCTAssertEqual(requestEntry.metadata["aws-service"], "test-service") let responseEntry = try XCTUnwrap(logCollection.filter(message: "AWS Response").first) @@ -104,7 +114,7 @@ class LoggingTests: XCTestCase { let client = AWSClient( credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar"), httpClientProvider: .createNew, - logger: logger + context: context ) defer { XCTAssertNoThrow(try client.syncShutdown()) } let config = createServiceConfig( @@ -132,7 +142,7 @@ class LoggingTests: XCTestCase { let client = AWSClient( credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar"), httpClientProvider: .createNew, - logger: logger + context: context ) defer { XCTAssertNoThrow(try client.syncShutdown()) } let config = createServiceConfig( diff --git a/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift b/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift index be922ba410..c163458a44 100644 --- a/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/PaginateTests.swift @@ -66,7 +66,7 @@ class PaginateTests: XCTestCase { let outputToken: Int? } - func counter(_ input: CounterInput, on eventLoop: EventLoop?, context: AWSClient.Context) -> EventLoopFuture { + func counter(_ input: CounterInput, context: AWSClient.Context, on eventLoop: EventLoop?) -> EventLoopFuture { return self.client.execute( operation: "TestOperation", path: "/", @@ -144,7 +144,7 @@ class PaginateTests: XCTestCase { let outputToken: String? } - func stringList(_ input: StringListInput, on eventLoop: EventLoop? = nil, context: AWSClient.Context) -> EventLoopFuture { + func stringList(_ input: StringListInput, context: AWSClient.Context, on eventLoop: EventLoop? = nil) -> EventLoopFuture { return self.client.execute( operation: "TestOperation", path: "/", diff --git a/Tests/AWSSDKSwiftCoreTests/TracingTests.swift b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift index 5803aa337f..33ce3f4128 100644 --- a/Tests/AWSSDKSwiftCoreTests/TracingTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/TracingTests.swift @@ -28,10 +28,10 @@ class TracingTests: XCTestCase { InstrumentationSystem.bootstrap(tracer) let awsServer = AWSTestServer(serviceProtocol: .json) - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try awsServer.stop()) - XCTAssertNoThrow(try elg.syncShutdownGracefully()) + XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let config = createServiceConfig( serviceProtocol: .json(version: "1.1"), @@ -58,8 +58,10 @@ class TracingTests: XCTestCase { // flush the tracer and check what have been recorded tracer.forceFlush() - XCTAssertGreaterThan(tracer._test_recordedSpans.count, 0) - let span = tracer._test_recordedSpans.first + let spans = tracer._test_recordedSpans + XCTAssertGreaterThan(spans.count, 0) + // TODO: extend + let span = spans.first { $0._test_operationName == "invoke" } XCTAssertNotNil(span) XCTAssertNotNil(span?._test_endTimestamp) XCTAssertEqual(span?._test_errors.count, 0) From c490c242b9a6c4606990c0cc185ce1b098b20587 Mon Sep 17 00:00:00 2001 From: Michal A Date: Tue, 8 Sep 2020 13:04:43 +0800 Subject: [PATCH 09/12] Move tracer convenience methods to separate file --- Sources/AWSSDKSwiftCore/AWSClient.swift | 94 ----------------- Sources/AWSSDKSwiftCore/Tracer+Helpers.swift | 104 +++++++++++++++++++ 2 files changed, 104 insertions(+), 94 deletions(-) create mode 100644 Sources/AWSSDKSwiftCore/Tracer+Helpers.swift diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index eded7b2ea6..4ce8939ab5 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -701,97 +701,3 @@ private extension AWSClient.Context { baggage[AWSClient.RequestKey.self] = value } } - -// TODO: extensions based on XRaySDK API, temporary, see https://github.com/slashmo/gsoc-swift-tracing/issues/125 - -private extension TracingInstrument { - func span( - named name: String, - context: BaggageContextCarrier, - ofKind kind: TracingInstrumentation.SpanKind = .internal, - at timestamp: TracingInstrumentation.Timestamp = .now(), - body: (TracingInstrumentation.Span) throws -> T - ) - rethrows -> T - { - var span = InstrumentationSystem.tracingInstrument.startSpan( - named: name, - context: context, - ofKind: kind, - at: timestamp - ) - defer { - span.end() - } - do { - return try body(span) - } catch { - span.recordError(error) - throw error - } - } - - func span( - named name: String, - context: BaggageContextCarrier, - ofKind kind: TracingInstrumentation.SpanKind = .internal, - at timestamp: TracingInstrumentation.Timestamp = .now(), - body: (TracingInstrumentation.Span) throws -> Result - ) - rethrows -> Result - { - var span = InstrumentationSystem.tracingInstrument.startSpan( - named: name, - context: context, - ofKind: kind, - at: timestamp - ) - defer { - span.end() - } - do { - let result = try body(span) - if case Result.failure(let error) = result { - span.recordError(error) - } - return result - } catch { - span.recordError(error) - throw error - } - } - - func span( - named name: String, - context: BaggageContextCarrier, - ofKind kind: TracingInstrumentation.SpanKind = .internal, - at timestamp: TracingInstrumentation.Timestamp = .now(), - body: (TracingInstrumentation.Span) -> EventLoopFuture - ) -> EventLoopFuture { - var span = InstrumentationSystem.tracingInstrument.startSpan( - named: name, - context: context, - ofKind: kind, - at: timestamp - ) - return body(span).always { result in - if case Result.failure(let error) = result { - span.recordError(error) - } - span.end() - } - } -} - -private extension EventLoopFuture { - func endSpan(_ span: TracingInstrumentation.Span) -> EventLoopFuture { - var span = span // TODO: see https://github.com/slashmo/gsoc-swift-tracing/issues/119 - whenComplete { result in - if case Result.failure(let error) = result { - span.recordError(error) - } - span.end() - } - return self - } -} diff --git a/Sources/AWSSDKSwiftCore/Tracer+Helpers.swift b/Sources/AWSSDKSwiftCore/Tracer+Helpers.swift new file mode 100644 index 0000000000..44e45658f8 --- /dev/null +++ b/Sources/AWSSDKSwiftCore/Tracer+Helpers.swift @@ -0,0 +1,104 @@ +// +// File.swift +// +// +// Created by MichaƂ A on 2020/9/8. +// + +import Baggage +import Instrumentation +import TracingInstrumentation + +// extensions based on XRaySDK API, may be redundant see https://github.com/slashmo/gsoc-swift-tracing/issues/125 + +internal extension TracingInstrument { + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) throws -> T + ) + rethrows -> T + { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + defer { + span.end() + } + do { + return try body(span) + } catch { + span.recordError(error) + throw error + } + } + + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) throws -> Result + ) + rethrows -> Result + { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + defer { + span.end() + } + do { + let result = try body(span) + if case Result.failure(let error) = result { + span.recordError(error) + } + return result + } catch { + span.recordError(error) + throw error + } + } + + func span( + named name: String, + context: BaggageContextCarrier, + ofKind kind: TracingInstrumentation.SpanKind = .internal, + at timestamp: TracingInstrumentation.Timestamp = .now(), + body: (TracingInstrumentation.Span) -> EventLoopFuture + ) -> EventLoopFuture { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: name, + context: context, + ofKind: kind, + at: timestamp + ) + return body(span).always { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() + } + } +} + +internal extension EventLoopFuture { + func endSpan(_ span: TracingInstrumentation.Span) -> EventLoopFuture { + var span = span // TODO: see https://github.com/slashmo/gsoc-swift-tracing/issues/119 + whenComplete { result in + if case Result.failure(let error) = result { + span.recordError(error) + } + span.end() + } + return self + } +} From f56fc2f52b24a19b64d5833411c610fb0a3b315b Mon Sep 17 00:00:00 2001 From: Michal A Date: Tue, 8 Sep 2020 13:08:53 +0800 Subject: [PATCH 10/12] Cosmetic --- Sources/AWSSDKSwiftCore/AWSClient.swift | 24 ------------------- Tests/AWSSDKSwiftCoreTests/LoggingTests.swift | 3 +-- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 4ce8939ab5..4d6d6b363b 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -646,34 +646,10 @@ extension AWSClient.Context { // MARK: RequestMetadata -// extension Logger { -// func attachingRequestId(_ id: Int, operation: String, service: String) -> Logger { -// var logger = self -// logger[metadataKey: "aws-service"] = .string(service) -// logger[metadataKey: "aws-operation"] = .string(operation) -// logger[metadataKey: "aws-request-id"] = "\(id)" -// return logger -// } -// } - private extension AWSClient { struct RequestMetadata: CustomStringConvertible { static let globalRequestID = NIOAtomic.makeAtomic(value: 0) -// static func create( -// operation: String, -// serviceConfig: AWSServiceConfig, -// requestId: Int = Self.globalRequestID.add(1) -// ) -> RequestMetadata { -// RequestMetadata(requestId: requestId, service: serviceConfig.service, operation: operation) -// } - -// static func create(serviceConfig: AWSServiceConfig) -> (String) -> RequestMetadata { -// { operation in -// Self.create(operation: operation, serviceConfig: serviceConfig) -// } -// } - var requestId: Int var service: String var operation: String diff --git a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift index c0ccf95d78..7d2c727151 100644 --- a/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/LoggingTests.swift @@ -23,8 +23,7 @@ class LoggingTests: XCTestCase { override func setUp() { super.setUp() - // NoOpTracingInstrument does not propagate baggage - // TODO: discuss, see https://github.com/slashmo/gsoc-swift-tracing/issues/126#issuecomment-674631978 + // TODO: will be fixed with https://github.com/slashmo/gsoc-swift-tracing/issues/138 InstrumentationSystem.bootstrap(TestTracer()) } From 7d47822cb4edf17b2e9f61ebb79dff3b7318dbb1 Mon Sep 17 00:00:00 2001 From: Michal A Date: Tue, 8 Sep 2020 13:14:50 +0800 Subject: [PATCH 11/12] Fix: createRequest never needs a signer, see #349 --- Sources/AWSSDKSwiftCore/AWSClient.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 4d6d6b363b..36f30dd80a 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -264,7 +264,7 @@ extension AWSClient { ) -> EventLoopFuture { return execute( operation: operationName, - createRequest: { _ in + createRequest: { try AWSRequest( operation: operationName, path: path, @@ -304,7 +304,7 @@ extension AWSClient { ) -> EventLoopFuture { return execute( operation: operationName, - createRequest: { _ in + createRequest: { try AWSRequest( operation: operationName, path: path, @@ -343,7 +343,7 @@ extension AWSClient { ) -> EventLoopFuture { return execute( operation: operationName, - createRequest: { _ in + createRequest: { try AWSRequest( operation: operationName, path: path, @@ -384,7 +384,7 @@ extension AWSClient { ) -> EventLoopFuture { return execute( operation: operationName, - createRequest: { _ in + createRequest: { try AWSRequest( operation: operationName, path: path, @@ -427,7 +427,7 @@ extension AWSClient { ) -> EventLoopFuture { return execute( operation: operationName, - createRequest: { _ in + createRequest: { try AWSRequest( operation: operationName, path: path, @@ -451,7 +451,7 @@ extension AWSClient { /// internal version of execute internal func execute( operation operationName: String, - createRequest: @escaping (AWSSigner) throws -> AWSRequest, + createRequest: @escaping () throws -> AWSRequest, execute: @escaping (AWSHTTPRequest, EventLoop, AWSClient.Context) -> EventLoopFuture, processResponse: @escaping (AWSHTTPResponse) throws -> Output, config: AWSServiceConfig, @@ -472,9 +472,7 @@ extension AWSClient { } .flatMapThrowing { credential in let signer = AWSSigner(credentials: credential, name: config.signingName, region: config.region.rawValue) - let awsRequest = try InstrumentationSystem.tracingInstrument.span(named: "createRequest", context: context) { _ in - try createRequest(signer) - } + let awsRequest = try createRequest() return try awsRequest .applyMiddlewares(config.middlewares + self.middlewares) .createHTTPRequest(signer: signer) From a35c1938e34493997d5d37a2697516230abd774e Mon Sep 17 00:00:00 2001 From: Michal A Date: Tue, 8 Sep 2020 13:29:58 +0800 Subject: [PATCH 12/12] Instrument signing and middleware --- Sources/AWSSDKSwiftCore/AWSClient.swift | 7 +++++-- .../AWSSDKSwiftCore/Message/AWSRequest.swift | 20 ++++++++++++++----- .../AWSRequestTests.swift | 6 +++--- .../PerformanceTests.swift | 8 ++++---- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Sources/AWSSDKSwiftCore/AWSClient.swift b/Sources/AWSSDKSwiftCore/AWSClient.swift index 36f30dd80a..707f85d251 100644 --- a/Sources/AWSSDKSwiftCore/AWSClient.swift +++ b/Sources/AWSSDKSwiftCore/AWSClient.swift @@ -474,8 +474,11 @@ extension AWSClient { let signer = AWSSigner(credentials: credential, name: config.signingName, region: config.region.rawValue) let awsRequest = try createRequest() return try awsRequest - .applyMiddlewares(config.middlewares + self.middlewares) - .createHTTPRequest(signer: signer) + .applyMiddlewares( + config.middlewares + self.middlewares, + context: context.with(baggage: span.context) + ) + .createHTTPRequest(signer: signer, context: context.with(baggage: span.context)) }.flatMap { request in return self.invoke(with: config, context: context) { context in execute(request, eventLoop, context) diff --git a/Sources/AWSSDKSwiftCore/Message/AWSRequest.swift b/Sources/AWSSDKSwiftCore/Message/AWSRequest.swift index d857260251..04c21dbb5a 100644 --- a/Sources/AWSSDKSwiftCore/Message/AWSRequest.swift +++ b/Sources/AWSSDKSwiftCore/Message/AWSRequest.swift @@ -18,8 +18,10 @@ import struct Foundation.Data import struct Foundation.Date import struct Foundation.URL import struct Foundation.URLComponents +import Instrumentation import NIO import NIOHTTP1 +import TracingInstrumentation /// Object encapsulating all the information needed to generate a raw HTTP request to AWS public struct AWSRequest { @@ -33,13 +35,13 @@ public struct AWSRequest { /// Create HTTP Client request from AWSRequest. /// If the signer's credentials are available the request will be sigend. Otherweise defaults to an unsinged request - func createHTTPRequest(signer: AWSSigner) -> AWSHTTPRequest { + func createHTTPRequest(signer: AWSSigner, context: AWSClient.Context) -> AWSHTTPRequest { // if credentials are empty don't sign request if signer.credentials.isEmpty() { return self.toHTTPRequest() } - return self.toHTTPRequestWithSignedHeader(signer: signer) + return self.toHTTPRequestWithSignedHeader(signer: signer, context: context) } /// Create HTTP Client request from AWSRequest @@ -48,7 +50,12 @@ public struct AWSRequest { } /// Create HTTP Client request with signed headers from AWSRequest - func toHTTPRequestWithSignedHeader(signer: AWSSigner) -> AWSHTTPRequest { + func toHTTPRequestWithSignedHeader(signer: AWSSigner, context: AWSClient.Context) -> AWSHTTPRequest { + var span = InstrumentationSystem.tracingInstrument.startSpan( + named: "toHTTPRequestWithSignedHeader", + context: context + ) + span.attributes["signer"] = .string(signer.name) let payload = self.body.asPayload() let bodyDataForSigning: AWSSigner.BodyData? switch payload.payload { @@ -77,15 +84,18 @@ public struct AWSRequest { bodyDataForSigning = nil } let signedHeaders = signer.signHeaders(url: url, method: httpMethod, headers: httpHeaders, body: bodyDataForSigning, date: Date()) + span.end() return AWSHTTPRequest(url: url, method: httpMethod, headers: signedHeaders, body: payload) } // return new request with middleware applied - func applyMiddlewares(_ middlewares: [AWSServiceMiddleware]) throws -> AWSRequest { + func applyMiddlewares(_ middlewares: [AWSServiceMiddleware], context: AWSClient.Context) throws -> AWSRequest { var awsRequest = self // apply middleware to request for middleware in middlewares { - awsRequest = try middleware.chain(request: awsRequest) + awsRequest = try InstrumentationSystem.tracingInstrument.span(named: "\(middleware)", context: context) { _ in + try middleware.chain(request: awsRequest) + } } return awsRequest } diff --git a/Tests/AWSSDKSwiftCoreTests/AWSRequestTests.swift b/Tests/AWSSDKSwiftCoreTests/AWSRequestTests.swift index f3a0517453..3845c2a111 100644 --- a/Tests/AWSSDKSwiftCoreTests/AWSRequestTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/AWSRequestTests.swift @@ -92,7 +92,7 @@ class AWSRequestTests: XCTestCase { region: config.region.rawValue ) - let signedRequest = awsRequest?.createHTTPRequest(signer: signer) + let signedRequest = awsRequest?.createHTTPRequest(signer: signer, context: TestEnvironment.context) XCTAssertNotNil(signedRequest) XCTAssertEqual(signedRequest?.method, HTTPMethod.POST) XCTAssertEqual(signedRequest?.headers["Host"].first, "kinesis.us-east-1.amazonaws.com") @@ -118,7 +118,7 @@ class AWSRequestTests: XCTestCase { region: config.region.rawValue ) - let request = awsRequest?.createHTTPRequest(signer: signer) + let request = awsRequest?.createHTTPRequest(signer: signer, context: TestEnvironment.context) XCTAssertNil(request?.headers["Authorization"].first) } @@ -143,7 +143,7 @@ class AWSRequestTests: XCTestCase { configuration: config )) - let request = awsRequest?.createHTTPRequest(signer: signer) + let request = awsRequest?.createHTTPRequest(signer: signer, context: TestEnvironment.context) XCTAssertNotNil(request?.headers["Authorization"].first) } } diff --git a/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift b/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift index cc62047e97..32ad31bc72 100644 --- a/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift +++ b/Tests/AWSSDKSwiftCoreTests/PerformanceTests.swift @@ -219,7 +219,7 @@ class PerformanceTests: XCTestCase { ) measure { for _ in 0..<1000 { - _ = request.createHTTPRequest(signer: signer) + _ = request.createHTTPRequest(signer: signer, context: TestEnvironment.context) } } } @@ -236,12 +236,12 @@ class PerformanceTests: XCTestCase { path: "/", httpMethod: .GET, configuration: config - ).applyMiddlewares(config.middlewares + client.middlewares) + ).applyMiddlewares(config.middlewares + client.middlewares, context: TestEnvironment.context) let signer = try! client.createSigner(serviceConfig: config, context: TestEnvironment.context).wait() measure { for _ in 0..<1000 { - _ = awsRequest.createHTTPRequest(signer: signer) + _ = awsRequest.createHTTPRequest(signer: signer, context: TestEnvironment.context) } } } @@ -264,7 +264,7 @@ class PerformanceTests: XCTestCase { ) measure { for _ in 0..<1000 { - _ = awsRequest.createHTTPRequest(signer: signer) + _ = awsRequest.createHTTPRequest(signer: signer, context: TestEnvironment.context) } } }