Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9f8f532
feat: Add OTel-Swift supported Tracing
dayaffe Feb 20, 2025
e34b38e
raise minimum macOS to v12
dayaffe Feb 20, 2025
0652e6c
Merge branch 'main' into day/implement-tracing-otel
dayaffe Feb 20, 2025
c738d11
add import for OpenTelemetryConcurrency for linux support
dayaffe Feb 20, 2025
fdf23b7
add linux and visionOS #if
dayaffe Feb 21, 2025
cbca5f8
only add opentelemetry on non-linux and non-vision os
dayaffe Feb 21, 2025
1266afa
Merge branch 'main' into day/implement-tracing-otel
dayaffe May 21, 2025
d315e22
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 18, 2025
8e1d756
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 19, 2025
c0d6252
only add telemetry dependency if correct os
dayaffe Jun 20, 2025
27982f8
fix client runtime dependencies
dayaffe Jun 20, 2025
39969cf
try again
dayaffe Jun 20, 2025
07a1320
revert previous bad change
dayaffe Jun 20, 2025
05e39a9
add preconcurrency flag
dayaffe Jun 20, 2025
014ed05
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 20, 2025
3311c02
try making swift 6 compatible
dayaffe Jun 20, 2025
914fa3b
reduce version to previous one we were using
dayaffe Jun 20, 2025
e72332f
add back preconcurrency
dayaffe Jun 20, 2025
45f6a89
another try at concurrency
dayaffe Jun 20, 2025
2be51b5
address comments
dayaffe Jun 20, 2025
e7a12d0
fix failures
dayaffe Jun 20, 2025
6d3cdea
remove extra comma
dayaffe Jun 20, 2025
95cb3b2
more fixes
dayaffe Jun 20, 2025
093a18f
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 20, 2025
891c9ee
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 24, 2025
0167e76
Merge branch 'main' into day/implement-tracing-otel
dayaffe Jun 24, 2025
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
23 changes: 23 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ let package = Package(
var dependencies: [Package.Dependency] = [
.package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.52.1"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
.package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.13.0"),
]

let isDocCEnabled = ProcessInfo.processInfo.environment["AWS_SWIFT_SDK_ENABLE_DOCC"] != nil
if isDocCEnabled {
dependencies.append(.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"))
Expand Down Expand Up @@ -95,6 +97,27 @@ let package = Package(
"SmithyChecksums",
"SmithyCBOR",
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"),
// Only include these on macOS, iOS, tvOS, and watchOS (visionOS and Linux are excluded)
.product(
name: "InMemoryExporter",
package: "opentelemetry-swift",
condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is because OTel-Swift is currently not working on non-Apple platforms?

),
.product(
name: "OpenTelemetryApi",
package: "opentelemetry-swift",
condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])
),
.product(
name: "OpenTelemetrySdk",
package: "opentelemetry-swift",
condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])
),
.product(
name: "OpenTelemetryProtocolExporterHTTP",
package: "opentelemetry-swift",
condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS])
),
],
resources: [
.copy("PrivacyInfo.xcprivacy")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ public class CRTClientEngine: HTTPClient {
public func send(request: HTTPRequest) async throws -> HTTPResponse {
let telemetryContext = telemetry.contextManager.current()
let tracer = telemetry.tracerProvider.getTracer(
scope: telemetry.tracerScope,
attributes: telemetry.tracerAttributes
scope: telemetry.tracerScope
)
let queuedStart = Date().timeIntervalSinceReferenceDate
let span = tracer.createSpan(
Expand Down Expand Up @@ -243,16 +242,17 @@ public class CRTClientEngine: HTTPClient {
func executeHTTP2Request(request: HTTPRequest) async throws -> HTTPResponse {
let telemetryContext = telemetry.contextManager.current()
let tracer = telemetry.tracerProvider.getTracer(
scope: telemetry.tracerScope,
attributes: telemetry.tracerAttributes)
scope: telemetry.tracerScope
)
do {
// START - smithy.client.http.requests.queued_duration
let queuedStart = Date().timeIntervalSinceReferenceDate
let span = tracer.createSpan(
name: telemetry.spanName,
initialAttributes: telemetry.spanAttributes,
spanKind: SpanKind.internal,
parentContext: telemetryContext)
spanKind: .internal,
parentContext: telemetryContext
)
defer {
span.end()
}
Expand Down Expand Up @@ -323,7 +323,8 @@ public class CRTClientEngine: HTTPClient {
telemetry.connectionsUptime.record(
value: Date().timeIntervalSinceReferenceDate - connectionUptimeStart,
attributes: Attributes(),
context: telemetryContext)
context: telemetryContext
)
}
// TICK - smithy.client.http.bytes_sent
try await stream.write(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,8 @@ public final class URLSessionHTTPClient: HTTPClient {
public func send(request: HTTPRequest) async throws -> HTTPResponse {
let telemetryContext = telemetry.contextManager.current()
let tracer = telemetry.tracerProvider.getTracer(
scope: telemetry.tracerScope,
attributes: telemetry.tracerAttributes)
scope: telemetry.tracerScope
)
do {
// START - smithy.client.http.requests.queued_duration
let queuedStart = Date().timeIntervalSinceReferenceDate
Expand Down
23 changes: 14 additions & 9 deletions Sources/ClientRuntime/Orchestrator/Orchestrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ public struct Orchestrator<
public func execute(input: InputType) async throws -> OutputType {
let telemetryContext = telemetry.contextManager.current()
let tracer = telemetry.tracerProvider.getTracer(
scope: telemetry.tracerScope,
attributes: telemetry.tracerAttributes)
scope: telemetry.tracerScope
)

// DURATION - smithy.client.call.duration
do {
Expand Down Expand Up @@ -309,14 +309,15 @@ public struct Orchestrator<
// with the thrown error in context.result
let telemetryContext = telemetry.contextManager.current()
let tracer = telemetry.tracerProvider.getTracer(
scope: telemetry.tracerScope,
attributes: telemetry.tracerAttributes)
scope: telemetry.tracerScope
)

// TICK - smithy.client.call.attempts
telemetry.rpcAttempts.add(
value: 1,
attributes: telemetry.metricsAttributes,
context: telemetryContext)
context: telemetryContext
)

// DURATION - smithy.client.call.attempt_duration
do {
Expand Down Expand Up @@ -349,7 +350,8 @@ public struct Orchestrator<
telemetry.resolveIdentityDuration.record(
value: Date().timeIntervalSinceReferenceDate - identityStart,
attributes: authSchemeAttributes,
context: telemetryContext)
context: telemetryContext
)
// END - smithy.client.call.auth.resolve_identity_duration

// START - smithy.client.call.resolve_endpoint_duration
Expand All @@ -362,7 +364,8 @@ public struct Orchestrator<
telemetry.resolveEndpointDuration.record(
value: Date().timeIntervalSinceReferenceDate - endpointStart,
attributes: telemetry.metricsAttributes,
context: telemetryContext)
context: telemetryContext
)
// END - smithy.client.call.resolve_endpoint_duration

context.updateRequest(updated: withEndpoint)
Expand All @@ -380,7 +383,8 @@ public struct Orchestrator<
telemetry.signingDuration.record(
value: Date().timeIntervalSinceReferenceDate - signingStart,
attributes: authSchemeAttributes,
context: telemetryContext)
context: telemetryContext
)
// END - smithy.client.call.auth.signing_duration

context.updateRequest(updated: signed)
Expand All @@ -405,7 +409,8 @@ public struct Orchestrator<
telemetry.deserializationDuration.record(
value: Date().timeIntervalSinceReferenceDate - deserializeStart,
attributes: telemetry.metricsAttributes,
context: telemetryContext)
context: telemetryContext
)
// END - smithy.client.call.deserialization_duration
context.updateOutput(updated: output)

Expand Down
2 changes: 1 addition & 1 deletion Sources/ClientRuntime/Telemetry/DefaultTelemetry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ extension DefaultTelemetry {
fileprivate static let defaultTraceSpan: TraceSpan = NoOpTraceSpan()

fileprivate final class NoOpTracerProvider: TracerProvider {
func getTracer(scope: String, attributes: Attributes?) -> Tracer { defaultTracer }
func getTracer(scope: String) -> Tracer { defaultTracer }
}

fileprivate final class NoOpTracer: Tracer {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Foundation

// OpenTelemetrySdk specific imports
@preconcurrency import protocol OpenTelemetrySdk.SpanExporter

/// Namespace for the SDK Telemetry implementation.
public enum OpenTelemetrySwift {
Comment thread
sichanyoo marked this conversation as resolved.
/// The SDK TelemetryProviderOTel Implementation.
///
/// - contextManager: no-op
/// - loggerProvider: provides SwiftLoggers
/// - meterProvider: no-op
/// - tracerProvider: provides OTelTracerProvider with InMemoryExporter
public static func provider(spanExporter: any SpanExporter) -> TelemetryProvider {
return OpenTelemetrySwiftProvider(spanExporter: spanExporter)
}

public final class OpenTelemetrySwiftProvider: TelemetryProvider {
public let contextManager: TelemetryContextManager
public let loggerProvider: LoggerProvider
public let meterProvider: MeterProvider
public let tracerProvider: TracerProvider

public init(spanExporter: SpanExporter) {
self.contextManager = DefaultTelemetry.defaultContextManager
self.loggerProvider = DefaultTelemetry.defaultLoggerProvider
self.meterProvider = DefaultTelemetry.defaultMeterProvider
self.tracerProvider = OTelTracerProvider(spanExporter: spanExporter)
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
// OpenTelemetryApi specific imports
@preconcurrency import protocol OpenTelemetryApi.Tracer
@preconcurrency import protocol OpenTelemetryApi.Span
@preconcurrency import enum OpenTelemetryApi.SpanKind
@preconcurrency import enum OpenTelemetryApi.Status
@preconcurrency import enum OpenTelemetryApi.AttributeValue

// OpenTelemetrySdk specific imports
@preconcurrency import class OpenTelemetrySdk.TracerProviderSdk
@preconcurrency import class OpenTelemetrySdk.TracerProviderBuilder
@preconcurrency import struct OpenTelemetrySdk.SimpleSpanProcessor
@preconcurrency import protocol OpenTelemetrySdk.SpanExporter
@preconcurrency import struct OpenTelemetrySdk.Resource

// Smithy specific imports
import struct Smithy.AttributeKey
import struct Smithy.Attributes

public typealias OpenTelemetryTracer = OpenTelemetryApi.Tracer
public typealias OpenTelemetrySpanKind = OpenTelemetryApi.SpanKind
public typealias OpenTelemetrySpan = OpenTelemetryApi.Span
public typealias OpenTelemetryStatus = OpenTelemetryApi.Status

// Trace
public final class OTelTracerProvider: TracerProvider {
private let sdkTracerProvider: TracerProviderSdk

public init(spanExporter: SpanExporter) {
self.sdkTracerProvider = TracerProviderBuilder()
.add(spanProcessor: SimpleSpanProcessor(spanExporter: spanExporter))
.with(resource: Resource())
.build()
}

public func getTracer(scope: String) -> any Tracer {
let tracer = self.sdkTracerProvider.get(instrumentationName: scope)
return OTelTracerImpl(otelTracer: tracer)
}
}

public final class OTelTracerImpl: Tracer {
private let otelTracer: OpenTelemetryTracer

public init(otelTracer: OpenTelemetryTracer) {
self.otelTracer = otelTracer
}

public func createSpan(
name: String,
initialAttributes: Attributes?, spanKind: SpanKind, parentContext: (any TelemetryContext)?
) -> any TraceSpan {
let spanBuilder = self.otelTracer
.spanBuilder(spanName: name)
.setSpanKind(spanKind: spanKind.toOTelSpanKind())

initialAttributes?.getKeys().forEach { key in
spanBuilder.setAttribute(
key: key,
value: (initialAttributes?.get(key: AttributeKey<String>(name: key)))!
)
}

return OTelTraceSpanImpl(name: name, otelSpan: spanBuilder.startSpan())
}
}

private final class OTelTraceSpanImpl: TraceSpan {
let name: String
private let otelSpan: OpenTelemetrySpan

public init(name: String, otelSpan: OpenTelemetrySpan) {
self.name = name
self.otelSpan = otelSpan
}

func emitEvent(name: String, attributes: Attributes?) {
if let attributes = attributes, !(attributes.size == 0) {
self.otelSpan.addEvent(name: name, attributes: attributes.toOtelAttributes())
} else {
self.otelSpan.addEvent(name: name)
}
}

func setAttribute<T>(key: AttributeKey<T>, value: T) {
self.otelSpan.setAttribute(key: key.getName(), value: AttributeValue.init(value))
}

func setStatus(status: TraceSpanStatus) {
self.otelSpan.status = status.toOTelStatus()
}

func end() {
self.otelSpan.end()
}
}

extension SpanKind {
func toOTelSpanKind() -> OpenTelemetrySpanKind {
switch self {
case .client:
return .client
case .consumer:
return .consumer
case .internal:
return .internal
case .producer:
return .producer
case .server:
return .server
}
}
}

extension TraceSpanStatus {
func toOTelStatus() -> OpenTelemetryStatus {
switch self {
case .error:
return .error(description: "An error occured!") // status doesn't have error description
case .ok:
return .ok
case .unset:
return .unset
}
}
}
#endif
Loading
Loading