Skip to content

refactor: instrument using new telemetry APIs #866

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 4 commits into from
Jun 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ 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 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 @@ -308,15 +308,15 @@ 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")
}
}

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")

Comment on lines +67 to +71
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Why did these move? Should ServiceApiVersion move as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't remember now but there was a reason 🤔 . ServiceApiVersion is AWS specific, the other two are not.

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,8 @@ abstract class HttpProtocolClientGenerator(
val Operation: SectionKey<OperationShape> = SectionKey("Operation")
}

object OperationSpanAttributes : SectionId

/**
* Render the implementation of the service client interface
*/
Expand Down Expand Up @@ -71,6 +73,8 @@ abstract class HttpProtocolClientGenerator(
.write("")
.call { renderClose(writer) }
.write("")
.call { renderInstrumentOperation(writer) }
.write("")
.call { renderAdditionalMethods(writer) }
.closeBlock("}")
.write("")
Expand Down Expand Up @@ -249,24 +253,45 @@ 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,
writer.write("val (span, telemetryCtx) = instrumentOperation(#S)", op.id.name)
writer.withBlock("return #T(span, telemetryCtx) {", "}", RuntimeTypes.Observability.TelemetryApi.withSpan) {
if (hasOutputStream) {
write("op.#T(client, #L, block)", RuntimeTypes.HttpClient.Operation.execute, inputVariableName)
} else {
write("op.#T(client, #L)", RuntimeTypes.HttpClient.Operation.roundTrip, inputVariableName)
}
}
}

private fun renderInstrumentOperation(writer: KotlinWriter) {
writer.withBlock(
"private fun instrumentOperation(operation: #T): Pair<#T, #T> {",
"}",
KotlinTypes.String,
RuntimeTypes.Observability.TelemetryApi.TraceSpan,
KotlinTypes.Coroutines.CoroutineContext,
) {
write("val tracer = config.telemetryProvider.tracerProvider.getOrCreateTracer(config.clientName)")
write("val parentCtx = config.telemetryProvider.contextManager.current()")
write(
"val telemetryCtx = #T(config.telemetryProvider) + #T(parentCtx)",
RuntimeTypes.Observability.TelemetryApi.TelemetryProviderContext,
RuntimeTypes.Observability.TelemetryApi.TelemetryContextElement,
)
.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)

withBlock("val span = tracer.createSpan(", ")") {
write("#S,", "\${ServiceId}.\${operation}")
withBlock("#T{", "},", RuntimeTypes.Core.Utils.attributesOf) {
write("#S to ServiceId", "rpc.service")
write("#S to operation", "rpc.method")
writer.declareSection(OperationSpanAttributes)
}
write("#T.CLIENT,", RuntimeTypes.Observability.TelemetryApi.SpanKind)
write("parentCtx,")
}

write("return span to telemetryCtx")
}
}

private fun ioSymbolNames(op: OperationShape): Pair<String, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,30 +140,17 @@ 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()

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 this will be a no-op
implementation.
""".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.None")
}

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, SdkClientConfig, TracingClientConfig {
public class Config private constructor(builder: Builder) : HttpAuthConfig, HttpClientConfig, HttpEngineConfig by builder.buildHttpEngineConfig(), IdempotencyTokenConfig, SdkClientConfig, TelemetryConfig {
"""
contents.shouldContainWithDiff(expectedCtor)

