Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create XRayInstrument #16

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
0fd427c
Chore: bump AnyCodable to 0.3.0
Jul 20, 2020
50c5da1
feat: create XRayInstrument
Jul 20, 2020
a27628d
Merge remote-tracking branch 'origin/master' into feature/instrument
Jul 20, 2020
b26c5f0
feat: expose Segment name
Jul 20, 2020
3ae1d29
refactor: rename TraceHeader to TraceContext, expose in Segment
Jul 21, 2020
ac2d5ac
feat: expose annotation and metadata values, allow to remove them
Jul 21, 2020
38f9a63
refactor: expose segment id
Jul 21, 2020
3d4d004
refactor: expose segment startTime and endTime
Jul 21, 2020
0f5d415
Merge branch 'develop' into feature/instrument
Jul 21, 2020
29ff160
Merge remote-tracking branch 'origin/master' into feature/instrument
Jul 21, 2020
6c1d3c9
WIP
Jul 21, 2020
80ed11f
WIP
Jul 23, 2020
e43ed86
Fixed XRayREcorder dependencies
Jul 23, 2020
e50851c
Do not limit extracting and injecting to HTTPHeaders, fixed testInjec…
Jul 23, 2020
a807dcc
Move dependency on HTTP injector/extractor to AWSXRayInstrumentTests
Jul 23, 2020
1a35ffa
Move dependency on HTTP injector/extractor to AWSXRayInstrumentTests
Jul 23, 2020
d749d4c
Merge branch 'develop' into feature/instrument
Jul 23, 2020
ec93186
Merge branch 'master' into feature/instrument
Jul 23, 2020
cf7d6fe
XRayInstrument - set attributes and status, append links and events
Jul 23, 2020
c5e7d2a
Generate TraceId in XRayInstrument tests
Jul 23, 2020
94c313e
feat: create AWSXRayInstrumentExample
Jul 23, 2020
e8aeb31
Create instrumented HTTP client
Jul 24, 2020
c7a1bbd
Fixed setting HTTP attributes
Jul 24, 2020
d8a597d
refactor: store events and links as string description (for now?)
Jul 24, 2020
b12b94b
fix: fix serialization of "appended" metadata
Jul 24, 2020
8e606f2
Merge branch 'develop' into feature/instrument
Jul 24, 2020
8bcb386
fix: testAppendingMetadata
Jul 24, 2020
393963a
Merge branch 'develop' into feature/instrument
Jul 24, 2020
924d164
Merge branch 'master' into feature/instrument
Jul 24, 2020
7a1e106
docs: add license headers
Jul 24, 2020
4ef2173
Merge branch 'master' into feature/instrument
Jul 25, 2020
d27ba09
Pull changes from swift-tracing
Jul 25, 2020
868aad2
Update XRayInstrument example to generate trace and query instrumente…
Jul 25, 2020
5b2d789
Store links and events separately
Jul 25, 2020
189a3aa
Merge branch 'master' into feature/instrument
Aug 1, 2020
0c634c7
Pull changes from swift-tracing
Aug 1, 2020
1af7dee
Merge branch 'master' into feature/instrument
Aug 1, 2020
d6cdd15
Fix compilation of AWSXRayInstrumentExample
Aug 1, 2020
93e39f7
Merge branch 'emitter' into feature/instrument
pokryfka Aug 4, 2020
de28605
Merge branch 'master' into feature/instrument
pokryfka Aug 15, 2020
683ff26
chore: pull changes from swift-tracing
pokryfka Aug 15, 2020
97c63f9
Merge branch 'master' into feature/instrument
pokryfka Aug 15, 2020
bb72833
test: create bootstrapping tests
pokryfka Aug 15, 2020
e317cdd
fix: expose span name
pokryfka Aug 15, 2020
0ac2849
test: test creating span attributes
pokryfka Aug 15, 2020
096b12e
test: update span tests
pokryfka Aug 15, 2020
99bd603
refactor: use instrumented ahc WIP
pokryfka Aug 15, 2020
e09bc8e
Merge branch 'master' into feature/instrument
pokryfka Aug 17, 2020
a0e2109
Bump tracing-instrument to fe80d76
pokryfka Aug 27, 2020
ed287b8
Update AWSXRaySDKExampleAWS to use instrumented AWS SDK
pokryfka Aug 28, 2020
7e93fd8
fix: fix nio convenience method
pokryfka Aug 28, 2020
857c1c3
Merge branch 'fix/nio_future' into feature/instrument
pokryfka Aug 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
--exclude Sources/AWSXRayRecorder/Utils/Locks.swift
--exclude Sources/AWSXRayRecorder/AnyCodable
--exclude Tests/AWSXRayRecorderTests/AnyCodable
--exclude Examples/Sources/AWSXRaySDKExampleAWS/S3

