Skip to content

Commit 19a0531

Browse files
committed
Added native instrumentation using OpenTelemetry API
Signed-off-by: Alexander Wert <[email protected]>
1 parent 4bd9029 commit 19a0531

File tree

14 files changed

+294
-60
lines changed

14 files changed

+294
-60
lines changed

java-client/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,11 @@ dependencies {
201201
// https://github.com/eclipse-ee4j/parsson
202202
api("org.eclipse.parsson:parsson:1.0.0")
203203

204+
// OpenTelemetry API for native instrumentation of the client.
205+
implementation("io.opentelemetry:opentelemetry-api:1.26.0")
206+
implementation("io.opentelemetry:opentelemetry-semconv:1.26.0-alpha")
207+
208+
204209
// EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
205210
// https://github.com/eclipse-ee4j/jsonb-api
206211
compileOnly("jakarta.json.bind", "jakarta.json.bind-api", "2.0.0")

java-client/src/main/java/co/elastic/clients/transport/Endpoint.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ public interface Endpoint<RequestT, ResponseT, ErrorT> {
5656
*/
5757
String requestUrl(RequestT request);
5858

59+
/**
60+
* Get the route for a request (i.e. URL pattern).
61+
*/
62+
String route(RequestT request);
63+
64+
/**
65+
* Get the path parameters for a request.
66+
*/
67+
default Map<String, String> pathParameters(RequestT request) {
68+
return Collections.emptyMap();
69+
}
70+
5971
/**
6072
* Get the query parameters for a request.
6173
*/
@@ -104,6 +116,8 @@ default BinaryEndpoint<RequestT> withBinaryResponse() {
104116
this.id(),
105117
this::method,
106118
this::requestUrl,
119+
this::route,
120+
this::pathParameters,
107121
this::queryParameters,
108122
this::headers,
109123
this::body,

java-client/src/main/java/co/elastic/clients/transport/endpoints/BinaryEndpoint.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,32 @@ public BinaryEndpoint(
2828
String id,
2929
Function<RequestT, String> method,
3030
Function<RequestT, String> requestUrl,
31+
Function<RequestT, String> route,
32+
Function<RequestT,
33+
Map<String, String>> pathParameters,
3134
Function<RequestT,
3235
Map<String, String>> queryParameters,
3336
Function<RequestT, Map<String, String>> headers,
3437
Function<RequestT, Object> body,
3538
Object ignored // same number of arguments as SimpleEndpoint
3639
) {
37-
super(id, method, requestUrl, queryParameters, headers, body);
40+
super(id, method, requestUrl, route, pathParameters, queryParameters, headers, body);
3841
}
3942

4043
public BinaryEndpoint(
4144
String id,
4245
Function<RequestT, String> method,
4346
Function<RequestT, String> requestUrl,
47+
Function<RequestT, String> route,
48+
Function<RequestT,
49+
Map<String, String>> pathParameters,
4450
Function<RequestT,
4551
Map<String, String>> queryParameters,
4652
Function<RequestT, Map<String, String>> headers,
4753
boolean hasRequestBody,
4854
Object ignored // same number of arguments as SimpleEndpoint
4955
) {
50-
super(id, method, requestUrl, queryParameters, headers, hasRequestBody ? returnSelf() : returnNull());
56+
super(id, method, requestUrl, route, pathParameters, queryParameters, headers, hasRequestBody ? returnSelf() : returnNull());
5157
}
5258

5359
@Override

java-client/src/main/java/co/elastic/clients/transport/endpoints/BooleanEndpoint.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ public BooleanEndpoint(
2828
String id,
2929
Function<RequestT, String> method,
3030
Function<RequestT, String> requestUrl,
31+
Function<RequestT, String> route,
32+
Function<RequestT,
33+
Map<String, String>> pathParameters,
3134
Function<RequestT,
3235
Map<String, String>> queryParameters,
3336
Function<RequestT, Map<String, String>> headers,
3437
boolean hasRequestBody,
3538
Object ignored // same number of arguments as SimpleEndpoint
3639
) {
37-
super(id, method, requestUrl, queryParameters, headers, hasRequestBody ? returnSelf() : returnNull());
40+
super(id, method, requestUrl, route, pathParameters, queryParameters, headers, hasRequestBody ? returnSelf() : returnNull());
3841
}
3942

4043
@Override

java-client/src/main/java/co/elastic/clients/transport/endpoints/DelegatingJsonEndpoint.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ public String requestUrl(Req request) {
4848
return endpoint.requestUrl(request);
4949
}
5050

51+
@Override
52+
public String route(Req request) {
53+
return endpoint.route(request);
54+
}
55+
56+
@Override
57+
public Map<String, String> pathParameters(Req request) {
58+
return endpoint.pathParameters(request);
59+
}
60+
5161
@Override
5262
public Map<String, String> queryParameters(Req request) {
5363
return endpoint.queryParameters(request);

java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ static <T, U> Function<T, U> returnSelf() {
6464
protected final String id;
6565
protected final Function<RequestT, String> method;
6666
protected final Function<RequestT, String> requestUrl;
67+
protected final Function<RequestT, String> route;
68+
protected final Function<RequestT, Map<String, String>> pathParameters;
6769
protected final Function<RequestT, Map<String, String>> queryParameters;
6870
protected final Function<RequestT, Map<String, String>> headers;
6971
protected final Function<RequestT, Object> body;
@@ -72,13 +74,17 @@ public EndpointBase(
7274
String id,
7375
Function<RequestT, String> method,
7476
Function<RequestT, String> requestUrl,
77+
Function<RequestT, String> route,
78+
Function<RequestT, Map<String, String>> pathParameters,
7579
Function<RequestT, Map<String, String>> queryParameters,
7680
Function<RequestT, Map<String, String>> headers,
7781
Function<RequestT, Object> body
7882
) {
7983
this.id = id;
8084
this.method = method;
8185
this.requestUrl = requestUrl;
86+
this.route = route;
87+
this.pathParameters = pathParameters;
8288
this.queryParameters = queryParameters;
8389
this.headers = headers;
8490
this.body = body;
@@ -99,6 +105,16 @@ public String requestUrl(RequestT request) {
99105
return this.requestUrl.apply(request);
100106
}
101107

108+
@Override
109+
public String route(RequestT request) {
110+
return this.route.apply(request);
111+
}
112+
113+
@Override
114+
public Map<String, String> pathParameters(RequestT request) {
115+
return this.pathParameters.apply(request);
116+
}
117+
102118
@Override
103119
public Map<String, String> queryParameters(RequestT request) {
104120
return this.queryParameters.apply(request);
@@ -133,6 +149,8 @@ public <NewResponseT> SimpleEndpoint<RequestT, NewResponseT> withResponseDeseria
133149
id,
134150
method,
135151
requestUrl,
152+
route,
153+
pathParameters,
136154
queryParameters,
137155
headers,
138156
body,

java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,23 @@ public SimpleEndpoint(
3636
String id,
3737
Function<RequestT, String> method,
3838
Function<RequestT, String> requestUrl,
39+
Function<RequestT, String> route,
40+
Function<RequestT, Map<String, String>> pathParameters,
3941
Function<RequestT, Map<String, String>> queryParameters,
4042
Function<RequestT, Map<String, String>> headers,
4143
Function<RequestT, Object> body,
4244
JsonpDeserializer<ResponseT> responseParser
4345
) {
44-
super(id, method, requestUrl, queryParameters, headers, body);
46+
super(id, method, requestUrl, route, pathParameters, queryParameters, headers, body);
4547
this.responseParser = responseParser;
4648
}
4749

4850
public SimpleEndpoint(
4951
String id,
5052
Function<RequestT, String> method,
5153
Function<RequestT, String> requestUrl,
54+
Function<RequestT, String> route,
55+
Function<RequestT, Map<String, String>> pathParameters,
5256
Function<RequestT, Map<String, String>> queryParameters,
5357
Function<RequestT, Map<String, String>> headers,
5458
boolean hasResponseBody,
@@ -58,6 +62,8 @@ public SimpleEndpoint(
5862
id,
5963
method,
6064
requestUrl,
65+
route,
66+
pathParameters,
6167
queryParameters,
6268
headers,
6369
hasResponseBody ? returnSelf() : returnNull(),
@@ -82,6 +88,8 @@ public <NewResponseT> SimpleEndpoint<RequestT, NewResponseT> withResponseDeseria
8288
id,
8389
method,
8490
requestUrl,
91+
route,
92+
pathParameters,
8593
queryParameters,
8694
headers,
8795
body,

java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleJsonEndpoint.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ public SimpleJsonEndpoint(
3333
String id,
3434
Function<RequestT, String> method,
3535
Function<RequestT, String> requestUrl,
36+
Function<RequestT, String> route,
3637
Function<RequestT,
37-
Map<String, String>> queryParameters,
38+
Map<String, String>> pathParameters,
39+
Function<RequestT,
40+
Map<String, String>> queryParameters,
3841
Function<RequestT, Map<String, String>> headers,
3942
boolean hasRequestBody,
4043
JsonpDeserializer<ResponseT> responseParser
4144
) {
42-
super(id, method, requestUrl, queryParameters, headers, hasRequestBody, responseParser);
45+
super(id, method, requestUrl, route, pathParameters, queryParameters, headers, hasRequestBody, responseParser);
4346
}
4447
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package co.elastic.clients.transport.rest_client;
21+
22+
import co.elastic.clients.transport.Endpoint;
23+
import io.opentelemetry.api.GlobalOpenTelemetry;
24+
import io.opentelemetry.api.OpenTelemetry;
25+
import io.opentelemetry.api.trace.Span;
26+
import io.opentelemetry.api.trace.SpanKind;
27+
import io.opentelemetry.api.trace.Tracer;
28+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
29+
30+
import javax.annotation.Nullable;
31+
import java.io.ByteArrayOutputStream;
32+
import java.nio.ByteBuffer;
33+
import java.nio.charset.StandardCharsets;
34+
import java.util.List;
35+
import java.util.Map;
36+
import java.util.stream.Collectors;
37+
38+
public class InstrumentationUtil {
39+
40+
private final OpenTelemetry openTelemetry;
41+
private final Tracer tracer;
42+
43+
protected InstrumentationUtil(@Nullable OpenTelemetry openTelemetry) {
44+
if (openTelemetry == null) {
45+
this.openTelemetry = GlobalOpenTelemetry.get();
46+
} else {
47+
this.openTelemetry = openTelemetry;
48+
}
49+
tracer = this.openTelemetry.getTracer("elasticsearch-api");
50+
}
51+
52+
protected <RequestT, ResponseT, ErrorT> Span createSpanForRequest(RequestT request,
53+
Endpoint<RequestT, ResponseT, ErrorT> endpoint) {
54+
String httpMethod = endpoint.method(request);
55+
String route = endpoint.route(request);
56+
57+
Span span = tracer.spanBuilder(httpMethod + " " + route).setSpanKind(SpanKind.CLIENT).startSpan();
58+
if (span.isRecording()) {
59+
span.setAttribute(SemanticAttributes.DB_SYSTEM, "elasticsearch");
60+
span.setAttribute(SemanticAttributes.HTTP_METHOD, endpoint.method(request));
61+
span.setAttribute("url.path", endpoint.requestUrl(request));
62+
63+
Map<String, String> queryParameters = endpoint.queryParameters(request);
64+
if (!queryParameters.isEmpty()) {
65+
String queryString =
66+
queryParameters.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.joining("&"));
67+
span.setAttribute("url.query", queryString);
68+
}
69+
Map<String, String> pathParameters = endpoint.pathParameters(request);
70+
if (pathParameters.containsKey("index")) {
71+
String indexValue = pathParameters.get("index");
72+
span.setAttribute("db.elasticsearch.target", indexValue);
73+
}
74+
75+
if (pathParameters.containsKey("id") && route.startsWith("/{index}/_") && route.endsWith("/{id}")) {
76+
String docId = pathParameters.get("id");
77+
span.setAttribute("db.elasticsearch.doc_id", docId);
78+
}
79+
}
80+
81+
return span;
82+
}
83+
84+
protected <RequestT> void captureBody(@Nullable Span span, RequestT request, Endpoint<RequestT, ?, ?> endpoint,
85+
List<ByteBuffer> lines) {
86+
if (shouldCaptureBody(span, request, endpoint)) {
87+
StringBuilder bodyString = new StringBuilder();
88+
for (ByteBuffer line : lines) {
89+
bodyString.append(StandardCharsets.UTF_8.decode(line));
90+
bodyString.append("\n");
91+
}
92+
93+
span.setAttribute(SemanticAttributes.DB_STATEMENT, bodyString.toString());
94+
}
95+
}
96+
97+
protected <RequestT> void captureBody(@Nullable Span span, RequestT request, Endpoint<RequestT, ?, ?> endpoint,
98+
ByteArrayOutputStream baos) {
99+
if (shouldCaptureBody(span, request, endpoint)) {
100+
span.setAttribute(SemanticAttributes.DB_STATEMENT, baos.toString());
101+
}
102+
}
103+
104+
private <RequestT> boolean shouldCaptureBody(@Nullable Span span, RequestT request, Endpoint<RequestT, ?, ?> endpoint) {
105+
if (span == null || !span.isRecording()) {
106+
return false;
107+
}
108+
109+
String route = endpoint.route(request);
110+
111+
// We capture the request body in the span only for search-type requests.
112+
return route.contains("/_search") ||
113+
route.contains("/_msearch") ||
114+
route.contains("/_async_search") ||
115+
route.equals("/{index}/_terms_enum") ||
116+
route.startsWith("/_render/template") ||
117+
route.equals("/{index}/_mvt/{field}/{zoom}/{x}/{y}");
118+
}
119+
120+
121+
}

0 commit comments

Comments
 (0)