Expand All @@ -55,12 +55,12 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
override val logMode: LogMode = builder.logMode ?: LogMode.Default
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
override val retryStrategy: RetryStrategy = builder.retryStrategy ?: StandardRetryStrategy()
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)
override val telemetryProvider: TelemetryProvider = builder.telemetryProvider ?: TelemetryProvider.None
"""
contents.shouldContainWithDiff(expectedProps)

val expectedBuilder = """
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, SdkClientConfig.Builder<Config>, TracingClientConfig.Builder {
public class Builder : HttpAuthConfig.Builder, HttpClientConfig.Builder, HttpEngineConfig.Builder by HttpEngineConfigImpl.BuilderImpl(), IdempotencyTokenConfig.Builder, SdkClientConfig.Builder<Config>, TelemetryConfig.Builder {
/**
* A reader-friendly name for the client.
*/
Expand Down Expand Up @@ -122,12 +122,10 @@ public class Config private constructor(builder: Builder) : HttpAuthConfig, Http
override var retryStrategy: RetryStrategy? = 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 this will be a no-op
* implementation.
*/
override var tracer: Tracer? = null
override var telemetryProvider: TelemetryProvider? = null

override fun build(): Config = Config(this)
}
Expand Down Expand Up @@ -256,7 +254,7 @@ public class Config private constructor(builder: Builder) {
override val logMode: LogMode = builder.logMode ?: LogMode.LogRequest
override val retryPolicy: RetryPolicy<Any?> = builder.retryPolicy ?: StandardRetryPolicy.Default
override val retryStrategy: RetryStrategy = builder.retryStrategy ?: StandardRetryStrategy()
override val tracer: Tracer = builder.tracer ?: DefaultTracer(LoggingTraceProbe, clientName)"""
override val telemetryProvider: TelemetryProvider = builder.telemetryProvider ?: TelemetryProvider.None"""
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, SdkClientConfig, TracingClientConfig"
val expected = "public class Config private constructor(builder: Builder) : IdempotencyTokenConfig, SdkClientConfig, TelemetryConfig"
commonTestContents.shouldContainOnlyOnce(expected)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ class HttpProtocolClientGeneratorTest {
commonTestContents.shouldContainOnlyOnceWithDiff("import ${KotlinDependency.HTTP.namespace}.operation.context")
commonTestContents.shouldContainOnlyOnceWithDiff("import ${KotlinDependency.HTTP.namespace}.operation.execute")
commonTestContents.shouldContainOnlyOnceWithDiff("import ${KotlinDependency.HTTP.namespace}.operation.roundTrip")
commonTestContents.shouldContainOnlyOnceWithDiff("import ${KotlinDependency.HTTP.namespace}.operation.sdkRequestId")
commonTestContents.shouldContainOnlyOnceWithDiff("import ${KotlinDependency.TRACING_CORE.namespace}.withRootTraceSpan")
}

@Test
Expand Down Expand Up @@ -89,8 +87,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFoo-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFoo")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -110,8 +108,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooNoInput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooNoInput")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -131,8 +129,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooNoOutput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooNoOutput")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -152,8 +150,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooStreamingInput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooStreamingInput")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -173,8 +171,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooStreamingOutput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooStreamingOutput")
return withSpan(span, telemetryCtx) {
op.execute(client, input, block)
}
}
Expand All @@ -194,8 +192,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooStreamingOutputNoInput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooStreamingOutputNoInput")
return withSpan(span, telemetryCtx) {
op.execute(client, input, block)
}
}
Expand All @@ -215,8 +213,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooStreamingInputNoOutput-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooStreamingInputNoOutput")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -236,8 +234,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooNoRequired-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooNoRequired")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand All @@ -257,8 +255,8 @@ class HttpProtocolClientGeneratorTest {
}
op.install(MockMiddleware(configurationField1 = "testing"))
op.interceptors.addAll(config.interceptors)
val rootSpan = config.tracer.createRootSpan("GetFooSomeRequired-${'$'}{op.context.sdkRequestId}")
return coroutineContext.withRootTraceSpan(rootSpan) {
val (span, telemetryCtx) = instrumentOperation("GetFooSomeRequired")
return withSpan(span, telemetryCtx) {
op.roundTrip(client, input)
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ kotlin.native.ignoreDisabledTargets=true
kotlin.mpp.enableCompatibilityMetadataVariant=true

# SDK
sdkVersion=0.21.2-SNAPSHOT
sdkVersion=0.22.0-SNAPSHOT

# kotlin
kotlinVersion=1.8.10
Expand Down
2 changes: 1 addition & 1 deletion runtime/auth/aws-credentials/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ kotlin {
// For Instant
api(project(":runtime:runtime-core"))
api(project(":runtime:auth:identity-api"))
implementation(project(":runtime:tracing:tracing-core"))
implementation(project(":runtime:observability:telemetry-api"))
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
package aws.smithy.kotlin.runtime.auth.awscredentials

import aws.smithy.kotlin.runtime.io.closeIfCloseable
import aws.smithy.kotlin.runtime.telemetry.logging.trace
import aws.smithy.kotlin.runtime.time.Clock
import aws.smithy.kotlin.runtime.tracing.trace
import aws.smithy.kotlin.runtime.util.Attributes
import aws.smithy.kotlin.runtime.util.CachedValue
import aws.smithy.kotlin.runtime.util.ExpiringValue
Expand Down
Loading