Skip to content

Commit 145ec12

Browse files
committed
Merge branch 'main' into servlet3-library
2 parents 7c9eb95 + 68b7715 commit 145ec12

File tree

20 files changed

+266
-75
lines changed

20 files changed

+266
-75
lines changed

.github/workflows/build-daily.yml

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,44 @@ jobs:
3131
markdown-lint-check:
3232
uses: ./.github/workflows/reusable-markdown-lint-check.yml
3333

34+
publish-snapshots:
35+
needs:
36+
- common
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
40+
41+
- name: Free disk space
42+
run: .github/scripts/gha-free-disk-space.sh
43+
44+
- name: Set up JDK for running Gradle
45+
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
46+
with:
47+
distribution: temurin
48+
java-version-file: .java-version
49+
50+
- name: Setup Gradle
51+
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
52+
53+
- name: Build and publish artifact snapshots
54+
env:
55+
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
56+
SONATYPE_USER: ${{ secrets.SONATYPE_USER }}
57+
SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }}
58+
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
59+
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
60+
run: ./gradlew assemble spdxSbom publishToSonatype
61+
62+
- name: Build and publish gradle plugin snapshots
63+
env:
64+
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
65+
SONATYPE_USER: ${{ secrets.SONATYPE_USER }}
66+
SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }}
67+
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
68+
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
69+
run: ./gradlew build publishToSonatype
70+
working-directory: gradle-plugins
71+
3472
workflow-notification:
3573
permissions:
3674
contents: read
@@ -40,6 +78,7 @@ jobs:
4078
- test-latest-deps
4179
- muzzle
4280
- link-check
81+
- publish-snapshots
4382
if: always()
4483
uses: ./.github/workflows/reusable-workflow-notification.yml
4584
with:
@@ -48,5 +87,6 @@ jobs:
4887
needs.common.result == 'success' &&
4988
needs.test-latest-deps.result == 'success' &&
5089
needs.muzzle.result == 'success' &&
51-
needs.link-check.result == 'success'
90+
needs.link-check.result == 'success' &&
91+
needs.publish-snapshots.result == 'success'
5292
}}

.github/workflows/build.yml

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,44 +45,3 @@ jobs:
4545
# release branches are excluded
4646
if: "!startsWith(github.ref_name, 'release/')"
4747
uses: ./.github/workflows/reusable-markdown-lint-check.yml
48-
49-
publish-snapshots:
50-
needs:
51-
# intentionally not blocking snapshot publishing on test-latest-deps, muzzle, or link-check
52-
- common
53-
runs-on: ubuntu-latest
54-
# skipping release branches because the versions in those branches are not snapshots
55-
if: github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-instrumentation'
56-
steps:
57-
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
58-
59-
- name: Free disk space
60-
run: .github/scripts/gha-free-disk-space.sh
61-
62-
- name: Set up JDK for running Gradle
63-
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
64-
with:
65-
distribution: temurin
66-
java-version-file: .java-version
67-
68-
- name: Setup Gradle
69-
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
70-
71-
- name: Build and publish artifact snapshots
72-
env:
73-
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
74-
SONATYPE_USER: ${{ secrets.SONATYPE_USER }}
75-
SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }}
76-
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
77-
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
78-
run: ./gradlew assemble spdxSbom publishToSonatype
79-
80-
- name: Build and publish gradle plugin snapshots
81-
env:
82-
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
83-
SONATYPE_USER: ${{ secrets.SONATYPE_USER }}
84-
SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }}
85-
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
86-
GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }}
87-
run: ./gradlew build publishToSonatype
88-
working-directory: gradle-plugins

.github/workflows/issue-management-stale-action.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ jobs:
5656
operations-per-run: 1000
5757

