From 9f8f532753a8026c60d088b1514e546ba4e21170 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 20 Feb 2025 11:22:31 -0500 Subject: [PATCH 1/5] feat: Add OTel-Swift supported Tracing --- Package.swift | 7 +- .../OpenTelemetry/OTelProvider.swift | 39 ++++++ .../Providers/OpenTelemetry/OTelTracing.swift | 118 ++++++++++++++++++ .../Providers/OpenTelemetry/OTelUtils.swift | 27 ++++ Sources/Smithy/Attribute.swift | 8 ++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift create mode 100644 Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift create mode 100644 Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift diff --git a/Package.swift b/Package.swift index 973b4721f..a4b281bac 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ let libXML2TargetOrNil: Target? = nil let package = Package( name: "smithy-swift", platforms: [ - .macOS(.v10_15), + .macOS(.v12), .iOS(.v13), .tvOS(.v13), .watchOS(.v6) @@ -58,6 +58,7 @@ let package = Package( var dependencies: [Package.Dependency] = [ .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.44.0"), .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 { @@ -95,6 +96,10 @@ let package = Package( "SmithyChecksums", "SmithyCBOR", .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"), + .product(name: "InMemoryExporter", package: "opentelemetry-swift"), + .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), + .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), + .product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"), ], resources: [ .copy("PrivacyInfo.xcprivacy") diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift new file mode 100644 index 000000000..76e3f6e38 --- /dev/null +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift @@ -0,0 +1,39 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import OpenTelemetryApi +import OpenTelemetrySdk +import Smithy +import OpenTelemetryProtocolExporterHttp +import Foundation + +/// Namespace for the SDK Telemetry implementation. +public enum OpenTelemetrySwift { + /// 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) + } + } +} diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift new file mode 100644 index 000000000..a3595dbe0 --- /dev/null +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift @@ -0,0 +1,118 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@preconcurrency import OpenTelemetryApi +@preconcurrency import OpenTelemetrySdk +import Smithy + +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, attributes: Attributes?) -> 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(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(key: AttributeKey, 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 + } + } +} diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift new file mode 100644 index 000000000..483877d77 --- /dev/null +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift @@ -0,0 +1,27 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import OpenTelemetryApi + +import Smithy + +extension Attributes { + public func toOtelAttributes() -> [String: AttributeValue] { + let keys: [String] = self.getKeys() + var otelKeys: [String: AttributeValue] = [:] + + guard !keys.isEmpty else { + return [:] + } + + keys.forEach { key in + otelKeys[key] = AttributeValue(self.get(key: AttributeKey(name: key))!) + } + + return otelKeys + } +} diff --git a/Sources/Smithy/Attribute.swift b/Sources/Smithy/Attribute.swift index 42cb8ea59..d6a11b0c5 100644 --- a/Sources/Smithy/Attribute.swift +++ b/Sources/Smithy/Attribute.swift @@ -13,6 +13,10 @@ public struct AttributeKey: Sendable { self.name = name } + public func getName() -> String { + return self.name + } + func toString() -> String { return "AttributeKey: \(name)" } @@ -33,6 +37,10 @@ public struct Attributes { get(key: key) != nil } + public func getKeys() -> [String] { + return Array(self.attributes.keys) + } + public mutating func set(key: AttributeKey, value: T?) { attributes[key.name] = value } From e34b38e732c2f3c7b016e50a31d4e06ceca76c17 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 20 Feb 2025 11:44:29 -0500 Subject: [PATCH 2/5] raise minimum macOS to v12 --- .../amazon/smithy/swift/codegen/PackageManifestGenerator.kt | 2 +- .../codegen/manifestanddocs/PackageManifestGeneratorTests.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/PackageManifestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/PackageManifestGenerator.kt index 714824402..221aee6f4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/PackageManifestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/PackageManifestGenerator.kt @@ -24,7 +24,7 @@ class PackageManifestGenerator( writer.write("name: \$S,", ctx.settings.moduleName) writer.openBlock("platforms: [", "],") { - writer.write(".macOS(.v10_15), .iOS(.v13)") + writer.write(".macOS(.v12), .iOS(.v13)") } writer.openBlock("products: [", "],") { diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/manifestanddocs/PackageManifestGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/manifestanddocs/PackageManifestGeneratorTests.kt index 5f786be36..72b115673 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/manifestanddocs/PackageManifestGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/manifestanddocs/PackageManifestGeneratorTests.kt @@ -30,7 +30,7 @@ class PackageManifestGeneratorTests { assertNotNull(packageManifest) val expected = """ platforms: [ - .macOS(.v10_15), .iOS(.v13) + .macOS(.v12), .iOS(.v13) ], """ packageManifest.shouldContain(expected) From c738d118d21dba25b74db6e150cfc660abddd08e Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 20 Feb 2025 14:03:11 -0500 Subject: [PATCH 3/5] add import for OpenTelemetryConcurrency for linux support --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 1d7580fd7..3e9b02c0a 100644 --- a/Package.swift +++ b/Package.swift @@ -100,6 +100,7 @@ let package = Package( .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), .product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"), + .product(name: "OpenTelemetryConcurrency", package: "opentelemetry-swift"), ], resources: [ .copy("PrivacyInfo.xcprivacy") From fdf23b7b47df0396c908848b4f76de763a1f7720 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Fri, 21 Feb 2025 10:37:00 -0500 Subject: [PATCH 4/5] add linux and visionOS #if --- .../Telemetry/Providers/OpenTelemetry/OTelProvider.swift | 2 ++ .../Telemetry/Providers/OpenTelemetry/OTelTracing.swift | 2 ++ .../Telemetry/Providers/OpenTelemetry/OTelUtils.swift | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift index 76e3f6e38..14dbe701f 100644 --- a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelProvider.swift @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // +#if !(os(Linux) || os(visionOS)) import OpenTelemetryApi import OpenTelemetrySdk import Smithy @@ -37,3 +38,4 @@ public enum OpenTelemetrySwift { } } } +#endif diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift index a3595dbe0..58aede021 100644 --- a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelTracing.swift @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // +#if !(os(Linux) || os(visionOS)) @preconcurrency import OpenTelemetryApi @preconcurrency import OpenTelemetrySdk import Smithy @@ -116,3 +117,4 @@ extension TraceSpanStatus { } } } +#endif diff --git a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift index 483877d77..5acc802b4 100644 --- a/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift +++ b/Sources/ClientRuntime/Telemetry/Providers/OpenTelemetry/OTelUtils.swift @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // +#if !(os(Linux) || os(visionOS)) import OpenTelemetryApi import Smithy @@ -25,3 +26,4 @@ extension Attributes { return otelKeys } } +#endif From cbca5f87f13224c4cad80e8c9b6e8b218a87a461 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Fri, 21 Feb 2025 11:00:59 -0500 Subject: [PATCH 5/5] only add opentelemetry on non-linux and non-vision os --- Package.swift | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index 3e9b02c0a..802fa949c 100644 --- a/Package.swift +++ b/Package.swift @@ -97,10 +97,22 @@ let package = Package( "SmithyCBOR", .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"), .product(name: "InMemoryExporter", package: "opentelemetry-swift"), - .product(name: "OpenTelemetryApi", package: "opentelemetry-swift"), - .product(name: "OpenTelemetrySdk", package: "opentelemetry-swift"), - .product(name: "OpenTelemetryProtocolExporterHTTP", package: "opentelemetry-swift"), - .product(name: "OpenTelemetryConcurrency", package: "opentelemetry-swift"), + // Only include these on macOS, iOS, tvOS, and watchOS (visionOS and Linux are excluded) + .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")