Unofficial AWS X-Ray Recorder SDK for Swift.
Add a dependency using Swift Package Manager.
dependencies: [
.package(url: "https://github.com/pokryfka/aws-xray-sdk-swift.git", from: "0.3.0")
]
Create an instance of XRayRecorder
:
let recorder = XRayRecorder()
Begin and end (sub)segments explicitly:
let segment = recorder.beginSegment(name: "Segment 1")
usleep(100_000)
segment.end()
use closures for convenience:
recorder.segment(name: "Segment 2") { segment in
try? segment.subsegment(name: "Subsegment 2.1") { segment in
_ = segment.subsegment(name: "Subsegment 2.1.1 with Result") { _ -> String in
usleep(100_000)
return "Result"
}
try segment.subsegment(name: "Subsegment 2.1.2 with Error") { _ in
usleep(200_000)
throw ExampleError.test
}
}
}
Events are emitted as soon as they end.
Subsegments have to be created before the parent segment ended.
Subsegments may end after their parent segment ended, in which case they will be presented as Pending until they end.
Make sure all segments are sent before program exits:
recorder.wait()
Result in AWS X-Ray console:
See AWSXRayRecorderExample/main.swift
for a complete example.
Segments and subsegments can include an annotations object containing one or more fields that X-Ray indexes for use with filter expressions. (...)
segment.setAnnotation("zip_code", value: 98101)
Segments and subsegments can include a metadata object containing one or more fields with values of any type, including objects and arrays. X-Ray does not index metadata (...)
segment.setMetadata(["debug": ["test": "Metadata string"]])
Record AWSClient requests with XRayMiddleware
:
let awsClient = AWSClient(
middlewares: [XRayMiddleware(recorder: recorder, name: "S3")],
httpClientProvider: .shared(httpClient)
)
let s3 = S3(client: awsClient)
and/or recording SwiftNIO futures:
recorder.segment(name: "List Buckets") {
s3.listBuckets()
}
Result in AWS X-Ray console:
See AWSXRayRecorderExampleSDK/main.swift
for a complete example.
AWS Lambda using Swift AWS Lambda Runtime
Enable tracing as described in Using AWS Lambda with AWS X-Ray.
Note that:
Lambda runs the daemon automatically any time a function is invoked for a sampled request.
Make sure to flush the recorder in each invocation:
private struct ExampleLambdaHandler: EventLoopLambdaHandler {
typealias In = Cloudwatch.ScheduledEvent
typealias Out = Void
private let recorder = XRayRecorder()
private func doWork(on eventLoop: EventLoop) -> EventLoopFuture<Void> {
eventLoop.submit { usleep(100_000) }.map { _ in }
}
func handle(context: Lambda.Context, event: In) -> EventLoopFuture<Void> {
recorder.segment(name: "ExampleLambdaHandler", context: context) {
self.doWork(on: context.eventLoop)
}.flatMap {
self.recorder.flush(on: context.eventLoop)
}
}
}
See AWSXRayRecorderExampleLambda/main.swift
for a complete example.
The library’s behavior can be configured using environment variables:
AWS_XRAY_SDK_DISABLED
: settrue
to disable tracing, enabled by default.AWS_XRAY_DAEMON_ADDRESS
– the IP address and port of the X-Ray daemon,127.0.0.1:2000
by default.XRAY_RECORDER_LOG_LEVEL
: swift-log logging level,info
by default.
Alternatively XRayRecorder
can be configured using XRayRecorder.Config
which will override environment variables:
let recorder = XRayRecorder(
config: .init(enabled: true,
logLevel: .debug,
daemonEndpoint: "127.0.0.1:2000",
serviceVersion: "aws-xray-sdk-swift-example-sdk")
)
Segments can be emitted on provided SwiftNIO EventLoopGroup
:
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let eventLoop = group.next()
let recorder = XRayRecorder(eventLoopGroup: group)
// ...
try recorder.flush(on: eventLoop).wait()
By default events are sent as UDP to AWS X-Ray daemon which buffers and relays it to AWS X-Ray API.
A custom emitter has to implement XRayEmitter
protocol:
public protocol XRayEmitter {
func send(_ segment: XRayRecorder.Segment)
func flush(_ callback: @escaping (Error?) -> Void)
}
example of an emitter which logs emitted segments:
public struct XRayLogEmitter: XRayEmitter {
private let logger: Logger
private let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
return encoder
}()
public init(label: String? = nil) {
let label = label ?? "xray.log_emitter.\(String.random32())"
logger = Logger(label: label)
}
public func send(_ segment: XRayRecorder.Segment) {
do {
let document: String = try encoder.encode(segment)
logger.info("\n\(document)")
} catch {
logger.error("Failed to encode a segment: \(error)")
}
}
public func flush(_: @escaping (Error?) -> Void) {}
}
The emitter has to be provided when creating an instance of XRayRecorder
:
let recorder = XRayRecorder(emitter: XRayNoOpEmitter())