# format options
#--self insert
Expand Down
29 changes: 24 additions & 5 deletions Examples/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import PackageDescription
let package = Package(
name: "aws-xray-sdk-swift-examples",
platforms: [
.macOS(.v10_14),
.macOS(.v10_14), // TODO: remove when new swift-aws-lambda-runtime is released
],
products: [
.executable(name: "AWSXRaySDKExample", targets: ["AWSXRaySDKExample"]),
.executable(name: "AWSXRayInstrumentExample", targets: ["AWSXRayInstrumentExample"]),
.executable(name: "AWSXRaySDKExampleAWS", targets: ["AWSXRaySDKExampleAWS"]),
.executable(name: "AWSXRaySDKExampleLambda", targets: ["AWSXRaySDKExampleLambda"]),
],
dependencies: [
.package(name: "aws-xray-sdk-swift", path: ".."),
.package(url: "https://github.com/swift-aws/aws-sdk-swift.git", .upToNextMinor(from: "5.0.0-alpha.5")),
.package(url: "https://github.com/swift-server/async-http-client.git", .upToNextMajor(from: "1.0.0")),
// .package(name: "swift-baggage-context", url: "https://github.com/slashmo/gsoc-swift-baggage-context.git", .upToNextMajor(from: "0.1.0")),
.package(url: "https://github.com/slashmo/gsoc-swift-tracing.git", .revision("fe80d764ad225b1dfd06dcb57d08b5e3485662f9")),
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")),
// .package(url: "https://github.com/swift-server/async-http-client.git", .upToNextMajor(from: "1.0.0")),
// .package(url: "https://github.com/pokryfka/async-http-client.git", .branch("feature/instrumentation")),
// .package(name: "aws-sdk-swift-core", path: "../../aws-sdk-swift-core"),
.package(url: "https://github.com/pokryfka/aws-sdk-swift-core.git", .branch("feature/tracing")),
// .package(url: "https://github.com/swift-aws/aws-sdk-swift.git", .upToNextMinor(from: "5.0.0-alpha.5")),
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", .upToNextMajor(from: "0.2.0")),
],
targets: [
Expand All @@ -26,12 +33,24 @@ let package = Package(
// .product(name: "AWSXRayTesting", package: "aws-xray-sdk-swift"),
]
),
.target(
name: "AWSXRayInstrumentExample",
dependencies: [
.product(name: "AWSXRaySDK", package: "aws-xray-sdk-swift"),
.product(name: "AWSXRayInstrument", package: "aws-xray-sdk-swift"),
// .product(name: "Baggage", package: "swift-baggage-context"),
.product(name: "NIOInstrumentation", package: "gsoc-swift-tracing"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio"),
]
),
.target(
name: "AWSXRaySDKExampleAWS",
dependencies: [
.product(name: "AWSXRaySDK", package: "aws-xray-sdk-swift"),
.product(name: "AsyncHTTPClient", package: "async-http-client"),
.product(name: "AWSS3", package: "aws-sdk-swift"),
.product(name: "AWSXRayInstrument", package: "aws-xray-sdk-swift"),
.product(name: "NIO", package: "swift-nio"),
.product(name: "AWSSDKSwiftCore", package: "aws-sdk-swift-core"),
]
),
.target(
Expand Down
72 changes: 72 additions & 0 deletions Examples/Sources/AWSXRayInstrumentExample/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the aws-xray-sdk-swift open source project
//
// Copyright (c) 2020 pokryfka and the aws-xray-sdk-swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import AsyncHTTPClient
import AWSXRayInstrument
import AWSXRaySDK
import Baggage // BaggageContext
import BaggageLogging // LoggingBaggageContextCarrier
import Instrumentation // InstrumentationSystem
import Logging // Logger
import NIOHTTP1 // HTTPHeaders
import NIOInstrumentation // HTTPHeadersExtractor
import TracingInstrumentation

extension BaggageContext: LoggingBaggageContextCarrier {
public var logger: Logger {
get { Logger(label: "", factory: { _ in Logging.SwiftLogNoOpLogHandler() }) }
set(newValue) {}
}
}

// create and boostrap the instrument
let instrument = XRayRecorder(config: .init(logLevel: .debug)) // XRayUDPEmitter
defer { instrument.shutdown() }
InstrumentationSystem.bootstrap(instrument)

// get the tracer
let tracer = InstrumentationSystem.tracingInstrument

// create new trace
let tracingHeader = XRayRecorder.TraceContext().tracingHeader

// extract the context from HTTP headers
let headers = HTTPHeaders([
("X-Amzn-Trace-Id", tracingHeader),
])
var baggage = BaggageContext()
tracer.extract(headers, into: &baggage, using: HTTPHeadersExtractor())

// create instrumented HTTP client
let http = HTTPClient(eventLoopGroupProvider: .createNew)
defer { http.shutdown { _ in } }

// create new span
var span = tracer.startSpan(named: "Span 1", context: baggage)
span.attributes["key"] = "value"
span.addLink(.init(context: baggage))
span.addEvent(.init(name: "Event"))
span.addEvent(.init(name: "Event 2"))

var span2 = tracer.startSpan(named: "Span 2", context: baggage)
span.attributes["key2"] = 2

// TODO: XRay subsegment parent needs to be sent before, consider sending parent twice - when started and when ended (?)
span.end()

tracer.forceFlush()

let url = "https://nf5g0bsxz5.execute-api.us-east-1.amazonaws.com/dev/hello"
_ = try http.get(url: url, context: span.context).wait()

span2.end()
142 changes: 142 additions & 0 deletions Examples/Sources/AWSXRaySDKExampleAWS/S3/S3RequestMiddleware.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//===----------------------------------------------------------------------===//
//
// 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 AWSCrypto
import AWSSDKSwiftCore
import AWSXML
import Foundation

public struct S3RequestMiddleware: AWSServiceMiddleware {
public init() {}

/// edit request before sending to S3
public func chain(request: AWSRequest) throws -> AWSRequest {
var request = request

self.virtualAddressFixup(request: &request)
self.createBucketFixup(request: &request)
self.calculateMD5(request: &request)

return request
}

/// Edit responses coming back from S3
public func chain(response: AWSResponse) throws -> AWSResponse {
var response = response

self.metadataFixup(response: &response)
self.getLocationResponseFixup(response: &response)

return response
}

func virtualAddressFixup(request: inout AWSRequest) {
/// process URL into form ${bucket}.s3.amazon.com
let paths = request.url.path.split(separator: "/", omittingEmptySubsequences: true)
if paths.count > 0 {
guard var host = request.url.host else { return }
if let port = request.url.port {
host = "\(host):\(port)"
}
let bucket = paths[0]
var urlPath: String
var urlHost: String
// if host name contains amazonaws.com and bucket name doesn't contain a period do virtual address look up
if host.contains("amazonaws.com"), !bucket.contains(".") {
let pathsWithoutBucket = paths.dropFirst() // bucket
urlPath = pathsWithoutBucket.joined(separator: "/")
if let firstHostComponent = host.split(separator: ".").first, bucket == firstHostComponent {
// Bucket name is part of host. No need to append bucket
urlHost = host
} else {
urlHost = "\(bucket).\(host)"
}
} else {
urlPath = paths.joined(separator: "/")
urlHost = host
}
// add percent encoding back into path as converting from URL to String has removed it
let percentEncodedUrlPath = urlPath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? urlPath
var urlString = "\(request.url.scheme ?? "https")://\(urlHost)/\(percentEncodedUrlPath)"
if let query = request.url.query {
urlString += "?\(query)"
}
request.url = URL(string: urlString)!
}
}

func createBucketFixup(request: inout AWSRequest) {
switch request.operation {
// fixup CreateBucket to include location
case "CreateBucket":
var xml = ""
if request.region != .useast1 {
xml += "<CreateBucketConfiguration xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">"
xml += "<LocationConstraint>"
xml += request.region.rawValue
xml += "</LocationConstraint>"
xml += "</CreateBucketConfiguration>"
}
request.body = .text(xml)

default:
break
}
}

func calculateMD5(request: inout AWSRequest) {
// if request has a body, calculate the MD5 for that body
if let byteBuffer = request.body.asByteBuffer() {
let byteBufferView = byteBuffer.readableBytesView
if let encoded = byteBufferView.withContiguousStorageIfAvailable({ bytes in
return Data(Insecure.MD5.hash(data: bytes)).base64EncodedString()
}) {
request.httpHeaders.replaceOrAdd(name: "Content-MD5", value: encoded)
}
}
}

func getLocationResponseFixup(response: inout AWSResponse) {
if case .xml(let element) = response.body {
// GetBucketLocation comes back without a containing xml element
if element.name == "LocationConstraint" {
if element.stringValue == "" {
element.addChild(.text(stringValue: "us-east-1"))
}
let parentElement = XML.Element(name: "BucketLocation")
parentElement.addChild(element)
response.body = .xml(parentElement)
}
}
}

func metadataFixup(response: inout AWSResponse) {
// convert x-amz-meta-* header values into a dictionary, which we add as a "x-amz-meta-" header. This is processed by AWSClient to fill metadata values in GetObject and HeadObject
switch response.body {
case .raw(_), .empty:
var metadata: [String: String] = [:]
for (key, value) in response.headers {
if key.hasPrefix("x-amz-meta-"), let value = value as? String {
let keyWithoutPrefix = key.dropFirst("x-amz-meta-".count)
metadata[String(keyWithoutPrefix)] = value
}
}
if !metadata.isEmpty {
response.headers["x-amz-meta-"] = metadata
}
default:
break
}
}
}
Loading