Skip to content

Commit 8481d9a

Browse files
authored
Fix ktor metric metadata (#15516)
1 parent 0005119 commit 8481d9a

File tree

7 files changed

+211
-30
lines changed

7 files changed

+211
-30
lines changed

docs/instrumentation-list.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7601,6 +7601,87 @@ libraries:
76017601
default: true
76027602
telemetry:
76037603
- when: default
7604+
metrics:
7605+
- name: http.client.request.duration
7606+
description: Duration of HTTP client requests.
7607+
type: HISTOGRAM
7608+
unit: s
7609+
attributes:
7610+
- name: http.request.method
7611+
type: STRING
7612+
- name: http.response.status_code
7613+
type: LONG
7614+
- name: network.protocol.version
7615+
type: STRING
7616+
- name: server.address
7617+
type: STRING
7618+
- name: server.port
7619+
type: LONG
7620+
- name: http.server.request.duration
7621+
description: Duration of HTTP server requests.
7622+
type: HISTOGRAM
7623+
unit: s
7624+
attributes:
7625+
- name: http.request.method
7626+
type: STRING
7627+
- name: http.response.status_code
7628+
type: LONG
7629+
- name: http.route
7630+
type: STRING
7631+
- name: network.protocol.version
7632+
type: STRING
7633+
- name: url.scheme
7634+
type: STRING
7635+
spans:
7636+
- span_kind: CLIENT
7637+
attributes:
7638+
- name: error.type
7639+
type: STRING
7640+
- name: http.request.method
7641+
type: STRING
7642+
- name: http.request.method_original
7643+
type: STRING
7644+
- name: http.request.resend_count
7645+
type: LONG
7646+
- name: http.response.status_code
7647+
type: LONG
7648+
- name: network.protocol.version
7649+
type: STRING
7650+
- name: server.address
7651+
type: STRING
7652+
- name: server.port
7653+
type: LONG
7654+
- name: url.full
7655+
type: STRING
7656+
- span_kind: SERVER
7657+
attributes:
7658+
- name: client.address
7659+
type: STRING
7660+
- name: error.type
7661+
type: STRING
7662+
- name: http.request.method
7663+
type: STRING
7664+
- name: http.request.method_original
7665+
type: STRING
7666+
- name: http.response.status_code
7667+
type: LONG
7668+
- name: http.route
7669+
type: STRING
7670+
- name: network.protocol.version
7671+
type: STRING
7672+
- name: server.address
7673+
type: STRING
7674+
- name: server.port
7675+
type: LONG
7676+
- name: url.path
7677+
type: STRING
7678+
- name: url.query
7679+
type: STRING
7680+
- name: url.scheme
7681+
type: STRING
7682+
- name: user_agent.original
7683+
type: STRING
7684+
- when: otel.instrumentation.http.server.emit-experimental-telemetry=true
76047685
metrics:
76057686
- name: http.client.request.duration
76067687
description: Duration of HTTP client requests.

instrumentation-docs/instrumentations.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ readonly INSTRUMENTATIONS=(
155155
"ktor:ktor-1.0:library:test"
156156
"ktor:ktor-2.0:library:test"
157157
"ktor:ktor-3.0:library:test"
158+
"ktor:ktor-3.0:library:testExperimental"
158159
"kubernetes-client-7.0:javaagent:test"
159160
"kubernetes-client-7.0:javaagent:testExperimental"
160161
"lettuce:lettuce-4.0:javaagent:test"

instrumentation/ktor/ktor-3.0/javaagent/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,16 @@ kotlin {
5454
javaParameters = true
5555
}
5656
}
57+
58+
tasks {
59+
val testExperimental by registering(Test::class) {
60+
testClassesDirs = sourceSets.test.get().output.classesDirs
61+
classpath = sourceSets.test.get().runtimeClasspath
62+
63+
jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true")
64+
}
65+
66+
check {
67+
dependsOn(testExperimental)
68+
}
69+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.ktor.v3_0
7+
8+
import io.ktor.server.application.Application
9+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension
10+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension
11+
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
12+
import org.junit.jupiter.api.extension.RegisterExtension
13+
14+
@EnabledIfSystemProperty(
15+
named = "otel.instrumentation.http.server.emit-experimental-telemetry",
16+
matches = "true"
17+
)
18+
class ServerMetricsTest : AbstractKtorServerMetricsTest() {
19+
companion object {
20+
@JvmStatic
21+
@RegisterExtension
22+
val testing: InstrumentationExtension = HttpServerInstrumentationExtension.forAgent()
23+
}
24+
25+
override fun serverInstall(application: Application) {
26+
// javaagent automatically instruments the application
27+
}
28+
29+
// For javaagent, HTTP server metrics are emitted by the Netty instrumentation
30+
// since Ktor runs on top of Netty as its HTTP engine
31+
override fun instrumentationName(): String = "io.opentelemetry.netty-4.1"
32+
}

instrumentation/ktor/ktor-3.0/library/build.gradle.kts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,21 @@ kotlin {
3333
}
3434
}
3535

36-
tasks.test {
37-
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
36+
tasks {
37+
withType<Test>().configureEach {
38+
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
39+
}
40+
41+
val testExperimental by registering(Test::class) {
42+
testClassesDirs = sourceSets.test.get().output.classesDirs
43+
classpath = sourceSets.test.get().runtimeClasspath
44+
45+
// this is used for enabling/disabling tests, library instrumentation doesn't use this flag
46+
jvmArgs("-Dotel.instrumentation.http.server.emit-experimental-telemetry=true")
47+
systemProperty("metadataConfig", "otel.instrumentation.http.server.emit-experimental-telemetry=true")
48+
}
49+
50+
check {
51+
dependsOn(testExperimental)
52+
}
3853
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.ktor.v3_0
7+
8+
import io.ktor.server.application.install
9+
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.Experimental
10+
import io.opentelemetry.instrumentation.ktor.v3_0.InstrumentationProperties.INSTRUMENTATION_NAME
11+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension
12+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension
13+
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
14+
import org.junit.jupiter.api.extension.RegisterExtension
15+
16+
@EnabledIfSystemProperty(
17+
named = "otel.instrumentation.http.server.emit-experimental-telemetry",
18+
matches = "true"
19+
)
20+
class ServerMetricsTest : AbstractKtorServerMetricsTest() {
21+
companion object {
22+
@JvmStatic
23+
@RegisterExtension
24+
val testing: InstrumentationExtension = HttpServerInstrumentationExtension.forLibrary()
25+
}
26+
27+
override fun serverInstall(application: io.ktor.server.application.Application) {
28+
application.install(KtorServerTelemetry) {
29+
setOpenTelemetry(testing.openTelemetry)
30+
Experimental.emitExperimentalTelemetry(this)
31+
}
32+
}
33+
34+
override fun instrumentationName(): String = INSTRUMENTATION_NAME
35+
}
Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,49 @@
55

66
package io.opentelemetry.instrumentation.ktor.v3_0
77

8-
import io.ktor.http.*
9-
import io.ktor.server.application.*
10-
import io.ktor.server.engine.*
11-
import io.ktor.server.netty.*
12-
import io.ktor.server.response.*
13-
import io.ktor.server.routing.*
14-
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.Experimental
15-
import io.opentelemetry.instrumentation.ktor.v3_0.InstrumentationProperties.INSTRUMENTATION_NAME
16-
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension
8+
import io.ktor.http.HttpStatusCode
9+
import io.ktor.server.application.Application
10+
import io.ktor.server.engine.EmbeddedServer
11+
import io.ktor.server.engine.embeddedServer
12+
import io.ktor.server.netty.Netty
13+
import io.ktor.server.response.respondBytesWriter
14+
import io.ktor.server.response.respondText
15+
import io.ktor.server.routing.get
16+
import io.ktor.server.routing.routing
1717
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest
18-
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension
1918
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
2019
import io.opentelemetry.sdk.metrics.data.MetricData
21-
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
20+
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat
21+
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo
2222
import io.opentelemetry.semconv.HttpAttributes
2323
import io.opentelemetry.semconv.UrlAttributes
2424
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest
2525
import io.opentelemetry.testing.internal.armeria.common.HttpMethod
2626
import org.assertj.core.api.ThrowingConsumer
2727
import org.junit.jupiter.api.AfterAll
2828
import org.junit.jupiter.api.BeforeAll
29-
import org.junit.jupiter.api.extension.RegisterExtension
3029
import org.junit.jupiter.params.ParameterizedTest
3130
import org.junit.jupiter.params.provider.Arguments
3231
import org.junit.jupiter.params.provider.Arguments.arguments
3332
import org.junit.jupiter.params.provider.MethodSource
3433
import java.util.concurrent.TimeUnit
3534
import java.util.stream.Stream
3635

37-
class KtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>() {
38-
companion object {
39-
@JvmStatic
40-
@RegisterExtension
41-
val testing: InstrumentationExtension = HttpServerInstrumentationExtension.forLibrary()
42-
}
36+
/**
37+
* Abstract test class for testing experimental HTTP server metrics (http.server.active_requests).
38+
* This is a regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/15303
39+
*
40+
* Subclasses should use @EnabledIfSystemProperty to ensure tests only run when
41+
* experimental flag is enabled.
42+
*/
43+
abstract class AbstractKtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>() {
4344

4445
private val errorDuringSendEndpoint = ServerEndpoint("errorDuringSend", "error-during-send", 500, "")
4546
private val errorAfterSendEndpoint = ServerEndpoint("errorAfterSend", "error-after-send", 200, "")
4647

48+
abstract fun serverInstall(application: Application)
49+
abstract fun instrumentationName(): String
50+
4751
@BeforeAll
4852
fun setupOptions() {
4953
startServer()
@@ -57,10 +61,7 @@ class KtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>(
5761
override fun getContextPath() = ""
5862

5963
override fun setupServer(): EmbeddedServer<*, *> = embeddedServer(Netty, port = port) {
60-
install(KtorServerTelemetry) {
61-
setOpenTelemetry(testing.openTelemetry)
62-
Experimental.emitExperimentalTelemetry(this)
63-
}
64+
serverInstall(this)
6465

6566
routing {
6667
get(errorDuringSendEndpoint.path) {
@@ -69,7 +70,10 @@ class KtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>(
6970
}
7071
}
7172
get(errorAfterSendEndpoint.path) {
72-
call.respondText(errorAfterSendEndpoint.body, status = HttpStatusCode.fromValue(errorAfterSendEndpoint.status))
73+
call.respondText(
74+
errorAfterSendEndpoint.body,
75+
status = HttpStatusCode.fromValue(errorAfterSendEndpoint.status)
76+
)
7377
throw IllegalArgumentException("exception")
7478
}
7579
}
@@ -92,20 +96,20 @@ class KtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>(
9296
// we expect server error
9397
}
9498

95-
testing.waitAndAssertMetrics(
96-
INSTRUMENTATION_NAME,
99+
testing().waitAndAssertMetrics(
100+
instrumentationName(),
97101
"http.server.active_requests"
98102
) { metrics ->
99103
metrics!!.anySatisfy(ThrowingConsumer { metric: MetricData? ->
100-
OpenTelemetryAssertions.assertThat(metric)
104+
assertThat(metric)
101105
.hasDescription("Number of active HTTP server requests.")
102106
.hasUnit("{requests}")
103107
.hasLongSumSatisfying { sum ->
104108
sum.hasPointsSatisfying({ point ->
105109
point.hasValue(0)
106110
.hasAttributesSatisfying {
107-
OpenTelemetryAssertions.equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
108-
OpenTelemetryAssertions.equalTo(UrlAttributes.URL_PATH, endpoint.path)
111+
equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
112+
equalTo(UrlAttributes.URL_PATH, endpoint.path)
109113
}
110114
})
111115
}

0 commit comments

Comments
 (0)