Skip to content

Commit a5b5a0e

Browse files
committed
debugging
1 parent 26fd7f4 commit a5b5a0e

File tree

14 files changed

+831
-5
lines changed

14 files changed

+831
-5
lines changed

instrumentation/spring/spring-webflux/spring-webflux-5.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webflux/v5_0/server/HandlerAdapterInstrumentation.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ public ElementMatcher<TypeDescription> typeMatcher() {
4747

4848
@Override
4949
public void transform(TypeTransformer transformer) {
50+
// Get the class name being transformed - this requires accessing transformer internals
51+
// For now, just log that we're transforming
52+
System.out.println("=== HandlerAdapterInstrumentation.transform() called ===");
53+
System.out.println(" Applying advice to 'handle' method");
5054
transformer.applyAdviceToMethod(
5155
isMethod()
5256
.and(isPublic())
@@ -55,6 +59,7 @@ public void transform(TypeTransformer transformer) {
5559
.and(takesArgument(1, Object.class))
5660
.and(takesArguments(2)),
5761
this.getClass().getName() + "$HandleAdvice");
62+
System.out.println("=== HandlerAdapterInstrumentation advice applied ===");
5863
}
5964

6065
@SuppressWarnings("unused")
@@ -71,7 +76,13 @@ private AdviceScope(Context context, Scope scope) {
7176

7277
@Nullable
7378
public static AdviceScope enter(ServerWebExchange exchange, Object handler) {
79+
System.out.println("=== HandlerAdapter.enter() called ===");
80+
System.out.println(
81+
" Handler: " + (handler != null ? handler.getClass().getName() : "null"));
82+
System.out.println(" Thread: " + Thread.currentThread().getName());
83+
7484
Context parentContext = Context.current();
85+
System.out.println(" Parent context: " + parentContext);
7586

7687
// HttpRouteSource.CONTROLLER has useFirst true, and it will update http.route only once
7788
// using the last portion of the nested path.
@@ -82,13 +93,20 @@ public static AdviceScope enter(ServerWebExchange exchange, Object handler) {
8293
parentContext, HttpServerRouteSource.NESTED_CONTROLLER, httpRouteGetter(), exchange);
8394

8495
if (handler == null) {
96+
System.out.println(" Handler is null, returning null");
8597
return null;
8698
}
8799

88-
if (!instrumenter().shouldStart(parentContext, handler)) {
100+
boolean shouldStart = instrumenter().shouldStart(parentContext, handler);
101+
System.out.println(" shouldStart: " + shouldStart);
102+
103+
if (!shouldStart) {
104+
System.out.println(" instrumenter().shouldStart returned false, returning null");
89105
return null;
90106
}
107+
91108
Context context = instrumenter().start(parentContext, handler);
109+
System.out.println(" Started new context: " + context);
92110
return new AdviceScope(context, context.makeCurrent());
93111
}
94112

@@ -114,10 +132,13 @@ public Mono<HandlerResult> exit(
114132
}
115133

116134
@Nullable
117-
@Advice.OnMethodEnter(suppress = Throwable.class)
135+
@Advice.OnMethodEnter
118136
public static AdviceScope methodEnter(
119137
@Advice.Argument(0) ServerWebExchange exchange, @Advice.Argument(1) Object handler) {
120-
return AdviceScope.enter(exchange, handler);
138+
System.out.println("=== HandlerAdapter.methodEnter() CALLED ===");
139+
AdviceScope scope = AdviceScope.enter(exchange, handler);
140+
System.out.println(" Returning AdviceScope: " + scope);
141+
return scope;
121142
}
122143

123144
@AssignReturned.ToReturned
@@ -129,11 +150,18 @@ public static Mono<HandlerResult> methodExit(
129150
@Advice.Thrown Throwable throwable,
130151
@Advice.Enter @Nullable AdviceScope adviceScope) {
131152

153+
System.out.println("=== HandlerAdapter.methodExit() CALLED ===");
154+
System.out.println(" AdviceScope: " + adviceScope);
155+
System.out.println(" Throwable: " + throwable);
156+
132157
if (adviceScope == null) {
158+
System.out.println(" AdviceScope is null, returning original mono");
133159
return mono;
134160
}
135161

136-
return adviceScope.exit(throwable, exchange, handler, mono);
162+
Mono<HandlerResult> result = adviceScope.exit(throwable, exchange, handler, mono);
163+
System.out.println(" Returning wrapped mono");
164+
return result;
137165
}
138166
}
139167
}

instrumentation/spring/spring-webflux/spring-webflux-5.0/testing-webflux7/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies {
1212
testInstrumentation(project(":instrumentation:reactor:reactor-netty:reactor-netty-1.0:javaagent"))
1313

1414
testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.0:testing"))
15+
testImplementation(project(":instrumentation:spring:spring-webflux:spring-webflux-5.3:testing"))
1516

1617
testImplementation("org.springframework.boot:spring-boot-starter-webflux:4.0.0")
1718
testImplementation("org.springframework.boot:spring-boot-starter-test:4.0.0")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0;
7+
8+
import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory;
9+
import org.springframework.boot.test.context.SpringBootTest;
10+
import org.springframework.boot.test.context.TestConfiguration;
11+
import org.springframework.context.annotation.Bean;
12+
import reactor.netty.resources.LoopResources;
13+
import server.SpringWebFluxTestApplication;
14+
15+
/**
16+
* Run all Webflux tests under netty event loop having only 1 thread. Some of the bugs are better
17+
* visible in this setup because same thread is reused for different requests.
18+
*/
19+
@SpringBootTest(
20+
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
21+
classes = {
22+
SpringWebFluxTestApplication.class,
23+
SpringThreadedSpringWebfluxTest.ForceSingleThreadedNettyAutoConfiguration.class
24+
})
25+
class SpringThreadedSpringWebfluxTest extends SpringWebfluxTest {
26+
27+
@TestConfiguration
28+
static class ForceSingleThreadedNettyAutoConfiguration {
29+
@Bean
30+
NettyReactiveWebServerFactory nettyFactory() {
31+
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
32+
// Configure single-threaded event loop for Spring Boot 4
33+
factory.addServerCustomizers(
34+
server -> server.runOn(LoopResources.create("my-http", 1, true)));
35+
return factory;
36+
}
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.client;
7+
8+
import io.opentelemetry.instrumentation.spring.webflux.client.AbstractSpringWebfluxClientInstrumentationTest;
9+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
10+
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension;
11+
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
12+
import org.junit.jupiter.api.condition.OS;
13+
import org.junit.jupiter.api.extension.RegisterExtension;
14+
import org.springframework.web.reactive.function.client.WebClient;
15+
16+
class SpringWebfluxClientInstrumentationTest
17+
extends AbstractSpringWebfluxClientInstrumentationTest {
18+
19+
@RegisterExtension
20+
static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();
21+
22+
@Override
23+
protected WebClient.Builder instrument(WebClient.Builder builder) {
24+
return builder;
25+
}
26+
27+
@Override
28+
protected void configure(HttpClientTestOptions.Builder optionsBuilder) {
29+
super.configure(optionsBuilder);
30+
31+
// Disable remote connection tests on Windows due to reactor-netty creating extra spans
32+
if (OS.WINDOWS.isCurrentOs()) {
33+
optionsBuilder.setTestRemoteConnection(false);
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base;
7+
8+
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
9+
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
10+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
11+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
12+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE;
13+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE;
14+
import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE;
15+
16+
import io.opentelemetry.api.trace.SpanKind;
17+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
18+
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
19+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
20+
import io.opentelemetry.sdk.trace.data.StatusData;
21+
import java.util.Locale;
22+
23+
public abstract class ControllerSpringWebFluxServerTest extends SpringWebFluxServerTest {
24+
25+
@Override
26+
protected SpanDataAssert assertHandlerSpan(
27+
SpanDataAssert span, String method, ServerEndpoint endpoint) {
28+
String handlerSpanName =
29+
ServerTestController.class.getSimpleName() + "." + endpoint.name().toLowerCase(Locale.ROOT);
30+
if (endpoint == NOT_FOUND) {
31+
handlerSpanName = "ResourceWebHandler.handle";
32+
}
33+
span.hasName(handlerSpanName).hasKind(SpanKind.INTERNAL);
34+
if (endpoint == EXCEPTION) {
35+
span.hasStatus(StatusData.error());
36+
span.hasEventsSatisfyingExactly(
37+
event ->
38+
event
39+
.hasName("exception")
40+
.hasAttributesSatisfyingExactly(
41+
equalTo(EXCEPTION_TYPE, "java.lang.IllegalStateException"),
42+
equalTo(EXCEPTION_MESSAGE, EXCEPTION.getBody()),
43+
satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))));
44+
} else if (endpoint == NOT_FOUND) {
45+
span.hasStatus(StatusData.error());
46+
// Spring 7 uses NoResourceFoundException instead of ResponseStatusException
47+
span.hasEventsSatisfyingExactly(
48+
event ->
49+
event
50+
.hasName("exception")
51+
.hasAttributesSatisfyingExactly(
52+
equalTo(
53+
EXCEPTION_TYPE,
54+
"org.springframework.web.reactive.resource.NoResourceFoundException"),
55+
satisfies(
56+
EXCEPTION_MESSAGE,
57+
val -> val.startsWith("404 NOT_FOUND \"No static resource notFound")),
58+
satisfies(EXCEPTION_STACKTRACE, val -> val.isInstanceOf(String.class))));
59+
}
60+
return span;
61+
}
62+
63+
@Override
64+
protected void configure(HttpServerTestOptions options) {
65+
super.configure(options);
66+
// TODO (trask) it seems like in this case ideally the controller span (which ends when the
67+
// Mono that the controller returns completes) should end before the server span (which needs
68+
// the result of the Mono)
69+
options.setVerifyServerSpanEndTime(false);
70+
71+
options.setResponseCodeOnNonStandardHttpMethod(405);
72+
73+
// TODO fails on java 21
74+
// span name set to "HTTP
75+
// org.springframework.web.reactive.function.server.RequestPredicates$$Lambda/0x00007fa574969238@4aaf6fa2"
76+
options.disableTestNonStandardHttpMethod();
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base;
7+
8+
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
9+
import java.time.Duration;
10+
import java.util.function.Supplier;
11+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
12+
import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.web.bind.annotation.RestController;
16+
import reactor.core.publisher.Mono;
17+
18+
/**
19+
* Tests the case which uses annotated controller methods, and where "controller" span is created
20+
* within a Mono map step, which follows a delay step. For exception endpoint, the exception is
21+
* thrown within the last map step.
22+
*/
23+
class DelayedControllerSpringWebFluxServerTest extends ControllerSpringWebFluxServerTest {
24+
@Override
25+
protected Class<?> getApplicationClass() {
26+
return Application.class;
27+
}
28+
29+
@Configuration
30+
@EnableAutoConfiguration
31+
static class Application {
32+
@Bean
33+
Controller controller() {
34+
return new Controller();
35+
}
36+
37+
@Bean
38+
NettyReactiveWebServerFactory nettyFactory() {
39+
return new NettyReactiveWebServerFactory();
40+
}
41+
}
42+
43+
@RestController
44+
static class Controller extends ServerTestController {
45+
@Override
46+
protected <T> Mono<T> wrapControllerMethod(ServerEndpoint endpoint, Supplier<T> handler) {
47+
return Mono.just("")
48+
.delayElement(Duration.ofMillis(10))
49+
.map(unused -> controller(endpoint, handler));
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.spring.webflux.v7_0.server.base;
7+
8+
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
9+
import java.time.Duration;
10+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
11+
import org.springframework.boot.reactor.netty.NettyReactiveWebServerFactory;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.web.reactive.function.server.RouterFunction;
15+
import org.springframework.web.reactive.function.server.ServerResponse;
16+
import reactor.core.publisher.Mono;
17+
18+
/**
19+
* Tests the case which uses route handlers, and where "controller" span is created within a Mono
20+
* map step, which follows a delay step. For exception endpoint, the exception is thrown within the
21+
* last map step.
22+
*/
23+
class DelayedHandlerSpringWebFluxServerTest extends HandlerSpringWebFluxServerTest {
24+
@Override
25+
protected Class<?> getApplicationClass() {
26+
return Application.class;
27+
}
28+
29+
@Configuration
30+
@EnableAutoConfiguration
31+
static class Application {
32+
@Bean
33+
RouterFunction<ServerResponse> router() {
34+
return new RouteFactory().createRoutes();
35+
}
36+
37+
@Bean
38+
NettyReactiveWebServerFactory nettyFactory() {
39+
return new NettyReactiveWebServerFactory();
40+
}
41+
}
42+
43+
static class RouteFactory extends ServerTestRouteFactory {
44+
45+
@Override
46+
protected Mono<ServerResponse> wrapResponse(
47+
ServerEndpoint endpoint, Mono<ServerResponse> response, Runnable spanAction) {
48+
return response
49+
.delayElement(Duration.ofMillis(10))
50+
.map(
51+
original ->
52+
controller(
53+
endpoint,
54+
() -> {
55+
spanAction.run();
56+
return original;
57+
}));
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)