Skip to content

refactor!: observability API and OTeL provider #886

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

Merged
merged 17 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changes/44708ceb-cc8c-478c-bd35-af82aa79f546.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "44708ceb-cc8c-478c-bd35-af82aa79f546",
"type": "misc",
"description": "**BREAKING**: Refactor observability API and configuration. See the [discussion](https://github.com/awslabs/aws-sdk-kotlin/discussions/981) post from the AWS SDK for Kotlin for more information."

}
5 changes: 5 additions & 0 deletions .changes/e59b3095-f066-4f70-a378-7cf151207921.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "e59b3095-f066-4f70-a378-7cf151207921",
"type": "feature",
"description": "Add experimental support for OpenTelemetry based telemetry provider"
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ data class KotlinDependency(
val AWS_CREDENTIALS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.auth.awscredentials", RUNTIME_GROUP, "aws-credentials", RUNTIME_VERSION)
val AWS_SIGNING_COMMON = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.auth.awssigning", RUNTIME_GROUP, "aws-signing-common", RUNTIME_VERSION)
val AWS_SIGNING_DEFAULT = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.auth.awssigning", RUNTIME_GROUP, "aws-signing-default", RUNTIME_VERSION)
val TRACING_CORE = KotlinDependency(GradleConfiguration.Api, "$RUNTIME_ROOT_NS.tracing", RUNTIME_GROUP, "tracing-core", RUNTIME_VERSION)
val TELEMETRY_API = KotlinDependency(GradleConfiguration.Api, "$RUNTIME_ROOT_NS.telemetry", RUNTIME_GROUP, "telemetry-api", RUNTIME_VERSION)
val TELEMETRY_DEFAULTS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.telemetry", RUNTIME_GROUP, "telemetry-defaults", RUNTIME_VERSION)

val AWS_JSON_PROTOCOLS = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.json", RUNTIME_GROUP, "aws-json-protocols", RUNTIME_VERSION)
val AWS_EVENT_STREAM = KotlinDependency(GradleConfiguration.Implementation, "$RUNTIME_ROOT_NS.awsprotocol.eventstream", RUNTIME_GROUP, "aws-event-stream", RUNTIME_VERSION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ object RuntimeTypes {
val HttpOperationContext = symbol("HttpOperationContext")
val HttpSerialize = symbol("HttpSerialize")
val OperationAuthConfig = symbol("OperationAuthConfig")
val OperationMetrics = symbol("OperationMetrics")
val OperationRequest = symbol("OperationRequest")
val roundTrip = symbol("roundTrip")
val sdkRequestId = symbol("sdkRequestId")
val telemetry = symbol("telemetry")
val SdkHttpOperation = symbol("SdkHttpOperation")
val SdkHttpRequest = symbol("SdkHttpRequest")
val setResolvedEndpoint = symbol("setResolvedEndpoint")
Expand Down Expand Up @@ -310,15 +311,18 @@ object RuntimeTypes {
}
}

object Tracing {
object Core : RuntimeTypePackage(KotlinDependency.TRACING_CORE) {
val debug = symbol("debug")
val DefaultTracer = symbol("DefaultTracer")
val LoggingTraceProbe = symbol("LoggingTraceProbe")
val TraceProbe = symbol("TraceProbe")
val Tracer = symbol("Tracer")
val TracingClientConfig = symbol("TracingClientConfig")
val withRootTraceSpan = symbol("withRootTraceSpan")
object Observability {
object TelemetryApi : RuntimeTypePackage(KotlinDependency.TELEMETRY_API) {
val SpanKind = symbol("SpanKind", "trace")
val TelemetryConfig = symbol("TelemetryConfig")
val TelemetryProvider = symbol("TelemetryProvider")
val TelemetryProviderContext = symbol("TelemetryProviderContext")
val TelemetryContextElement = symbol("TelemetryContextElement", "context")
val TraceSpan = symbol("TraceSpan", "trace")
val withSpan = symbol("withSpan", "trace")
}
object TelemetryDefaults : RuntimeTypePackage(KotlinDependency.TELEMETRY_DEFAULTS) {
val Global = symbol("Global")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package software.amazon.smithy.kotlin.codegen.lang

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
import software.amazon.smithy.kotlin.codegen.model.toSymbol

/**
* Builtin kotlin types
Expand Down Expand Up @@ -90,6 +91,10 @@ object KotlinTypes {
val seconds = builtInSymbol("seconds", "kotlin.time.Duration.Companion")
}

object Coroutines {
val CoroutineContext = "kotlin.coroutines.CoroutineContext".toSymbol()
}

/**
* A (non-exhaustive) set of builtin Kotlin symbols
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ServiceClientConfigGenerator(

add(RuntimeConfigProperty.RetryPolicy)
add(RuntimeConfigProperty.RetryStrategy)
add(RuntimeConfigProperty.Tracer)
add(RuntimeConfigProperty.TelemetryProvider)

if (shape.hasTrait<ClientContextParamsTrait>()) {
addAll(clientContextConfigProps(shape.expectTrait()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ class ServiceClientGenerator(private val ctx: RenderingContext<ServiceShape>) {
private val writer = ctx.writer

fun render() {
writer.write("\n\n")
writer.write("public const val ServiceId: String = #S", ctx.settings.sdkId)
writer.write("public const val SdkVersion: String = #S", ctx.settings.pkg.version)
writer.write("\n\n")

writer.putContext("service.name", ctx.settings.sdkId)

val topDownIndex = TopDownIndex.of(ctx.model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ abstract class HttpProtocolClientGenerator(
val Operation: SectionKey<OperationShape> = SectionKey("Operation")
}

object OperationTelemetryBuilder : SectionId {
val Operation: SectionKey<OperationShape> = SectionKey("Operation")
}

/**
* Render the implementation of the service client interface
*/
Expand Down Expand Up @@ -114,6 +118,9 @@ abstract class HttpProtocolClientGenerator(

write("toMap()")
}

writer.write("private val telemetryScope = #S", ctx.settings.pkg.name)
writer.write("private val opMetrics = #T(telemetryScope, config.telemetryProvider)", RuntimeTypes.HttpClient.Operation.OperationMetrics)
}

protected open fun importSymbols(writer: KotlinWriter) {
Expand Down Expand Up @@ -206,6 +213,7 @@ abstract class HttpProtocolClientGenerator(
writer.write("expectedHttpStatus = ${httpTrait.code}")
// property from implementing SdkClient
writer.write("operationName = #S", op.id.name)
writer.write("serviceName = #L", "ServiceId")

// optional endpoint trait
op.getTrait<EndpointTrait>()?.let { endpointTrait ->
Expand All @@ -224,6 +232,14 @@ abstract class HttpProtocolClientGenerator(
}
}

// telemetry
writer.withBlock("#T {", "}", RuntimeTypes.HttpClient.Operation.telemetry) {
write("provider = config.telemetryProvider")
write("scope = telemetryScope")
write("metrics = opMetrics")
writer.declareSection(OperationTelemetryBuilder, mapOf(OperationTelemetryBuilder.Operation to op))
}

writer.write(
"execution.auth = #T(#T, configuredAuthSchemes, identityProviderConfig)",
RuntimeTypes.HttpClient.Operation.OperationAuthConfig,
Expand All @@ -249,24 +265,11 @@ abstract class HttpProtocolClientGenerator(
val hasOutputStream = outputShape.map { it.hasStreamingMember(ctx.model) }.orElse(false)
val inputVariableName = if (inputShape.isPresent) "input" else KotlinTypes.Unit.fullName

writer
.write(
"""val rootSpan = config.tracer.createRootSpan("#L-${'$'}{op.context.#T}")""",
op.id.name,
RuntimeTypes.HttpClient.Operation.sdkRequestId,
)
.withBlock(
"return #T.#T(rootSpan) {",
"}",
RuntimeTypes.KotlinCoroutines.coroutineContext,
RuntimeTypes.Tracing.Core.withRootTraceSpan,
) {
if (hasOutputStream) {
write("op.#T(client, #L, block)", RuntimeTypes.HttpClient.Operation.execute, inputVariableName)
} else {
write("op.#T(client, #L)", RuntimeTypes.HttpClient.Operation.roundTrip, inputVariableName)
}
}
if (hasOutputStream) {
writer.write("return op.#T(client, #L, block)", RuntimeTypes.HttpClient.Operation.execute, inputVariableName)
} else {
writer.write("return op.#T(client, #L)", RuntimeTypes.HttpClient.Operation.roundTrip, inputVariableName)
}
}

private fun ioSymbolNames(op: OperationShape): Pair<String, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,30 +128,18 @@ object RuntimeConfigProperty {
""".trimIndent()
}

// TODO support a nice DSL for this so that callers don't have to be aware of `DefaultTracer` if they don't want
// to, they can just call `SomeClient { tracer { clientName = "Foo" } }` in the simple case.
// We could do this as an extension in the runtime off TracingClientConfig.Builder type...
val Tracer = ConfigProperty {
symbol = RuntimeTypes.Tracing.Core.Tracer
baseClass = RuntimeTypes.Tracing.Core.TracingClientConfig
var TelemetryProvider = ConfigProperty {
symbol = RuntimeTypes.Observability.TelemetryApi.TelemetryProvider
baseClass = RuntimeTypes.Observability.TelemetryApi.TelemetryConfig
useNestedBuilderBaseClass()
additionalImports = listOf(RuntimeTypes.Observability.TelemetryDefaults.Global)

documentation = """
The tracer that is responsible for creating trace spans and wiring them up to a tracing backend (e.g.,
a trace probe). By default, this will create a standard tracer that uses the service name for the root
trace span and delegates to a logging trace probe (i.e.,
`DefaultTracer(LoggingTraceProbe, "<service-name>")`).
The telemetry provider used to instrument the SDK operations with. By default, the global telemetry
provider will be used.
""".trimIndent()
propertyType = ConfigPropertyType.Custom(
render = { prop, writer ->
writer.write(
"""override val #1L: Tracer = builder.#1L ?: #2T(#3T, clientName)""",
prop.propertyName,
RuntimeTypes.Tracing.Core.DefaultTracer,
RuntimeTypes.Tracing.Core.LoggingTraceProbe,
)
},
)

propertyType = ConfigPropertyType.RequiredWithDefault("TelemetryProvider.Global")
}

val HttpInterceptors = ConfigProperty {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ServiceClientConfigGeneratorTest {
contents.assertBalancedBracesAndParens()

val expectedCtor = """
public class Config private constructor(builder: Builder) : HttpAuthConfig, HttpClientConfig, HttpEngineConfig by builder.buildHttpEngineConfig(), IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TracingClientConfig {
public class Config private constructor(builder: Builder) : HttpAuthConfig, HttpClientConfig, HttpEngineConfig by builder.buildHttpEngineConfig(), IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TelemetryConfig {
"""
contents.shouldContainWithDiff(expectedCtor)

Expand All @@ -54,12 +54,12 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
override val interceptors: kotlin.collections.List<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = builder.interceptors
override val logMode: LogMode = builder.logMode ?: LogMode.Default
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)
override val telemetryProvider: TelemetryProvider = builder.telemetryProvider ?: TelemetryProvider.Global
"""
contents.shouldContainWithDiff(expectedProps)

val expectedBuilder = """
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, RetryClientConfig.Builder, RetryStrategyClientConfig.Builder by RetryStrategyClientConfigImpl.BuilderImpl(), SdkClientConfig.Builder<Config>, TracingClientConfig.Builder {
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, RetryClientConfig.Builder, RetryStrategyClientConfig.Builder by RetryStrategyClientConfigImpl.BuilderImpl(), SdkClientConfig.Builder<Config>, TelemetryConfig.Builder {
/**
* A reader-friendly name for the client.
*/
Expand Down Expand Up @@ -115,12 +115,10 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
override var retryPolicy: RetryPolicy<Any?>? = null

/**
* The tracer that is responsible for creating trace spans and wiring them up to a tracing backend (e.g.,
* a trace probe). By default, this will create a standard tracer that uses the service name for the root
* trace span and delegates to a logging trace probe (i.e.,
* `DefaultTracer(LoggingTraceProbe, "<service-name>")`).
* The telemetry provider used to instrument the SDK operations with. By default, the global telemetry
* provider will be used.
*/
override var tracer: Tracer? = null
override var telemetryProvider: TelemetryProvider? = null

override fun build(): Config = Config(this)
}
Expand Down Expand Up @@ -248,7 +246,7 @@ public class Config private constructor(builder: Builder) {
override val interceptors: kotlin.collections.List<aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor> = builder.interceptors
override val logMode: LogMode = builder.logMode ?: LogMode.LogRequest
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)"""
override val telemetryProvider: TelemetryProvider = builder.telemetryProvider ?: TelemetryProvider.Global"""
contents.shouldContainWithDiff(expectedConfigValues)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class ServiceClientGeneratorTest {

@Test
fun `it generates config`() {
val expected = "public class Config private constructor(builder: Builder) : IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TracingClientConfig"
val expected = "public class Config private constructor(builder: Builder) : IdempotencyTokenConfig, RetryClientConfig, RetryStrategyClientConfig by builder.buildRetryStrategyClientConfig(), SdkClientConfig, TelemetryConfig"
commonTestContents.shouldContainOnlyOnce(expected)
}

Expand Down
Loading