Skip to content

Commit c4a494c

Browse files
author
Anuraag Agrawal
authored
Add instrumentation for Armeria WebClient. (#920)
* Add instrumentation for Armeria WebClient. * Format: * httpurlconnection * Docs and consistency:
1 parent 5f31109 commit c4a494c

File tree

17 files changed

+539
-103
lines changed

17 files changed

+539
-103
lines changed

instrumentation/armeria-1.0/auto/src/main/java/io/opentelemetry/auto/instrumentation/armeria/v1_0/AbstractArmeriaInstrumentation.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,18 @@ public AbstractArmeriaInstrumentation() {
2929
@Override
3030
public String[] helperClassNames() {
3131
return new String[] {
32-
"io.opentelemetry.instrumentation.armeria.v1_0.internal.ContextUtil",
32+
"io.opentelemetry.auto.instrumentation.armeria.v1_0.ArmeriaDecorators",
33+
"io.opentelemetry.instrumentation.armeria.v1_0.client.ArmeriaClientTracer",
34+
"io.opentelemetry.instrumentation.armeria.v1_0.client.ArmeriaClientTracer$ArmeriaSetter",
35+
"io.opentelemetry.instrumentation.armeria.v1_0.client.OpenTelemetryClient",
36+
"io.opentelemetry.instrumentation.armeria.v1_0.client.OpenTelemetryClient$Decorator",
37+
// Corresponds to lambda when calling .thenAccept(log -> ...
38+
"io.opentelemetry.instrumentation.armeria.v1_0.client.OpenTelemetryClient$1",
3339
"io.opentelemetry.instrumentation.armeria.v1_0.server.ArmeriaServerTracer",
3440
"io.opentelemetry.instrumentation.armeria.v1_0.server.ArmeriaServerTracer$ArmeriaGetter",
3541
"io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService",
3642
"io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService$Decorator",
37-
// .thenAccept(log -> lambda
43+
// Corresponds to lambda when calling .thenAccept(log -> ...
3844
"io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService$1",
3945
};
4046
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.auto.instrumentation.armeria.v1_0;
18+
19+
import com.linecorp.armeria.client.HttpClient;
20+
import com.linecorp.armeria.server.HttpService;
21+
import io.opentelemetry.instrumentation.armeria.v1_0.client.OpenTelemetryClient;
22+
import io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService;
23+
import java.util.function.Function;
24+
25+
// Holds singleton references to decorators to match against during suppression.
26+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/903
27+
public final class ArmeriaDecorators {
28+
29+
public static final Function<? super HttpClient, ? extends HttpClient> CLIENT_DECORATOR =
30+
OpenTelemetryClient.newDecorator();
31+
32+
public static final Function<? super HttpService, ? extends HttpService> SERVER_DECORATOR =
33+
OpenTelemetryService.newDecorator();
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.auto.instrumentation.armeria.v1_0;
18+
19+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
20+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
21+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
22+
import static net.bytebuddy.matcher.ElementMatchers.named;
23+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
24+
25+
import com.google.auto.service.AutoService;
26+
import com.linecorp.armeria.server.ServerBuilder;
27+
import io.opentelemetry.auto.tooling.Instrumenter;
28+
import io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
import java.util.function.Function;
32+
import net.bytebuddy.asm.Advice;
33+
import net.bytebuddy.description.method.MethodDescription;
34+
import net.bytebuddy.description.type.TypeDescription;
35+
import net.bytebuddy.matcher.ElementMatcher;
36+
37+
@AutoService(Instrumenter.class)
38+
public class ArmeriaServerBuilderInstrumentation extends AbstractArmeriaInstrumentation {
39+
40+
@Override
41+
public ElementMatcher<? super TypeDescription> typeMatcher() {
42+
return named("com.linecorp.armeria.server.ServerBuilder");
43+
}
44+
45+
@Override
46+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
47+
Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();
48+
transformers.put(
49+
isConstructor(),
50+
ArmeriaServerBuilderInstrumentation.class.getName() + "$ConstructorAdvice");
51+
transformers.put(
52+
isMethod().and(isPublic()).and(named("decorator").and(takesArgument(0, Function.class))),
53+
ArmeriaServerBuilderInstrumentation.class.getName() + "$SuppressDecoratorAdvice");
54+
return transformers;
55+
}
56+
57+
public static class ConstructorAdvice {
58+
@Advice.OnMethodExit
59+
public static void construct(@Advice.This ServerBuilder builder) {
60+
builder.decorator(OpenTelemetryService.newDecorator());
61+
}
62+
}
63+
64+
// Intercept calls from app to register decorator and suppress them to avoid registering
65+
// multiple decorators, one from user app and one from our auto instrumentation. Otherwise, we
66+
// will end up with double telemetry.
67+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/903
68+
public static class SuppressDecoratorAdvice {
69+
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class, inline = false)
70+
public static boolean suppressDecorator(@Advice.Argument(0) Function<?, ?> decorator) {
71+
return decorator != ArmeriaDecorators.SERVER_DECORATOR;
72+
}
73+
74+
@Advice.OnMethodExit
75+
public static void handleSuppression(
76+
@Advice.This ServerBuilder builder,
77+
@Advice.Enter boolean suppressed,
78+
@Advice.Return(readOnly = false) ServerBuilder returned) {
79+
if (suppressed) {
80+
returned = builder;
81+
}
82+
}
83+
}
84+
}

instrumentation/armeria-1.0/auto/src/main/java/io/opentelemetry/auto/instrumentation/armeria/v1_0/ArmeriaServerInstrumentation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import com.google.auto.service.AutoService;
2525
import com.linecorp.armeria.server.ServerBuilder;
2626
import io.opentelemetry.auto.tooling.Instrumenter;
27-
import io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService;
2827
import java.util.Collections;
2928
import java.util.Map;
3029
import net.bytebuddy.asm.Advice;
@@ -49,7 +48,7 @@ public Map<? extends ElementMatcher<? super MethodDescription>, String> transfor
4948
public static class AddDecoratorAdvice {
5049
@Advice.OnMethodExit
5150
public static void addDecorator(@Advice.Return ServerBuilder sb) {
52-
sb.decorator(OpenTelemetryService.newDecorator());
51+
sb.decorator(ArmeriaDecorators.SERVER_DECORATOR);
5352
}
5453
}
5554
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.auto.instrumentation.armeria.v1_0;
18+
19+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
20+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
21+
import static net.bytebuddy.matcher.ElementMatchers.named;
22+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
23+
24+
import com.google.auto.service.AutoService;
25+
import com.linecorp.armeria.client.WebClientBuilder;
26+
import io.opentelemetry.auto.tooling.Instrumenter;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
import java.util.function.Function;
30+
import net.bytebuddy.asm.Advice;
31+
import net.bytebuddy.description.method.MethodDescription;
32+
import net.bytebuddy.description.type.TypeDescription;
33+
import net.bytebuddy.matcher.ElementMatcher;
34+
35+
@AutoService(Instrumenter.class)
36+
public class ArmeriaWebClientBuilderInstrumentation extends AbstractArmeriaInstrumentation {
37+
38+
@Override
39+
public ElementMatcher<? super TypeDescription> typeMatcher() {
40+
return named("com.linecorp.armeria.client.WebClientBuilder");
41+
}
42+
43+
@Override
44+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
45+
Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();
46+
transformers.put(
47+
isMethod().and(isPublic()).and(named("decorator").and(takesArgument(0, Function.class))),
48+
ArmeriaWebClientBuilderInstrumentation.class.getName() + "$SuppressDecoratorAdvice");
49+
transformers.put(
50+
isMethod().and(isPublic()).and(named("build")),
51+
ArmeriaWebClientBuilderInstrumentation.class.getName() + "$BuildAdvice");
52+
return transformers;
53+
}
54+
55+
// Intercept calls from app to register decorator and suppress them to avoid registering
56+
// multiple decorators, one from user app and one from our auto instrumentation. Otherwise, we
57+
// will end up with double telemetry.
58+
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/903
59+
public static class SuppressDecoratorAdvice {
60+
@Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class)
61+
public static boolean suppressDecorator(@Advice.Argument(0) Function<?, ?> decorator) {
62+
return decorator != ArmeriaDecorators.CLIENT_DECORATOR;
63+
}
64+
65+
@Advice.OnMethodExit
66+
public static void handleSuppression(
67+
@Advice.This WebClientBuilder builder,
68+
@Advice.Enter boolean suppressed,
69+
@Advice.Return(readOnly = false) WebClientBuilder returned) {
70+
if (suppressed) {
71+
returned = builder;
72+
}
73+
}
74+
}
75+
76+
public static class BuildAdvice {
77+
@Advice.OnMethodEnter
78+
public static void build(@Advice.This WebClientBuilder builder) {
79+
builder.decorator(ArmeriaDecorators.CLIENT_DECORATOR);
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.auto.instrumentation.armeria.v1_0
18+
19+
import com.linecorp.armeria.client.WebClientBuilder
20+
import com.linecorp.armeria.server.ServerBuilder
21+
import io.opentelemetry.auto.test.AgentTestTrait
22+
import io.opentelemetry.instrumentation.armeria.v1_0.AbstractArmeriaTest
23+
import io.opentelemetry.instrumentation.armeria.v1_0.client.OpenTelemetryClient
24+
import io.opentelemetry.instrumentation.armeria.v1_0.server.OpenTelemetryService
25+
26+
class ArmeriaNoDuplicateInstrumentationTest extends AbstractArmeriaTest implements AgentTestTrait {
27+
@Override
28+
ServerBuilder configureServer(ServerBuilder sb) {
29+
return sb.decorator(OpenTelemetryService.newDecorator())
30+
}
31+
32+
@Override
33+
WebClientBuilder configureClient(WebClientBuilder clientBuilder) {
34+
return clientBuilder.decorator(OpenTelemetryClient.newDecorator())
35+
}
36+
37+
def childSetupSpec() {
38+
server.before()
39+
}
40+
41+
def childCleanupSpec() {
42+
server.after()
43+
}
44+
}
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@
1616

1717
package io.opentelemetry.auto.instrumentation.armeria.v1_0
1818

19+
import com.linecorp.armeria.client.WebClientBuilder
1920
import com.linecorp.armeria.server.ServerBuilder
2021
import io.opentelemetry.auto.test.AgentTestTrait
21-
import io.opentelemetry.instrumentation.armeria.v1_0.AbstractArmeriaServerTest
22+
import io.opentelemetry.instrumentation.armeria.v1_0.AbstractArmeriaTest
2223

23-
class ArmeriaServerTest extends AbstractArmeriaServerTest implements AgentTestTrait {
24+
class ArmeriaTest extends AbstractArmeriaTest implements AgentTestTrait {
2425
@Override
25-
void configureServer(ServerBuilder sb) {}
26+
ServerBuilder configureServer(ServerBuilder sb) {
27+
return sb
28+
}
29+
30+
@Override
31+
WebClientBuilder configureClient(WebClientBuilder clientBuilder) {
32+
return clientBuilder
33+
}
2634

2735
def childSetupSpec() {
2836
server.before()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.opentelemetry.instrumentation.armeria.v1_0.client;
18+
19+
import com.linecorp.armeria.client.ClientRequestContext;
20+
import com.linecorp.armeria.common.logging.RequestLog;
21+
import io.opentelemetry.context.propagation.HttpTextFormat.Setter;
22+
import io.opentelemetry.instrumentation.api.decorator.HttpClientTracer;
23+
import io.opentelemetry.trace.Tracer;
24+
import java.net.URI;
25+
import java.net.URISyntaxException;
26+
27+
public class ArmeriaClientTracer extends HttpClientTracer<ClientRequestContext, RequestLog> {
28+
29+
ArmeriaClientTracer() {}
30+
31+
ArmeriaClientTracer(Tracer tracer) {
32+
super(tracer);
33+
}
34+
35+
@Override
36+
protected String method(ClientRequestContext ctx) {
37+
return ctx.method().name();
38+
}
39+
40+
@Override
41+
protected URI url(ClientRequestContext ctx) throws URISyntaxException {
42+
return ctx.request().uri();
43+
}
44+
45+
@Override
46+
protected Integer status(RequestLog log) {
47+
return log.responseHeaders().status().code();
48+
}
49+
50+
@Override
51+
protected String requestHeader(ClientRequestContext ctx, String name) {
52+
return ctx.request().headers().get(name);
53+
}
54+
55+
@Override
56+
protected String responseHeader(RequestLog log, String name) {
57+
return log.responseHeaders().get(name);
58+
}
59+
60+
@Override
61+
protected Setter<ClientRequestContext> getSetter() {
62+
return ArmeriaSetter.INSTANCE;
63+
}
64+
65+
@Override
66+
protected String getInstrumentationName() {
67+
return "io.opentelemetry.armeria-1.0";
68+
}
69+
70+
private static class ArmeriaSetter implements Setter<ClientRequestContext> {
71+
72+
private static final ArmeriaSetter INSTANCE = new ArmeriaSetter();
73+
74+
@Override
75+
public void set(ClientRequestContext ctx, String key, String value) {
76+
ctx.addAdditionalRequestHeader(key, value);
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)