diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift index 0d6a285b..f7e1b95a 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift @@ -276,7 +276,7 @@ private enum LocalLambda { response.headers = [ (AmazonHeaders.requestID, self.requestID), (AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:\(Int16.random(in: Int16.min ... Int16.max)):function:custom-runtime"), - (AmazonHeaders.traceID, "Root=\(Int16.random(in: Int16.min ... Int16.max));Parent=\(Int16.random(in: Int16.min ... Int16.max));Sampled=1"), + (AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"), (AmazonHeaders.deadline, "\(DispatchWallTime.distantFuture.millisSinceEpoch)"), ] return response @@ -295,4 +295,5 @@ private enum LocalLambda { case cantBind } } + #endif diff --git a/Sources/AWSLambdaRuntimeCore/Utils.swift b/Sources/AWSLambdaRuntimeCore/Utils.swift index ae5db50f..5e7ffa6e 100644 --- a/Sources/AWSLambdaRuntimeCore/Utils.swift +++ b/Sources/AWSLambdaRuntimeCore/Utils.swift @@ -104,3 +104,29 @@ extension String { bytes.append(UInt8(ascii: "\"")) } } + +extension AmazonHeaders { + /// Generates (X-Ray) trace ID. + /// # Trace ID Format + /// A `trace_id` consists of three numbers separated by hyphens. + /// For example, `1-58406520-a006649127e371903a2de979`. This includes: + /// - The version number, that is, 1. + /// - The time of the original request, in Unix epoch time, in **8 hexadecimal digits**. + /// For example, 10:00AM December 1st, 2016 PST in epoch time is `1480615200` seconds, or `58406520` in hexadecimal digits. + /// - A 96-bit identifier for the trace, globally unique, in **24 hexadecimal digits**. + /// # References + /// - [Generating trace IDs](https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids) + /// - [Tracing header](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader) + internal static func generateXRayTraceID() -> String { + // The version number, that is, 1. + let version: UInt = 1 + // The time of the original request, in Unix epoch time, in 8 hexadecimal digits. + let now = UInt32(DispatchWallTime.now().millisSinceEpoch / 1000) + let dateValue = String(now, radix: 16, uppercase: false) + let datePadding = String(repeating: "0", count: max(0, 8 - dateValue.count)) + // A 96-bit identifier for the trace, globally unique, in 24 hexadecimal digits. + let identifier = String(UInt64.random(in: UInt64.min ... UInt64.max) | 1 << 63, radix: 16, uppercase: false) + + String(UInt32.random(in: UInt32.min ... UInt32.max) | 1 << 31, radix: 16, uppercase: false) + return "\(version)-\(datePadding)\(dateValue)-\(identifier)" + } +} diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift index 728672e6..94c8ac62 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift @@ -257,7 +257,7 @@ class LambdaRuntimeClientTest: XCTestCase { (AmazonHeaders.requestID, "test"), (AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).millisSinceEpoch)), (AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"), - (AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"), + (AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"), ]) var inv: Lambda.Invocation? XCTAssertNoThrow(inv = try Lambda.Invocation(headers: header)) @@ -297,7 +297,7 @@ class LambdaRuntimeClientTest: XCTestCase { (AmazonHeaders.requestID, "test"), (AmazonHeaders.deadline, String(Date(timeIntervalSinceNow: 60).millisSinceEpoch)), (AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"), - (AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"), + (AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"), ]) var inv: Lambda.Invocation? XCTAssertNoThrow(inv = try Lambda.Invocation(headers: header)) diff --git a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift index b2f671a6..9708ccc1 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift @@ -144,7 +144,7 @@ internal final class HTTPHandler: ChannelInboundHandler { responseHeaders = [ (AmazonHeaders.requestID, requestId), (AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"), - (AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"), + (AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"), (AmazonHeaders.deadline, String(deadline)), ] case .failure(let error): diff --git a/Tests/AWSLambdaRuntimeCoreTests/UtilsTest.swift b/Tests/AWSLambdaRuntimeCoreTests/UtilsTest.swift new file mode 100644 index 00000000..c5fc4ab5 --- /dev/null +++ b/Tests/AWSLambdaRuntimeCoreTests/UtilsTest.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import AWSLambdaRuntimeCore +import XCTest + +class UtilsTest: XCTestCase { + func testGenerateXRayTraceID() { + // the time and identifier should be in hexadecimal digits + let invalidCharacters = CharacterSet(charactersIn: "abcdef0123456789").inverted + let numTests = 1000 + var values = Set() + for _ in 0 ..< numTests { + // check the format, see https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids) + let traceId = AmazonHeaders.generateXRayTraceID() + let segments = traceId.split(separator: "-") + XCTAssertEqual(3, segments.count) + XCTAssertEqual("1", segments[0]) + XCTAssertEqual(8, segments[1].count) + XCTAssertNil(segments[1].rangeOfCharacter(from: invalidCharacters)) + XCTAssertEqual(24, segments[2].count) + XCTAssertNil(segments[2].rangeOfCharacter(from: invalidCharacters)) + values.insert(traceId) + } + // check that the generated values are different + XCTAssertEqual(values.count, numTests) + } +}