From 05753aa19f450fb197331da13a8160ee5c9582ba Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 16 Sep 2020 13:28:02 +0900 Subject: [PATCH 1/5] adopts baggage context; readonly context --- Package.swift | 3 + .../AWSLambdaRuntimeCore/LambdaContext.swift | 75 ++++++++++++++++--- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Package.swift b/Package.swift index 0e5823f6..2622b9f0 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")), + // .package(url: "https://github.com/slashmo/gsoc-swift-baggage-context.git", .branch("main")), + .package(name: "swift-context", path: "/Users/ktoso/code/gsoc-swift-baggage-context"), .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")), ], targets: [ @@ -27,6 +29,7 @@ let package = Package( ]), .target(name: "AWSLambdaRuntimeCore", dependencies: [ .product(name: "Logging", package: "swift-log"), + .product(name: "BaggageContext", package: "swift-context"), .product(name: "Backtrace", package: "swift-backtrace"), .product(name: "NIOHTTP1", package: "swift-nio"), ]), diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index ab30dd7b..d6d8f606 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -15,6 +15,7 @@ import Dispatch import Logging import NIO +import BaggageContext // MARK: - InitializationContext @@ -49,12 +50,21 @@ extension Lambda { extension Lambda { /// Lambda runtime context. /// The Lambda runtime generates and passes the `Context` to the Lambda handler as an argument. - public final class Context: CustomDebugStringConvertible { + public final class Context: BaggageContext.Context, CustomDebugStringConvertible { + + /// Contains contextual metadata such as request and trace identifiers, along with other information which may + /// be carried throughout asynchronous and cross-node boundaries (e.g. through HTTPClient calls). + public let baggage: Baggage + /// The request ID, which identifies the request that triggered the function invocation. - public let requestID: String + public var requestID: String { + self.baggage.lambdaRequestID + } /// The AWS X-Ray tracing header. - public let traceID: String + public var traceID: String { + self.baggage.lambdaTraceID + } /// The ARN of the Lambda function, version, or alias that's specified in the invocation. public let invokedFunctionARN: String @@ -68,10 +78,13 @@ extension Lambda { /// For invocations from the AWS Mobile SDK, data about the client application and device. public let clientContext: String? - /// `Logger` to log with + /// `Logger` to log with, it is automatically populated with `baggage` information (such as `traceID` and `requestID`). /// /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. - public let logger: Logger + public var logger: Logger { + self._logger.with(self.baggage) + } + private var _logger: Logger /// The `EventLoop` the Lambda is executed on. Use this to schedule work with. /// This is useful when implementing the `EventLoopLambdaHandler` protocol. @@ -93,8 +106,10 @@ extension Lambda { logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator) { - self.requestID = requestID - self.traceID = traceID + var baggage = Baggage.background + baggage.lambdaRequestID = requestID + baggage.lambdaTraceID = traceID + self.baggage = baggage self.invokedFunctionARN = invokedFunctionARN self.cognitoIdentity = cognitoIdentity self.clientContext = clientContext @@ -102,11 +117,7 @@ extension Lambda { // utility self.eventLoop = eventLoop self.allocator = allocator - // mutate logger with context - var logger = logger - logger[metadataKey: "awsRequestID"] = .string(requestID) - logger[metadataKey: "awsTraceID"] = .string(traceID) - self.logger = logger + self._logger = logger } public func getRemainingTime() -> TimeAmount { @@ -146,3 +157,43 @@ extension Lambda { } } } + +// MARK: - Baggage Items + +extension Baggage { + + // MARK: - Baggage: RequestID + + enum LambdaRequestIDKey: Key { + typealias Value = String + static var name: String? { AmazonHeaders.requestID } + } + + /// The request ID, which identifies the request that triggered the function invocation. + public internal(set) var lambdaRequestID: String { + get { + return self[LambdaRequestIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing + } + set { + self[LambdaRequestIDKey.self] = newValue + } + } + + // MARK: - Baggage: TraceID + + enum LambdaTraceIDKey: Key { + typealias Value = String + static var name: String? { AmazonHeaders.traceID } + } + + /// The AWS X-Ray tracing header. + public internal(set) var lambdaTraceID: String { + get { + return self[LambdaTraceIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing + } + set { + self[LambdaTraceIDKey.self] = newValue + } + } + +} From 9ae0cb7fff11fe556427cd7bc6d6be0a6ddfe0f6 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 16 Sep 2020 13:40:48 +0900 Subject: [PATCH 2/5] writable, convenient to use for users context --- .../AWSLambdaRuntimeCore/LambdaContext.swift | 120 ++++++++++++++---- .../AWSLambdaRuntimeCore/LambdaRunner.swift | 2 +- 2 files changed, 94 insertions(+), 28 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index d6d8f606..020626df 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// +import BaggageContext import Dispatch import Logging import NIO -import BaggageContext // MARK: - InitializationContext @@ -50,52 +50,118 @@ extension Lambda { extension Lambda { /// Lambda runtime context. /// The Lambda runtime generates and passes the `Context` to the Lambda handler as an argument. - public final class Context: BaggageContext.Context, CustomDebugStringConvertible { + public struct Context: BaggageContext.Context, CustomDebugStringConvertible { + private var storage: _Storage + + final class _Storage { + var baggage: Baggage + + let invokedFunctionARN: String + let deadline: DispatchWallTime + let cognitoIdentity: String? + let clientContext: String? + + var _logger: Logger + + let eventLoop: EventLoop + let allocator: ByteBufferAllocator + + init( + baggage: Baggage, + invokedFunctionARN: String, + deadline: DispatchWallTime, + cognitoIdentity: String?, + clientContext: String?, + _logger: Logger, + eventLoop: EventLoop, + allocator: ByteBufferAllocator + ) { + self.baggage = baggage + self.invokedFunctionARN = invokedFunctionARN + self.deadline = deadline + self.cognitoIdentity = cognitoIdentity + self.clientContext = clientContext + self._logger = _logger + self.eventLoop = eventLoop + self.allocator = allocator + } + } /// Contains contextual metadata such as request and trace identifiers, along with other information which may /// be carried throughout asynchronous and cross-node boundaries (e.g. through HTTPClient calls). - public let baggage: Baggage + public var baggage: Baggage { + get { + self.storage.baggage + } + set { + if isKnownUniquelyReferenced(&self.storage) { + self.storage.baggage = newValue + } else { + self.storage = _Storage( + baggage: newValue, + invokedFunctionARN: self.storage.invokedFunctionARN, + deadline: self.storage.deadline, + cognitoIdentity: self.storage.cognitoIdentity, + clientContext: self.storage.clientContext, + _logger: self.storage._logger, + eventLoop: self.storage.eventLoop, + allocator: self.storage.allocator + ) + } + } + } /// The request ID, which identifies the request that triggered the function invocation. public var requestID: String { - self.baggage.lambdaRequestID + self.storage.baggage.lambdaRequestID } /// The AWS X-Ray tracing header. public var traceID: String { - self.baggage.lambdaTraceID + self.storage.baggage.lambdaTraceID } /// The ARN of the Lambda function, version, or alias that's specified in the invocation. - public let invokedFunctionARN: String + public var invokedFunctionARN: String { + self.storage.invokedFunctionARN + } /// The timestamp that the function times out - public let deadline: DispatchWallTime + public var deadline: DispatchWallTime { + self.storage.deadline + } /// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. - public let cognitoIdentity: String? + public var cognitoIdentity: String? { + self.storage.cognitoIdentity + } /// For invocations from the AWS Mobile SDK, data about the client application and device. - public let clientContext: String? + public var clientContext: String? { + self.storage.clientContext + } /// `Logger` to log with, it is automatically populated with `baggage` information (such as `traceID` and `requestID`). /// /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. public var logger: Logger { - self._logger.with(self.baggage) + self.storage._logger.with(self.baggage) } - private var _logger: Logger /// The `EventLoop` the Lambda is executed on. Use this to schedule work with. /// This is useful when implementing the `EventLoopLambdaHandler` protocol. /// /// - note: The `EventLoop` is shared with the Lambda runtime engine and should be handled with extra care. /// Most importantly the `EventLoop` must never be blocked. - public let eventLoop: EventLoop + public var eventLoop: EventLoop { + self.storage.eventLoop + } /// `ByteBufferAllocator` to allocate `ByteBuffer` /// This is useful when implementing `EventLoopLambdaHandler` - public let allocator: ByteBufferAllocator + public var allocator: ByteBufferAllocator { + self.storage.allocator + } internal init(requestID: String, traceID: String, @@ -109,15 +175,17 @@ extension Lambda { var baggage = Baggage.background baggage.lambdaRequestID = requestID baggage.lambdaTraceID = traceID - self.baggage = baggage - self.invokedFunctionARN = invokedFunctionARN - self.cognitoIdentity = cognitoIdentity - self.clientContext = clientContext - self.deadline = deadline - // utility - self.eventLoop = eventLoop - self.allocator = allocator - self._logger = logger + self.storage = _Storage( + baggage: baggage, + invokedFunctionARN: invokedFunctionARN, + deadline: deadline, + cognitoIdentity: cognitoIdentity, + clientContext: clientContext, + // utility + _logger: logger, + eventLoop: eventLoop, + allocator: allocator + ) } public func getRemainingTime() -> TimeAmount { @@ -161,7 +229,6 @@ extension Lambda { // MARK: - Baggage Items extension Baggage { - // MARK: - Baggage: RequestID enum LambdaRequestIDKey: Key { @@ -172,9 +239,9 @@ extension Baggage { /// The request ID, which identifies the request that triggered the function invocation. public internal(set) var lambdaRequestID: String { get { - return self[LambdaRequestIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing + self[LambdaRequestIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing } - set { + set { self[LambdaRequestIDKey.self] = newValue } } @@ -189,11 +256,10 @@ extension Baggage { /// The AWS X-Ray tracing header. public internal(set) var lambdaTraceID: String { get { - return self[LambdaTraceIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing + self[LambdaTraceIDKey.self]! // !-safe, the runtime guarantees to always set an identifier, even in testing } set { self[LambdaTraceIDKey.self] = newValue } } - } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift index 8fc22de3..f24f6909 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift @@ -101,7 +101,7 @@ extension Lambda { } private extension Lambda.Context { - convenience init(logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator, invocation: Lambda.Invocation) { + init(logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator, invocation: Lambda.Invocation) { self.init(requestID: invocation.requestID, traceID: invocation.traceID, invokedFunctionARN: invocation.invokedFunctionARN, From 9ecf61a323a16a139a9865d1598d55ec1f12848a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Wed, 16 Sep 2020 14:01:18 +0900 Subject: [PATCH 3/5] link to real deps --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 2622b9f0..1427da26 100644 --- a/Package.swift +++ b/Package.swift @@ -17,8 +17,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")), - // .package(url: "https://github.com/slashmo/gsoc-swift-baggage-context.git", .branch("main")), - .package(name: "swift-context", path: "/Users/ktoso/code/gsoc-swift-baggage-context"), + .package(name: "swift-context", url: "https://github.com/ktoso/gsoc-swift-baggage-context.git", .branch("simple-is-good-proposal")), // TODO: use main once merged + // .package(name: "swift-context", path: "/Users/ktoso/code/gsoc-swift-baggage-context"), // TODO: remove development dep .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")), ], targets: [ From 68b230a75dc11ffdcfb827f3ceac7098ee888718 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 17 Sep 2020 09:12:40 +0900 Subject: [PATCH 4/5] review feedback --- .../AWSLambdaRuntimeCore/LambdaContext.swift | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index 020626df..10dc6a7b 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -51,9 +51,10 @@ extension Lambda { /// Lambda runtime context. /// The Lambda runtime generates and passes the `Context` to the Lambda handler as an argument. public struct Context: BaggageContext.Context, CustomDebugStringConvertible { - private var storage: _Storage + /// Used to store all contents of the context and implement CoW semantics for it. + private var storage: Storage - final class _Storage { + final class Storage { var baggage: Baggage let invokedFunctionARN: String @@ -61,6 +62,10 @@ extension Lambda { let cognitoIdentity: String? let clientContext: String? + // Implementation note: This logger is the "user provided logger" that we will log to when `context.logger` is used. + // However, we don't use it directly but through the `_logger.with(baggage)` pattern, in order to fulfil the `Context`'s + // contract about how the `logger` property must be implemented -- i.e. by always containing the latest baggage. + // This makes all log statements automatically include any baggage that is meaningful to include in logging. var _logger: Logger let eventLoop: EventLoop @@ -97,7 +102,7 @@ extension Lambda { if isKnownUniquelyReferenced(&self.storage) { self.storage.baggage = newValue } else { - self.storage = _Storage( + self.storage = Storage( baggage: newValue, invokedFunctionARN: self.storage.invokedFunctionARN, deadline: self.storage.deadline, @@ -143,9 +148,14 @@ extension Lambda { /// `Logger` to log with, it is automatically populated with `baggage` information (such as `traceID` and `requestID`). /// - /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. + /// - note: The default `Logger.LogLevel` can be configured using the `LOG_LEVEL` environment variable. public var logger: Logger { - self.storage._logger.with(self.baggage) + get { + self.storage._logger.with(self.baggage) + } + set { + self.storage._logger = newValue + } } /// The `EventLoop` the Lambda is executed on. Use this to schedule work with. @@ -175,7 +185,7 @@ extension Lambda { var baggage = Baggage.background baggage.lambdaRequestID = requestID baggage.lambdaTraceID = traceID - self.storage = _Storage( + self.storage = Storage( baggage: baggage, invokedFunctionARN: invokedFunctionARN, deadline: deadline, From e4bfc22067b5868ed43b553105de170433a6101d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Thu, 24 Sep 2020 13:06:08 +0900 Subject: [PATCH 5/5] update to latest eager metadata setting baggage/logger --- Package.swift | 1 - .../AWSLambdaRuntimeCore/LambdaContext.swift | 25 ++++++++------ .../Lambda+CodeableTest.swift | 33 ++++++++++++++++++- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/Package.swift b/Package.swift index 1427da26..9c0bfedd 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,6 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")), .package(name: "swift-context", url: "https://github.com/ktoso/gsoc-swift-baggage-context.git", .branch("simple-is-good-proposal")), // TODO: use main once merged - // .package(name: "swift-context", path: "/Users/ktoso/code/gsoc-swift-baggage-context"), // TODO: remove development dep .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")), ], targets: [ diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index 10dc6a7b..738abe90 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -63,9 +63,7 @@ extension Lambda { let clientContext: String? // Implementation note: This logger is the "user provided logger" that we will log to when `context.logger` is used. - // However, we don't use it directly but through the `_logger.with(baggage)` pattern, in order to fulfil the `Context`'s - // contract about how the `logger` property must be implemented -- i.e. by always containing the latest baggage. - // This makes all log statements automatically include any baggage that is meaningful to include in logging. + // It must be updated with the latest metadata whenever the `baggage` changes. var _logger: Logger let eventLoop: EventLoop @@ -77,7 +75,7 @@ extension Lambda { deadline: DispatchWallTime, cognitoIdentity: String?, clientContext: String?, - _logger: Logger, + logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator ) { @@ -86,7 +84,7 @@ extension Lambda { self.deadline = deadline self.cognitoIdentity = cognitoIdentity self.clientContext = clientContext - self._logger = _logger + self._logger = logger self.eventLoop = eventLoop self.allocator = allocator } @@ -100,15 +98,18 @@ extension Lambda { } set { if isKnownUniquelyReferenced(&self.storage) { + self.storage._logger.updateMetadata(previous: self.storage.baggage, latest: newValue) self.storage.baggage = newValue } else { + var logger = self.storage._logger + logger.updateMetadata(previous: self.storage.baggage, latest: newValue) self.storage = Storage( baggage: newValue, invokedFunctionARN: self.storage.invokedFunctionARN, deadline: self.storage.deadline, cognitoIdentity: self.storage.cognitoIdentity, clientContext: self.storage.clientContext, - _logger: self.storage._logger, + logger: self.storage._logger, eventLoop: self.storage.eventLoop, allocator: self.storage.allocator ) @@ -151,10 +152,13 @@ extension Lambda { /// - note: The default `Logger.LogLevel` can be configured using the `LOG_LEVEL` environment variable. public var logger: Logger { get { - self.storage._logger.with(self.baggage) + self.storage._logger } set { - self.storage._logger = newValue + if isKnownUniquelyReferenced(&self.storage) { + self.storage._logger = newValue + self.storage._logger.updateMetadata(previous: .topLevel, latest: self.storage.baggage) + } } } @@ -182,7 +186,7 @@ extension Lambda { logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator) { - var baggage = Baggage.background + var baggage = Baggage.topLevel baggage.lambdaRequestID = requestID baggage.lambdaTraceID = traceID self.storage = Storage( @@ -192,7 +196,7 @@ extension Lambda { cognitoIdentity: cognitoIdentity, clientContext: clientContext, // utility - _logger: logger, + logger: logger, eventLoop: eventLoop, allocator: allocator ) @@ -239,6 +243,7 @@ extension Lambda { // MARK: - Baggage Items extension Baggage { + // MARK: - Baggage: RequestID enum LambdaRequestIDKey: Key { diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift index 9aa3f72a..a6676014 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift @@ -18,6 +18,7 @@ import Logging import NIO import NIOFoundationCompat import XCTest +import Baggage class CodableLambdaTest: XCTestCase { var eventLoopGroup: EventLoopGroup! @@ -63,7 +64,22 @@ class CodableLambdaTest: XCTestCase { XCTAssertEqual(response?.requestId, request.requestId) } - // convencience method + func testBaggageContextInteractions() { + var context = newContext() + context.baggage.testValue = "hello" + + context.logger.info("Test") + XCTAssertEqual(context.baggage.testValue, "hello") + XCTAssertEqual(context.baggage.lambdaTraceID, "abc123") + XCTAssertEqual(context.baggage.lambdaTraceID, context.traceID) + XCTAssertEqual(context.baggage.lambdaRequestID, context.requestID) + + XCTAssertEqual("\(context.logger[metadataKey: "LambdaTraceIDKey"]!)", context.traceID) + XCTAssertEqual("\(context.logger[metadataKey: "LambdaRequestIDKey"]!)", context.requestID) + XCTAssertEqual("\(context.logger[metadataKey: "TestKey"]!)", context.baggage.testValue) + } + + // convenience method func newContext() -> Lambda.Context { Lambda.Context(requestID: UUID().uuidString, traceID: "abc123", @@ -90,3 +106,18 @@ private struct Response: Codable, Equatable { self.requestId = requestId } } + +extension Baggage { + enum TestKey: Baggage.Key { + typealias Value = String + + } + var testValue: String? { + get { + return self[TestKey.self] + } + set { + self[TestKey.self] = newValue + } + } +}