Skip to content

Commit d1e600e

Browse files
authored
Generate events for OkHttp Websocket events (open-telemetry#863)
* Generate events for OkHttp Websocket events * move websocket to separate module and add test
1 parent 5b0af2e commit d1e600e

File tree

17 files changed

+426
-2
lines changed

17 files changed

+426
-2
lines changed

instrumentation/okhttp/okhttp-3.0/agent/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ android {
1010
}
1111

1212
dependencies {
13+
compileOnly(libs.okhttp)
1314
implementation(project(":instrumentation:okhttp:okhttp-3.0:library"))
14-
implementation(libs.okhttp)
1515
implementation(libs.byteBuddy)
1616
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
plugins {
2+
id("otel.android-library-conventions")
3+
id("otel.publish-conventions")
4+
}
5+
6+
description = "OpenTelemetry build-time auto-instrumentation for OkHttp Websocket on Android"
7+
8+
android {
9+
namespace = "io.opentelemetry.android.okhttp.websocket.agent"
10+
}
11+
12+
dependencies {
13+
implementation(project(":instrumentation:okhttp:websocket:okhttp-3.0:library"))
14+
compileOnly(libs.okhttp)
15+
implementation(libs.byteBuddy)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.agent.okhttp.v3_0.websocket;
7+
8+
import io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal.WebsocketListenerWrapper;
9+
import net.bytebuddy.asm.Advice;
10+
import okhttp3.WebSocketListener;
11+
12+
public class OkHttpClientWebsocketAdvice {
13+
14+
@Advice.OnMethodEnter
15+
public static void enter(
16+
@Advice.Argument(value = 1, readOnly = false) WebSocketListener webSocketListener) {
17+
if (webSocketListener instanceof WebsocketListenerWrapper) return;
18+
19+
webSocketListener = new WebsocketListenerWrapper(webSocketListener);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.agent.okhttp.v3_0.websocket;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
10+
11+
import java.io.IOException;
12+
import net.bytebuddy.asm.Advice;
13+
import net.bytebuddy.build.Plugin;
14+
import net.bytebuddy.description.type.TypeDescription;
15+
import net.bytebuddy.dynamic.ClassFileLocator;
16+
import net.bytebuddy.dynamic.DynamicType;
17+
import okhttp3.WebSocketListener;
18+
19+
public class OkHttpClientWebsocketPlugin implements Plugin {
20+
21+
@Override
22+
public DynamicType.Builder<?> apply(
23+
DynamicType.Builder<?> builder,
24+
TypeDescription typeDescription,
25+
ClassFileLocator classFileLocator) {
26+
return builder.visit(
27+
Advice.to(OkHttpClientWebsocketAdvice.class)
28+
.on(named("newWebSocket").and(takesArgument(1, WebSocketListener.class))));
29+
}
30+
31+
@Override
32+
public void close() throws IOException {
33+
// No operation.
34+
}
35+
36+
@Override
37+
public boolean matches(TypeDescription target) {
38+
return target.getTypeName().equals("okhttp3.OkHttpClient");
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.opentelemetry.instrumentation.agent.okhttp.v3_0.websocket.OkHttpClientWebsocketPlugin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
id("otel.android-library-conventions")
3+
id("otel.publish-conventions")
4+
}
5+
6+
description = "OpenTelemetry OkHttp Websocket library instrumentation for Android"
7+
8+
android {
9+
namespace = "io.opentelemetry.android.okhttp.websocket.library"
10+
11+
defaultConfig {
12+
consumerProguardFiles("consumer-rules.pro")
13+
}
14+
}
15+
16+
dependencies {
17+
api(project(":instrumentation:android-instrumentation"))
18+
compileOnly(libs.okhttp)
19+
api(libs.opentelemetry.instrumentation.okhttp)
20+
implementation(libs.opentelemetry.instrumentation.apiSemconv)
21+
implementation(libs.opentelemetry.api.incubator)
22+
}

instrumentation/okhttp/websocket/okhttp-3.0/library/consumer-rules.pro

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal;
7+
8+
import androidx.annotation.NonNull;
9+
import com.google.auto.service.AutoService;
10+
import io.opentelemetry.android.instrumentation.AndroidInstrumentation;
11+
import io.opentelemetry.android.instrumentation.InstallationContext;
12+
13+
@AutoService(AndroidInstrumentation.class)
14+
public class OkHttpWebsocketInstrumentation implements AndroidInstrumentation {
15+
@Override
16+
public void install(@NonNull InstallationContext ctx) {
17+
WebsocketEventGenerator.configure(ctx);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.api.common.AttributesBuilder;
10+
import io.opentelemetry.semconv.HttpAttributes;
11+
import io.opentelemetry.semconv.NetworkAttributes;
12+
import io.opentelemetry.semconv.UrlAttributes;
13+
import okhttp3.Request;
14+
import okhttp3.WebSocket;
15+
16+
class WebsocketAttributeExtractor {
17+
private WebsocketAttributeExtractor() {}
18+
19+
static Attributes extractAttributes(WebSocket socket) {
20+
AttributesBuilder builder = Attributes.builder();
21+
Request request = socket.request();
22+
builder.put(NetworkAttributes.NETWORK_PROTOCOL_NAME, "websocket");
23+
24+
builder.put(HttpAttributes.HTTP_REQUEST_METHOD, request.method());
25+
builder.put(UrlAttributes.URL_FULL, request.url().toString());
26+
builder.put(NetworkAttributes.NETWORK_PEER_PORT, request.url().port());
27+
28+
return builder.build();
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal;
7+
8+
import io.opentelemetry.api.common.AttributeKey;
9+
10+
public class WebsocketAttributes {
11+
private WebsocketAttributes() {}
12+
13+
public static AttributeKey<Long> MESSAGE_SIZE = AttributeKey.longKey("websocket.message.size");
14+
public static AttributeKey<String> MESSAGE_TYPE =
15+
AttributeKey.stringKey("websocket.message.type");
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal;
7+
8+
import io.opentelemetry.android.instrumentation.InstallationContext;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
import io.opentelemetry.api.common.Attributes;
11+
import io.opentelemetry.api.incubator.logs.ExtendedLogRecordBuilder;
12+
import io.opentelemetry.api.incubator.logs.ExtendedLogger;
13+
14+
public final class WebsocketEventGenerator {
15+
16+
private WebsocketEventGenerator() {}
17+
18+
private static final String SCOPE = "io.opentelemetry.websocket.events";
19+
20+
private static ExtendedLogger logger =
21+
(ExtendedLogger) OpenTelemetry.noop().getLogsBridge().loggerBuilder(SCOPE).build();
22+
23+
public static void configure(InstallationContext context) {
24+
WebsocketEventGenerator.logger =
25+
(ExtendedLogger)
26+
context.getOpenTelemetry().getLogsBridge().loggerBuilder(SCOPE).build();
27+
}
28+
29+
public static void generateEvent(String eventName, Attributes attributes) {
30+
ExtendedLogRecordBuilder logRecordBuilder = logger.logRecordBuilder();
31+
logRecordBuilder.setEventName(eventName).setAllAttributes(attributes).emit();
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.library.okhttp.v3_0.websocket.internal;
7+
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
10+
import io.opentelemetry.api.common.Attributes;
11+
import okhttp3.Response;
12+
import okhttp3.WebSocket;
13+
import okhttp3.WebSocketListener;
14+
import okio.ByteString;
15+
16+
public class WebsocketListenerWrapper extends WebSocketListener {
17+
private final WebSocketListener delegate;
18+
19+
public WebsocketListenerWrapper(WebSocketListener delegate) {
20+
this.delegate = delegate;
21+
}
22+
23+
@Override
24+
public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
25+
Attributes attributes = WebsocketAttributeExtractor.extractAttributes(webSocket);
26+
WebsocketEventGenerator.generateEvent("websocket.close", attributes);
27+
delegate.onClosed(webSocket, code, reason);
28+
}
29+
30+
@Override
31+
public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
32+
Attributes attributes = WebsocketAttributeExtractor.extractAttributes(webSocket);
33+
WebsocketEventGenerator.generateEvent("websocket.open", attributes);
34+
delegate.onOpen(webSocket, response);
35+
}
36+
37+
@Override
38+
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
39+
Attributes attributes = WebsocketAttributeExtractor.extractAttributes(webSocket);
40+
WebsocketEventGenerator.generateEvent(
41+
"websocket.message",
42+
attributes.toBuilder()
43+
.put(WebsocketAttributes.MESSAGE_TYPE, "text")
44+
.put(WebsocketAttributes.MESSAGE_SIZE, text.length())
45+
.build());
46+
delegate.onMessage(webSocket, text);
47+
}
48+
49+
@Override
50+
public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
51+
Attributes attributes = WebsocketAttributeExtractor.extractAttributes(webSocket);
52+
WebsocketEventGenerator.generateEvent(
53+
"websocket.message",
54+
attributes.toBuilder()
55+
.put(WebsocketAttributes.MESSAGE_TYPE, "bytes")
56+
.put(WebsocketAttributes.MESSAGE_SIZE, bytes.size())
57+
.build());
58+
delegate.onMessage(webSocket, bytes);
59+
}
60+
61+
@Override
62+
public void onFailure(
63+
@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {
64+
Attributes attributes = WebsocketAttributeExtractor.extractAttributes(webSocket);
65+
WebsocketEventGenerator.generateEvent("websocket.error", attributes);
66+
delegate.onFailure(webSocket, t, response);
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
id("otel.android-app-conventions")
3+
id("net.bytebuddy.byte-buddy-gradle-plugin")
4+
}
5+
6+
android {
7+
namespace = "io.opentelemetry.android.okhttp.websocket.testing"
8+
}
9+
10+
dependencies {
11+
implementation(project(":test-common"))
12+
byteBuddy(project(":instrumentation:okhttp:websocket:okhttp-3.0:agent"))
13+
implementation(project(":instrumentation:okhttp:websocket:okhttp-3.0:library"))
14+
15+
implementation(libs.okhttp)
16+
implementation(libs.opentelemetry.exporter.otlp)
17+
androidTestImplementation(libs.okhttp.mockwebserver)
18+
}

0 commit comments

Comments
 (0)