5858
# Action #3: Handle stale PRs
59-
# - After 180 days inactive: Adds "stale" label + warning comment
59+
# - After 90 days inactive: Adds "stale" label + warning comment
6060
# - After 14 more days inactive: Closes
6161
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
6262
with:
6363
days-before-issue-stale: -1
6464
days-before-issue-close: -1
65-
days-before-pr-stale: 180
65+
days-before-pr-stale: 90
6666
days-before-pr-close: 14
6767
stale-pr-label: stale
6868
stale-pr-message: >

.github/workflows/pr-automation-comments.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
"",
4343
"### Checklist",
4444
"- [ ] Migration notes added to the PR description",
45-
"- [ ] Breaking change is documented in the changelog entry for the next release",
45+
"- [ ] Breaking change is documented in the "Unreleased" section of the [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md), preferably as part of this PR.",
4646
"- [ ] Consider if this change requires a major version bump",
4747
"",
4848
"Your migration notes will be included in the release notes to help users upgrade smoothly. The more detailed and helpful they are, the better the user experience will be.",

conventions/src/main/kotlin/otel.java-conventions.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ dependencies {
159159
compileOnly("com.google.code.findbugs:jsr305")
160160
compileOnly("com.google.errorprone:error_prone_annotations")
161161

162-
codenarc("org.codenarc:CodeNarc:3.6.0")
162+
codenarc("org.codenarc:CodeNarc:3.7.0")
163163
codenarc(platform("org.codehaus.groovy:groovy-bom:3.0.25"))
164164

165165
modules {

dependencyManagement/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ val DEPENDENCIES = listOf(
8585
"io.r2dbc:r2dbc-proxy:1.1.6.RELEASE",
8686
"ch.qos.logback:logback-classic:1.3.16", // 1.4+ requires Java 11+
8787
"uk.org.webcompere:system-stubs-jupiter:2.0.3",
88-
"com.uber.nullaway:nullaway:0.12.12",
88+
"com.uber.nullaway:nullaway:0.12.13",
8989
"commons-beanutils:commons-beanutils:1.11.0",
9090
"commons-cli:commons-cli:1.11.0",
9191
"commons-codec:commons-codec:1.20.0",

instrumentation/ktor/ktor-2-common/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/common/internal/KtorServerTelemetryUtil.kt

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package io.opentelemetry.instrumentation.ktor.v2_0.common.internal
77

88
import io.ktor.server.application.*
9+
import io.ktor.server.application.hooks.*
910
import io.ktor.server.request.*
1011
import io.ktor.server.response.*
1112
import io.ktor.util.*
@@ -25,14 +26,15 @@ import kotlinx.coroutines.withContext
2526
*/
2627
object KtorServerTelemetryUtil {
2728

28-
fun configureTelemetry(builder: AbstractKtorServerTelemetryBuilder, application: Application) {
29+
fun PluginBuilder<*>.configureTelemetry(builder: AbstractKtorServerTelemetryBuilder, application: Application) {
2930
val contextKey = AttributeKey<Context>("OpenTelemetry")
3031
val errorKey = AttributeKey<Throwable>("OpenTelemetryException")
32+
val processedKey = AttributeKey<Unit>("OpenTelemetryProcessed")
3133

3234
val instrumenter = instrumenter(builder)
3335
val tracer = KtorServerTracer(instrumenter)
34-
val startPhase = PipelinePhase("OpenTelemetry")
3536

37+
val startPhase = PipelinePhase("OpenTelemetry")
3638
application.insertPhaseBefore(ApplicationCallPipeline.Setup, startPhase)
3739
application.intercept(startPhase) {
3840
val context = tracer.start(call)
@@ -59,22 +61,15 @@ object KtorServerTelemetryUtil {
5961
}
6062
}
6163

62-
val postSendPhase = PipelinePhase("OpenTelemetryPostSend")
63-
application.sendPipeline.insertPhaseAfter(ApplicationSendPipeline.After, postSendPhase)
64-
application.sendPipeline.intercept(postSendPhase) {
64+
on(ResponseSent) { call ->
65+
if (call.attributes.contains(processedKey)) {
66+
return@on
67+
}
68+
6569
val context = call.attributes.getOrNull(contextKey)
6670
if (context != null) {
67-
var error: Throwable? = call.attributes.getOrNull(errorKey)
68-
try {
69-
proceed()
70-
} catch (t: Throwable) {
71-
error = t
72-
throw t
73-
} finally {
74-
tracer.end(context, call, error)
75-
}
76-
} else {
77-
proceed()
71+
tracer.end(context, call, call.attributes.getOrNull(errorKey))
72+
call.attributes.put(processedKey, Unit)
7873
}
7974
}
8075
}

instrumentation/ktor/ktor-2.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v2_0/KtorServerTelemetryBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute
1212
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource
1313
import io.opentelemetry.instrumentation.ktor.v2_0.InstrumentationProperties.INSTRUMENTATION_NAME
1414
import io.opentelemetry.instrumentation.ktor.v2_0.common.AbstractKtorServerTelemetryBuilder
15-
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.KtorServerTelemetryUtil
15+
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.KtorServerTelemetryUtil.configureTelemetry
1616

1717
class KtorServerTelemetryBuilder internal constructor(
1818
instrumentationName: String
@@ -21,7 +21,7 @@ class KtorServerTelemetryBuilder internal constructor(
2121
val KtorServerTelemetry = createRouteScopedPlugin("OpenTelemetry", { KtorServerTelemetryBuilder(INSTRUMENTATION_NAME) }) {
2222
require(pluginConfig.isOpenTelemetryInitialized()) { "OpenTelemetry must be set" }
2323

24-
KtorServerTelemetryUtil.configureTelemetry(pluginConfig, application)
24+
configureTelemetry(pluginConfig, application)
2525

2626
application.environment.monitor.subscribe(Routing.RoutingCallStarted) { call ->
2727
HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg!!.route.parent.toString() }, call)

instrumentation/ktor/ktor-3.0/library/src/main/kotlin/io/opentelemetry/instrumentation/ktor/v3_0/KtorServerTelemetryBuilder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import io.opentelemetry.context.Context
1111
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute
1212
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource
1313
import io.opentelemetry.instrumentation.ktor.v2_0.common.AbstractKtorServerTelemetryBuilder
14-
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.KtorServerTelemetryUtil
14+
import io.opentelemetry.instrumentation.ktor.v2_0.common.internal.KtorServerTelemetryUtil.configureTelemetry
1515
import io.opentelemetry.instrumentation.ktor.v3_0.InstrumentationProperties.INSTRUMENTATION_NAME
1616

1717
class KtorServerTelemetryBuilder internal constructor(
@@ -21,7 +21,7 @@ class KtorServerTelemetryBuilder internal constructor(
2121
val KtorServerTelemetry = createRouteScopedPlugin("OpenTelemetry", { KtorServerTelemetryBuilder(INSTRUMENTATION_NAME) }) {
2222
require(pluginConfig.isOpenTelemetryInitialized()) { "OpenTelemetry must be set" }
2323

24-
KtorServerTelemetryUtil.configureTelemetry(pluginConfig, application)
24+
configureTelemetry(pluginConfig, application)
2525

2626
application.monitor.subscribe(RoutingRoot.RoutingCallStarted) { call ->
2727
HttpServerRoute.update(Context.current(), HttpServerRouteSource.SERVER, { _, arg -> arg!!.route.parent.toString() }, call)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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.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
17+
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerUsingTest
18+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension
19+
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
20+
import io.opentelemetry.sdk.metrics.data.MetricData
21+
import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions
22+
import io.opentelemetry.semconv.HttpAttributes
23+
import io.opentelemetry.semconv.UrlAttributes
24+
import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest
25+
import io.opentelemetry.testing.internal.armeria.common.HttpMethod
26+
import org.assertj.core.api.ThrowingConsumer
27+
import org.junit.jupiter.api.AfterAll
28+
import org.junit.jupiter.api.BeforeAll
29+
import org.junit.jupiter.api.extension.RegisterExtension
30+
import org.junit.jupiter.params.ParameterizedTest
31+
import org.junit.jupiter.params.provider.Arguments
32+
import org.junit.jupiter.params.provider.Arguments.arguments
33+
import org.junit.jupiter.params.provider.MethodSource
34+
import java.util.concurrent.TimeUnit
35+
import java.util.stream.Stream
36+
37+
class KtorServerMetricsTest : AbstractHttpServerUsingTest<EmbeddedServer<*, *>>() {
38+
companion object {
39+
@JvmStatic
40+
@RegisterExtension
41+
val testing: InstrumentationExtension = HttpServerInstrumentationExtension.forLibrary()
42+
}
43+
44+
private val errorDuringSendEndpoint = ServerEndpoint("errorDuringSend", "error-during-send", 500, "")
45+
private val errorAfterSendEndpoint = ServerEndpoint("errorAfterSend", "error-after-send", 200, "")
46+
47+
@BeforeAll
48+
fun setupOptions() {
49+
startServer()
50+
}
51+
52+
@AfterAll
53+
fun cleanup() {
54+
cleanupServer()
55+
}
56+
57+
override fun getContextPath() = ""
58+
59+
override fun setupServer(): EmbeddedServer<*, *> = embeddedServer(Netty, port = port) {
60+
install(KtorServerTelemetry) {
61+
setOpenTelemetry(testing.openTelemetry)
62+
Experimental.emitExperimentalTelemetry(this)
63+
}
64+
65+
routing {
66+
get(errorDuringSendEndpoint.path) {
67+
call.respondBytesWriter {
68+
throw IllegalArgumentException("exception")
69+
}
70+
}
71+
get(errorAfterSendEndpoint.path) {
72+
call.respondText(errorAfterSendEndpoint.body, status = HttpStatusCode.fromValue(errorAfterSendEndpoint.status))
73+
throw IllegalArgumentException("exception")
74+
}
75+
}
76+
}.start()
77+
78+
override fun stopServer(server: EmbeddedServer<*, *>) {
79+
server.stop(0, 10, TimeUnit.SECONDS)
80+
}
81+
82+
// regression test for
83+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/15303
84+
// verify that active requests are counted correctly when there is a send error
85+
@ParameterizedTest
86+
@MethodSource("provideArguments")
87+
fun testActiveRequestsMetric(endpoint: ServerEndpoint) {
88+
val request = AggregatedHttpRequest.of(HttpMethod.valueOf("GET"), resolveAddress(endpoint))
89+
try {
90+
client.execute(request).aggregate().join()
91+
} catch (_: Throwable) {
92+
// we expect server error
93+
}
94+
95+
testing.waitAndAssertMetrics(
96+
INSTRUMENTATION_NAME,
97+
"http.server.active_requests"
98+
) { metrics ->
99+
metrics!!.anySatisfy(ThrowingConsumer { metric: MetricData? ->
100+
OpenTelemetryAssertions.assertThat(metric)
101+
.hasDescription("Number of active HTTP server requests.")
102+
.hasUnit("{requests}")
103+
.hasLongSumSatisfying { sum ->
104+
sum.hasPointsSatisfying({ point ->
105+
point.hasValue(0)
106+
.hasAttributesSatisfying {
107+
OpenTelemetryAssertions.equalTo(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
108+
OpenTelemetryAssertions.equalTo(UrlAttributes.URL_PATH, endpoint.path)
109+
}
110+
})
111+
}
112+
})
113+
}
114+
}
115+
116+
private fun provideArguments(): Stream<Arguments> = Stream.of(
117+
arguments(errorDuringSendEndpoint),
118+
arguments(errorAfterSendEndpoint),
119+
)
120+
}

0 commit comments

Comments
 (0)