Skip to content

Commit 95fd5a4

Browse files
authored
Add gRPC body and headers capture (#69)
* Workign client body and metadata Signed-off-by: Pavol Loffay <[email protected]> * Server part Signed-off-by: Pavol Loffay <[email protected]> * Rename Signed-off-by: Pavol Loffay <[email protected]> * Working all Signed-off-by: Pavol Loffay <[email protected]> * Fix order Signed-off-by: Pavol Loffay <[email protected]> * rename Signed-off-by: Pavol Loffay <[email protected]> * dynamic config test Signed-off-by: Pavol Loffay <[email protected]> * Fix muzzle Signed-off-by: Pavol Loffay <[email protected]> * With waitForReady Signed-off-by: Pavol Loffay <[email protected]> * Add sleep Signed-off-by: Pavol Loffay <[email protected]> * format Signed-off-by: Pavol Loffay <[email protected]> * Fix packages and deps scopes Signed-off-by: Pavol Loffay <[email protected]> * Add stacktrace Signed-off-by: Pavol Loffay <[email protected]> * Clear props Signed-off-by: Pavol Loffay <[email protected]> * Add wait Signed-off-by: Pavol Loffay <[email protected]> * add waits Signed-off-by: Pavol Loffay <[email protected]> * Wait for spans Signed-off-by: Pavol Loffay <[email protected]>
1 parent 316d4e3 commit 95fd5a4

File tree

19 files changed

+963
-5
lines changed

19 files changed

+963
-5
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ build
3434

3535
.idea
3636

37+
.DS_Store
38+
3739
# the ./gradlew printVersion generates this file
3840
# ignore this file because the version is consumed from the git tags only
3941
semantic-build-versioning.gradle

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
.PHONY: build
33
build:
4-
./gradlew build
4+
./gradlew build --stacktrace
55

66
.PHONY: muzzle
77
muzzle:

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ List of supported frameworks with additional capabilities:
1515
|--------------------------------------------------------------------------------------------------------|-----------------|
1616
| [Servlet](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/package-summary.html) | 2.3+ |
1717
| [Spark Web Framework](https://github.com/perwendel/spark) | 2.3+ |
18+
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
1819

1920

2021
## Build
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import com.google.protobuf.gradle.*
2+
3+
plugins {
4+
`java-library`
5+
idea
6+
id("com.google.protobuf") version "0.8.13"
7+
id("net.bytebuddy.byte-buddy-gradle-plugin") version "1.10.10"
8+
id("io.opentelemetry.instrumentation.auto-instrumentation")
9+
muzzle
10+
}
11+
12+
muzzle {
13+
pass {
14+
group = "io.grpc"
15+
module = "grpc-core"
16+
versions = "[1.5.0, 1.33.0)" // see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/1453
17+
// for body capture via com.google.protobuf.util.JsonFormat
18+
extraDependency("io.grpc:grpc-protobuf:1.5.0")
19+
}
20+
}
21+
22+
afterEvaluate{
23+
byteBuddy {
24+
transformation(closureOf<net.bytebuddy.build.gradle.Transformation> {
25+
setTasks(kotlin.collections.setOf("compileJava", "compileScala", "compileKotlin"))
26+
plugin = "io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin"
27+
setClassPath(project(":javaagent-tooling").configurations["instrumentationMuzzle"] + configurations.runtimeClasspath + sourceSets["main"].output)
28+
})
29+
}
30+
}
31+
32+
idea {
33+
module {
34+
sourceDirs.add(file("${projectDir}/build/generated/source/proto/main/proto"))
35+
}
36+
}
37+
38+
protobuf {
39+
protoc {
40+
// The artifact spec for the Protobuf Compiler
41+
artifact = "com.google.protobuf:protoc:3.3.0"
42+
}
43+
plugins {
44+
id("grpc") {
45+
artifact = "io.grpc:protoc-gen-grpc-java:1.5.0"
46+
}
47+
}
48+
generateProtoTasks {
49+
all().forEach { task ->
50+
task.plugins {
51+
id("grpc") {
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
dependencies {
59+
api("io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-grpc-1.5:0.9.0")
60+
api("io.opentelemetry.instrumentation:opentelemetry-grpc-1.5:0.9.0")
61+
62+
compileOnly("io.grpc:grpc-core:1.5.0")
63+
compileOnly("io.grpc:grpc-protobuf:1.5.0")
64+
compileOnly("io.grpc:grpc-stub:1.5.0")
65+
66+
implementation("javax.annotation:javax.annotation-api:1.3.2")
67+
implementation("net.bytebuddy:byte-buddy:1.10.10")
68+
69+
testImplementation(project(":testing-common"))
70+
testImplementation("io.grpc:grpc-core:1.5.0")
71+
testImplementation("io.grpc:grpc-protobuf:1.5.0")
72+
testImplementation("io.grpc:grpc-stub:1.5.0")
73+
testImplementation("io.grpc:grpc-netty:1.5.0")
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright The Hypertrace 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.hypertrace.grpc.v1_5;
18+
19+
import com.google.protobuf.InvalidProtocolBufferException;
20+
import com.google.protobuf.Message;
21+
import com.google.protobuf.util.JsonFormat;
22+
import io.grpc.Metadata;
23+
import io.grpc.Metadata.Key;
24+
import io.opentelemetry.common.AttributeKey;
25+
import io.opentelemetry.trace.Span;
26+
import java.util.LinkedHashMap;
27+
import java.util.Map;
28+
import java.util.function.Function;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
public class GrpcSpanDecorator {
33+
34+
private GrpcSpanDecorator() {}
35+
36+
private static final Logger log = LoggerFactory.getLogger(GrpcSpanDecorator.class);
37+
private static final JsonFormat.Printer PRINTER = JsonFormat.printer();
38+
39+
public static void addMessageAttribute(Object message, Span span, AttributeKey<String> key) {
40+
if (message instanceof Message) {
41+
Message mb = (Message) message;
42+
try {
43+
String requestBody = PRINTER.print(mb);
44+
span.setAttribute(key, requestBody);
45+
} catch (InvalidProtocolBufferException e) {
46+
log.error("Failed to parse request message to JSON", e);
47+
}
48+
}
49+
}
50+
51+
public static void addMetadataAttributes(
52+
Metadata metadata, Span span, Function<String, AttributeKey<String>> keySupplier) {
53+
for (String key : metadata.keys()) {
54+
if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
55+
// do not add binary metadata
56+
continue;
57+
}
58+
Key<String> stringKey = Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
59+
Iterable<String> stringValues = metadata.getAll(stringKey);
60+
for (String stringValue : stringValues) {
61+
span.setAttribute(keySupplier.apply(key), stringValue);
62+
}
63+
}
64+
}
65+
66+
public static void addMetadataAttributes(
67+
Map<String, String> metadata, Span span, Function<String, AttributeKey<String>> keySupplier) {
68+
for (Map.Entry<String, String> entry : metadata.entrySet()) {
69+
span.setAttribute(keySupplier.apply(entry.getKey()), entry.getValue());
70+
}
71+
}
72+
73+
public static Map<String, String> metadataToMap(Metadata metadata) {
74+
Map<String, String> mapHeaders = new LinkedHashMap(metadata.keys().size());
75+
for (String key : metadata.keys()) {
76+
if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
77+
continue;
78+
}
79+
Key<String> stringKey = Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
80+
Iterable<String> stringValues = metadata.getAll(stringKey);
81+
for (String stringValue : stringValues) {
82+
mapHeaders.put(key, stringValue);
83+
}
84+
}
85+
return mapHeaders;
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright The Hypertrace 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.hypertrace.grpc.v1_5;
18+
19+
public class GrpcTracer
20+
extends io.opentelemetry.instrumentation.grpc.v1_5.client.GrpcClientTracer {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The Hypertrace 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.hypertrace.grpc.v1_5;
18+
19+
public class InstrumentationName {
20+
public static final String[] INSTRUMENTATION_NAME = {"grpc"};
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright The Hypertrace 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.hypertrace.grpc.v1_5.client;
18+
19+
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
20+
import static java.util.Collections.singletonMap;
21+
import static net.bytebuddy.matcher.ElementMatchers.declaresField;
22+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
23+
import static net.bytebuddy.matcher.ElementMatchers.named;
24+
25+
import com.google.auto.service.AutoService;
26+
import io.grpc.ClientInterceptor;
27+
import io.opentelemetry.javaagent.tooling.Instrumenter;
28+
import java.util.List;
29+
import java.util.Map;
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 GrpcClientBodyInstrumentation extends Instrumenter.Default {
37+
38+
public GrpcClientBodyInstrumentation() {
39+
super("grpc");
40+
}
41+
42+
@Override
43+
public int getOrder() {
44+
return 1;
45+
}
46+
47+
@Override
48+
public String[] helperClassNames() {
49+
return new String[] {
50+
"io.opentelemetry.instrumentation.grpc.v1_5.common.GrpcHelper",
51+
"io.opentelemetry.instrumentation.grpc.v1_5.client.GrpcClientTracer",
52+
"io.opentelemetry.instrumentation.grpc.v1_5.client.GrpcInjectAdapter",
53+
"io.opentelemetry.instrumentation.grpc.v1_5.client.TracingClientInterceptor",
54+
"io.opentelemetry.instrumentation.grpc.v1_5.client.TracingClientInterceptor$TracingClientCall",
55+
"io.opentelemetry.instrumentation.grpc.v1_5.client.TracingClientInterceptor$TracingClientCallListener",
56+
"io.opentelemetry.instrumentation.grpc.v1_5.server.GrpcExtractAdapter",
57+
"io.opentelemetry.instrumentation.grpc.v1_5.server.GrpcServerTracer",
58+
"io.opentelemetry.instrumentation.grpc.v1_5.server.TracingServerInterceptor",
59+
"io.opentelemetry.instrumentation.grpc.v1_5.server.TracingServerInterceptor$TracingServerCall",
60+
"io.opentelemetry.instrumentation.grpc.v1_5.server.TracingServerInterceptor$TracingServerCallListener",
61+
"org.hypertrace.agent.core.HypertraceSemanticAttributes",
62+
"org.hypertrace.agent.core.DynamicConfig",
63+
"io.opentelemetry.instrumentation.hypertrace.grpc.v1_5.GrpcTracer",
64+
"io.opentelemetry.instrumentation.hypertrace.grpc.v1_5.GrpcSpanDecorator",
65+
"io.opentelemetry.instrumentation.hypertrace.grpc.v1_5.InstrumentationName",
66+
packageName + ".GrpcClientInterceptor"
67+
};
68+
}
69+
70+
@Override
71+
public ElementMatcher<TypeDescription> typeMatcher() {
72+
return extendsClass(named("io.grpc.ManagedChannelBuilder"))
73+
.and(declaresField(named("interceptors")));
74+
}
75+
76+
@Override
77+
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
78+
return singletonMap(
79+
isMethod().and(named("build")),
80+
GrpcClientBodyInstrumentation.class.getName() + "$AddInterceptorAdvice");
81+
}
82+
83+
public static class AddInterceptorAdvice {
84+
@Advice.OnMethodEnter(suppress = Throwable.class)
85+
public static void addInterceptor(
86+
@Advice.FieldValue("interceptors") List<ClientInterceptor> interceptors) {
87+
boolean shouldRegister = true;
88+
for (ClientInterceptor interceptor : interceptors) {
89+
if (interceptor instanceof GrpcClientInterceptor) {
90+
shouldRegister = false;
91+
break;
92+
}
93+
}
94+
if (shouldRegister) {
95+
interceptors.add(0, new GrpcClientInterceptor());
96+
}
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)