Convert Elasticsearch Transport to Instrumenter API (#4252)

* Convert Elasticsearch Transport to Instrumenter API

* Don't set duplicate attributes

* Code review comments
This commit is contained in:
Nikita Salnikov-Tarnovski 2021-10-01 13:45:29 +03:00 committed by GitHub
parent c11b96e4d0
commit bfeb482465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 613 additions and 586 deletions

View File

@ -22,8 +22,15 @@ public abstract class NetAttributesExtractor<REQUEST, RESPONSE>
@Override @Override
protected final void onStart(AttributesBuilder attributes, REQUEST request) { protected final void onStart(AttributesBuilder attributes, REQUEST request) {
set(attributes, SemanticAttributes.NET_TRANSPORT, transport(request)); set(attributes, SemanticAttributes.NET_TRANSPORT, transport(request));
set(attributes, SemanticAttributes.NET_PEER_IP, peerIp(request, null));
set(attributes, SemanticAttributes.NET_PEER_NAME, peerName(request, null)); String peerIp = peerIp(request, null);
String peerName = peerName(request, null);
if (peerName != null && !peerName.equals(peerIp)) {
set(attributes, SemanticAttributes.NET_PEER_NAME, peerName);
}
set(attributes, SemanticAttributes.NET_PEER_IP, peerIp);
Integer peerPort = peerPort(request, null); Integer peerPort = peerPort(request, null);
if (peerPort != null) { if (peerPort != null) {
set(attributes, SemanticAttributes.NET_PEER_PORT, (long) peerPort); set(attributes, SemanticAttributes.NET_PEER_PORT, (long) peerPort);
@ -36,8 +43,15 @@ public abstract class NetAttributesExtractor<REQUEST, RESPONSE>
REQUEST request, REQUEST request,
@Nullable RESPONSE response, @Nullable RESPONSE response,
@Nullable Throwable error) { @Nullable Throwable error) {
set(attributes, SemanticAttributes.NET_PEER_IP, peerIp(request, response));
set(attributes, SemanticAttributes.NET_PEER_NAME, peerName(request, response)); String peerIp = peerIp(request, response);
String peerName = peerName(request, response);
if (peerName != null && !peerName.equals(peerIp)) {
set(attributes, SemanticAttributes.NET_PEER_NAME, peerName);
}
set(attributes, SemanticAttributes.NET_PEER_IP, peerIp);
Integer peerPort = peerPort(request, response); Integer peerPort = peerPort(request, response);
if (peerPort != null) { if (peerPort != null) {
set(attributes, SemanticAttributes.NET_PEER_PORT, (long) peerPort); set(attributes, SemanticAttributes.NET_PEER_PORT, (long) peerPort);

View File

@ -87,4 +87,40 @@ class NetAttributesExtractorTest {
entry(SemanticAttributes.NET_PEER_PORT, 42L), entry(SemanticAttributes.NET_PEER_PORT, 42L),
entry(SemanticAttributes.NET_PEER_IP, "4.3.2.1")); entry(SemanticAttributes.NET_PEER_IP, "4.3.2.1"));
} }
@Test
public void doesNotSetDuplicateAttributes() {
// given
Map<String, String> request = new HashMap<>();
request.put("transport", "TCP");
request.put("peerName", "1.2.3.4");
request.put("peerIp", "1.2.3.4");
request.put("peerPort", "123");
Map<String, String> response = new HashMap<>();
response.put("peerName", "4.3.2.1");
response.put("peerPort", "42");
response.put("peerIp", "4.3.2.1");
TestNetAttributesExtractor extractor = new TestNetAttributesExtractor();
// when
AttributesBuilder startAttributes = Attributes.builder();
extractor.onStart(startAttributes, request);
AttributesBuilder endAttributes = Attributes.builder();
extractor.onEnd(endAttributes, request, response, null);
// then
assertThat(startAttributes.build())
.containsOnly(
entry(SemanticAttributes.NET_TRANSPORT, "TCP"),
entry(SemanticAttributes.NET_PEER_PORT, 123L),
entry(SemanticAttributes.NET_PEER_IP, "1.2.3.4"));
assertThat(endAttributes.build())
.containsOnly(
entry(SemanticAttributes.NET_PEER_PORT, 42L),
entry(SemanticAttributes.NET_PEER_IP, "4.3.2.1"));
}
} }

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0; package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer; import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0.Elasticsearch5TransportSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -15,6 +15,8 @@ import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope; import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.TransportActionListener;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher;
@ -51,27 +53,38 @@ public class AbstractClientInstrumentation implements TypeInstrumentation {
@Advice.Argument(1) ActionRequest actionRequest, @Advice.Argument(1) ActionRequest actionRequest,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope, @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest,
@Advice.Argument(value = 2, readOnly = false) @Advice.Argument(value = 2, readOnly = false)
ActionListener<ActionResponse> actionListener) { ActionListener<ActionResponse> actionListener) {
transportRequest = ElasticTransportRequest.create(action, actionRequest);
Context parentContext = currentContext(); Context parentContext = currentContext();
context = tracer().startSpan(parentContext, null, action); if (!instrumenter().shouldStart(parentContext, transportRequest)) {
return;
}
context = instrumenter().start(parentContext, transportRequest);
scope = context.makeCurrent(); scope = context.makeCurrent();
tracer().onRequest(context, action.getClass(), actionRequest.getClass());
actionListener = actionListener =
new TransportActionListener<>(actionRequest, actionListener, context, parentContext); new TransportActionListener<>(
instrumenter(), transportRequest, actionListener, context, parentContext);
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan( public static void stopSpan(
@Advice.Thrown Throwable throwable, @Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) { @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest) {
if (scope == null) {
return;
}
scope.close(); scope.close();
if (throwable != null) { if (throwable != null) {
tracer().endExceptionally(context, throwable); instrumenter().end(context, transportRequest, null, throwable);
} }
} }
} }

View File

@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportExperimentalAttributesExtractor;
import org.elasticsearch.action.DocumentRequest;
public class Elasticsearch5TransportExperimentalAttributesExtractor
extends ElasticsearchTransportExperimentalAttributesExtractor {
@Override
protected void onStart(AttributesBuilder attributes, ElasticTransportRequest transportRequest) {
super.onStart(attributes, transportRequest);
Object request = transportRequest.getRequest();
if (request instanceof DocumentRequest) {
DocumentRequest<?> req = (DocumentRequest<?>) request;
attributes.put("elasticsearch.request.write.type", req.type());
attributes.put("elasticsearch.request.write.routing", req.routing());
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportNetAttributesExtractor;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportInstrumenterFactory;
import org.elasticsearch.action.ActionResponse;
public final class Elasticsearch5TransportSingletons {
private static final Instrumenter<ElasticTransportRequest, ActionResponse> INSTRUMENTER =
ElasticsearchTransportInstrumenterFactory.create(
"io.opentelemetry.elasticsearch-transport-5.0",
new Elasticsearch5TransportExperimentalAttributesExtractor(),
new ElasticTransportNetAttributesExtractor());
public static Instrumenter<ElasticTransportRequest, ActionResponse> instrumenter() {
return INSTRUMENTER;
}
private Elasticsearch5TransportSingletons() {}
}

View File

@ -1,144 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_0;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.DocumentRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.bulk.BulkShardResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBoolean("otel.instrumentation.elasticsearch.experimental-span-attributes", false);
private final ActionListener<T> listener;
private final Context context;
private final Context parentContext;
public TransportActionListener(
ActionRequest<?> actionRequest,
ActionListener<T> listener,
Context context,
Context parentContext) {
this.listener = listener;
this.context = context;
this.parentContext = parentContext;
onRequest(actionRequest);
}
private void onRequest(ActionRequest<?> request) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Span span = Span.fromContext(context);
if (request instanceof IndicesRequest) {
IndicesRequest req = (IndicesRequest) request;
String[] indices = req.indices();
if (indices != null && indices.length > 0) {
span.setAttribute("elasticsearch.request.indices", String.join(",", indices));
}
}
if (request instanceof SearchRequest) {
SearchRequest req = (SearchRequest) request;
String[] types = req.types();
if (types != null && types.length > 0) {
span.setAttribute("elasticsearch.request.search.types", String.join(",", types));
}
}
if (request instanceof DocumentRequest) {
DocumentRequest<?> req = (DocumentRequest<?>) request;
span.setAttribute("elasticsearch.request.write.type", req.type());
span.setAttribute("elasticsearch.request.write.routing", req.routing());
}
}
}
@Override
public void onResponse(T response) {
Span span = Span.fromContext(context);
if (response.remoteAddress() != null) {
NetPeerAttributes.INSTANCE.setNetPeer(
span, response.remoteAddress().getHost(), response.remoteAddress().getAddress());
span.setAttribute(
SemanticAttributes.NET_PEER_PORT, (long) response.remoteAddress().getPort());
}
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
if (response instanceof GetResponse) {
GetResponse resp = (GetResponse) response;
span.setAttribute("elasticsearch.type", resp.getType());
span.setAttribute("elasticsearch.id", resp.getId());
span.setAttribute("elasticsearch.version", resp.getVersion());
}
if (response instanceof BroadcastResponse) {
BroadcastResponse resp = (BroadcastResponse) response;
span.setAttribute("elasticsearch.shard.broadcast.total", resp.getTotalShards());
span.setAttribute("elasticsearch.shard.broadcast.successful", resp.getSuccessfulShards());
span.setAttribute("elasticsearch.shard.broadcast.failed", resp.getFailedShards());
}
if (response instanceof ReplicationResponse) {
ReplicationResponse resp = (ReplicationResponse) response;
span.setAttribute("elasticsearch.shard.replication.total", resp.getShardInfo().getTotal());
span.setAttribute(
"elasticsearch.shard.replication.successful", resp.getShardInfo().getSuccessful());
span.setAttribute(
"elasticsearch.shard.replication.failed", resp.getShardInfo().getFailed());
}
if (response instanceof IndexResponse) {
span.setAttribute(
"elasticsearch.response.status", ((IndexResponse) response).status().getStatus());
}
if (response instanceof BulkShardResponse) {
BulkShardResponse resp = (BulkShardResponse) response;
span.setAttribute("elasticsearch.shard.bulk.id", resp.getShardId().getId());
span.setAttribute("elasticsearch.shard.bulk.index", resp.getShardId().getIndexName());
}
if (response instanceof BaseNodesResponse) {
BaseNodesResponse<?> resp = (BaseNodesResponse<?>) response;
if (resp.hasFailures()) {
span.setAttribute("elasticsearch.node.failures", resp.failures().size());
}
span.setAttribute("elasticsearch.node.cluster.name", resp.getClusterName().value());
}
}
tracer().end(context);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onResponse(response);
}
}
@Override
public void onFailure(Exception e) {
tracer().endExceptionally(context, e);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onFailure(e);
}
}
}

View File

@ -243,7 +243,7 @@ class Elasticsearch5NodeClientTest extends AbstractElasticsearchNodeClientTest {
} }
} }
} }
trace(3, 2) { trace(3, 1) {
span(0) { span(0) {
name "IndexAction" name "IndexAction"
kind CLIENT kind CLIENT
@ -260,17 +260,6 @@ class Elasticsearch5NodeClientTest extends AbstractElasticsearchNodeClientTest {
"elasticsearch.shard.replication.failed" 0 "elasticsearch.shard.replication.failed" 0
} }
} }
span(1) {
name "PutMappingAction"
kind CLIENT
childOf span(0)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "elasticsearch"
"${SemanticAttributes.DB_OPERATION.key}" "PutMappingAction"
"elasticsearch.action" "PutMappingAction"
"elasticsearch.request" "PutMappingRequest"
}
}
} }
trace(4, 1) { trace(4, 1) {
span(0) { span(0) {

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3; package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer; import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3.Elasticsearch53TransportSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
@ -15,6 +15,8 @@ import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope; import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.TransportActionListener;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher;
@ -51,27 +53,38 @@ public class AbstractClientInstrumentation implements TypeInstrumentation {
@Advice.Argument(1) ActionRequest actionRequest, @Advice.Argument(1) ActionRequest actionRequest,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope, @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest,
@Advice.Argument(value = 2, readOnly = false) @Advice.Argument(value = 2, readOnly = false)
ActionListener<ActionResponse> actionListener) { ActionListener<ActionResponse> actionListener) {
transportRequest = ElasticTransportRequest.create(action, actionRequest);
Context parentContext = currentContext(); Context parentContext = currentContext();
context = tracer().startSpan(parentContext, null, action); if (!instrumenter().shouldStart(parentContext, transportRequest)) {
return;
}
context = instrumenter().start(parentContext, transportRequest);
scope = context.makeCurrent(); scope = context.makeCurrent();
tracer().onRequest(context, action.getClass(), actionRequest.getClass());
actionListener = actionListener =
new TransportActionListener<>(actionRequest, actionListener, context, parentContext); new TransportActionListener<>(
instrumenter(), transportRequest, actionListener, context, parentContext);
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan( public static void stopSpan(
@Advice.Thrown Throwable throwable, @Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) { @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest) {
if (scope == null) {
return;
}
scope.close(); scope.close();
if (throwable != null) { if (throwable != null) {
tracer().endExceptionally(context, throwable); instrumenter().end(context, transportRequest, null, throwable);
} }
} }
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportExperimentalAttributesExtractor;
import org.elasticsearch.action.DocWriteRequest;
public class Elasticsearch53TransportExperimentalAttributesExtractor
extends ElasticsearchTransportExperimentalAttributesExtractor {
@Override
protected void onStart(AttributesBuilder attributes, ElasticTransportRequest transportRequest) {
super.onStart(attributes, transportRequest);
Object request = transportRequest.getRequest();
if (request instanceof DocWriteRequest) {
DocWriteRequest<?> req = (DocWriteRequest<?>) request;
attributes.put("elasticsearch.request.write.type", req.type());
attributes.put("elasticsearch.request.write.routing", req.routing());
attributes.put("elasticsearch.request.write.version", req.version());
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportNetAttributesExtractor;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportInstrumenterFactory;
import org.elasticsearch.action.ActionResponse;
public final class Elasticsearch53TransportSingletons {
private static final Instrumenter<ElasticTransportRequest, ActionResponse> INSTRUMENTER =
ElasticsearchTransportInstrumenterFactory.create(
"io.opentelemetry.elasticsearch-transport-5.3",
new Elasticsearch53TransportExperimentalAttributesExtractor(),
new ElasticTransportNetAttributesExtractor());
public static Instrumenter<ElasticTransportRequest, ActionResponse> instrumenter() {
return INSTRUMENTER;
}
private Elasticsearch53TransportSingletons() {}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v5_3;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.bulk.BulkShardResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBoolean("otel.instrumentation.elasticsearch.experimental-span-attributes", false);
private final ActionListener<T> listener;
private final Context context;
private final Context parentContext;
public TransportActionListener(
ActionRequest actionRequest,
ActionListener<T> listener,
Context context,
Context parentContext) {
this.listener = listener;
this.context = context;
this.parentContext = parentContext;
onRequest(actionRequest);
}
private void onRequest(ActionRequest request) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Span span = Span.fromContext(context);
if (request instanceof IndicesRequest) {
IndicesRequest req = (IndicesRequest) request;
String[] indices = req.indices();
if (indices != null && indices.length > 0) {
span.setAttribute("elasticsearch.request.indices", String.join(",", indices));
}
}
if (request instanceof SearchRequest) {
SearchRequest req = (SearchRequest) request;
String[] types = req.types();
if (types != null && types.length > 0) {
span.setAttribute("elasticsearch.request.search.types", String.join(",", types));
}
}
if (request instanceof DocWriteRequest) {
DocWriteRequest<?> req = (DocWriteRequest<?>) request;
span.setAttribute("elasticsearch.request.write.type", req.type());
span.setAttribute("elasticsearch.request.write.routing", req.routing());
span.setAttribute("elasticsearch.request.write.version", req.version());
}
}
}
@Override
public void onResponse(T response) {
Span span = Span.fromContext(context);
if (response.remoteAddress() != null) {
NetPeerAttributes.INSTANCE.setNetPeer(
span, response.remoteAddress().getHost(), response.remoteAddress().getAddress());
span.setAttribute(
SemanticAttributes.NET_PEER_PORT, (long) response.remoteAddress().getPort());
}
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
if (response instanceof GetResponse) {
GetResponse resp = (GetResponse) response;
span.setAttribute("elasticsearch.type", resp.getType());
span.setAttribute("elasticsearch.id", resp.getId());
span.setAttribute("elasticsearch.version", resp.getVersion());
}
if (response instanceof BroadcastResponse) {
BroadcastResponse resp = (BroadcastResponse) response;
span.setAttribute("elasticsearch.shard.broadcast.total", resp.getTotalShards());
span.setAttribute("elasticsearch.shard.broadcast.successful", resp.getSuccessfulShards());
span.setAttribute("elasticsearch.shard.broadcast.failed", resp.getFailedShards());
}
if (response instanceof ReplicationResponse) {
ReplicationResponse resp = (ReplicationResponse) response;
span.setAttribute("elasticsearch.shard.replication.total", resp.getShardInfo().getTotal());
span.setAttribute(
"elasticsearch.shard.replication.successful", resp.getShardInfo().getSuccessful());
span.setAttribute(
"elasticsearch.shard.replication.failed", resp.getShardInfo().getFailed());
}
if (response instanceof IndexResponse) {
span.setAttribute(
"elasticsearch.response.status", ((IndexResponse) response).status().getStatus());
}
if (response instanceof BulkShardResponse) {
BulkShardResponse resp = (BulkShardResponse) response;
span.setAttribute("elasticsearch.shard.bulk.id", resp.getShardId().getId());
span.setAttribute("elasticsearch.shard.bulk.index", resp.getShardId().getIndexName());
}
if (response instanceof BaseNodesResponse) {
BaseNodesResponse<?> resp = (BaseNodesResponse<?>) response;
if (resp.hasFailures()) {
span.setAttribute("elasticsearch.node.failures", resp.failures().size());
}
span.setAttribute("elasticsearch.node.cluster.name", resp.getClusterName().value());
}
}
tracer().end(context);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onResponse(response);
}
}
@Override
public void onFailure(Exception e) {
tracer().endExceptionally(context, e);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onFailure(e);
}
}
}

View File

@ -246,7 +246,7 @@ class Elasticsearch53NodeClientTest extends AbstractElasticsearchNodeClientTest
} }
} }
} }
trace(3, 2) { trace(3, 1) {
span(0) { span(0) {
name "IndexAction" name "IndexAction"
kind CLIENT kind CLIENT
@ -264,17 +264,6 @@ class Elasticsearch53NodeClientTest extends AbstractElasticsearchNodeClientTest
"elasticsearch.shard.replication.failed" 0 "elasticsearch.shard.replication.failed" 0
} }
} }
span(1) {
name "PutMappingAction"
kind CLIENT
childOf span(0)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "elasticsearch"
"${SemanticAttributes.DB_OPERATION.key}" "PutMappingAction"
"elasticsearch.action" "PutMappingAction"
"elasticsearch.request" "PutMappingRequest"
}
}
} }
trace(4, 1) { trace(4, 1) {
span(0) { span(0) {

View File

@ -115,7 +115,7 @@ class Elasticsearch53SpringRepositoryTest extends AgentInstrumentationSpecificat
and: and:
assertTraces(1) { assertTraces(1) {
trace(0, 4) { trace(0, 3) {
span(0) { span(0) {
name "ElasticsearchRepository.index" name "ElasticsearchRepository.index"
kind INTERNAL kind INTERNAL
@ -141,17 +141,6 @@ class Elasticsearch53SpringRepositoryTest extends AgentInstrumentationSpecificat
} }
} }
span(2) { span(2) {
name "PutMappingAction"
kind CLIENT
childOf span(1)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "elasticsearch"
"${SemanticAttributes.DB_OPERATION.key}" "PutMappingAction"
"elasticsearch.action" "PutMappingAction"
"elasticsearch.request" "PutMappingRequest"
}
}
span(3) {
name "RefreshAction" name "RefreshAction"
kind CLIENT kind CLIENT
childOf span(0) childOf span(0)

View File

@ -180,7 +180,7 @@ class Elasticsearch53SpringTemplateTest extends AgentInstrumentationSpecificatio
} }
} }
} }
trace(3, 2) { trace(3, 1) {
span(0) { span(0) {
name "IndexAction" name "IndexAction"
kind CLIENT kind CLIENT
@ -198,17 +198,6 @@ class Elasticsearch53SpringTemplateTest extends AgentInstrumentationSpecificatio
"elasticsearch.shard.replication.total" 2 "elasticsearch.shard.replication.total" 2
} }
} }
span(1) {
name "PutMappingAction"
kind CLIENT
childOf span(0)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "elasticsearch"
"${SemanticAttributes.DB_OPERATION.key}" "PutMappingAction"
"elasticsearch.action" "PutMappingAction"
"elasticsearch.request" "PutMappingRequest"
}
}
} }
trace(4, 1) { trace(4, 1) {
span(0) { span(0) {

View File

@ -6,7 +6,7 @@
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0; package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext; import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer; import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0.Elasticsearch6TransportSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@ -16,6 +16,8 @@ import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope; import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.TransportActionListener;
import net.bytebuddy.asm.Advice; import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatcher;
@ -55,26 +57,38 @@ public class AbstractClientInstrumentation implements TypeInstrumentation {
@Advice.Argument(1) ActionRequest actionRequest, @Advice.Argument(1) ActionRequest actionRequest,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope, @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest,
@Advice.Argument(value = 2, readOnly = false) @Advice.Argument(value = 2, readOnly = false)
ActionListener<ActionResponse> actionListener) { ActionListener<ActionResponse> actionListener) {
transportRequest = ElasticTransportRequest.create(action, actionRequest);
Context parentContext = currentContext(); Context parentContext = currentContext();
context = tracer().startSpan(parentContext, null, action); if (!instrumenter().shouldStart(parentContext, transportRequest)) {
return;
}
context = instrumenter().start(parentContext, transportRequest);
scope = context.makeCurrent(); scope = context.makeCurrent();
tracer().onRequest(context, action.getClass(), actionRequest.getClass());
actionListener = actionListener =
new TransportActionListener<>(actionRequest, actionListener, context, parentContext); new TransportActionListener<>(
instrumenter(), transportRequest, actionListener, context, parentContext);
} }
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan( public static void stopSpan(
@Advice.Thrown Throwable throwable, @Advice.Thrown Throwable throwable,
@Advice.Local("otelContext") Context context, @Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) { @Advice.Local("otelScope") Scope scope,
@Advice.Local("otelRequest") ElasticTransportRequest transportRequest) {
if (scope == null) {
return;
}
scope.close(); scope.close();
if (throwable != null) { if (throwable != null) {
tracer().endExceptionally(context, throwable); instrumenter().end(context, transportRequest, null, throwable);
} }
} }
} }

View File

@ -0,0 +1,28 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportExperimentalAttributesExtractor;
import org.elasticsearch.action.DocWriteRequest;
public class Elasticsearch6TransportExperimentalAttributesExtractor
extends ElasticsearchTransportExperimentalAttributesExtractor {
@Override
protected void onStart(AttributesBuilder attributes, ElasticTransportRequest transportRequest) {
super.onStart(attributes, transportRequest);
Object request = transportRequest.getRequest();
if (request instanceof DocWriteRequest) {
DocWriteRequest<?> req = (DocWriteRequest<?>) request;
attributes.put("elasticsearch.request.write.type", req.type());
attributes.put("elasticsearch.request.write.routing", req.routing());
attributes.put("elasticsearch.request.write.version", req.version());
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0;
import io.opentelemetry.instrumentation.api.instrumenter.net.InetSocketAddressNetAttributesExtractor;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import java.net.InetSocketAddress;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.elasticsearch.action.ActionResponse;
public class Elasticsearch6TransportNetAttributesExtractor
extends InetSocketAddressNetAttributesExtractor<ElasticTransportRequest, ActionResponse> {
@Override
public @Nullable String transport(ElasticTransportRequest elasticTransportRequest) {
return null;
}
@Override
public @Nullable InetSocketAddress getAddress(
ElasticTransportRequest elasticTransportRequest, @Nullable ActionResponse response) {
if (response != null && response.remoteAddress() != null) {
return response.remoteAddress().address();
}
return null;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticTransportRequest;
import io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportInstrumenterFactory;
import org.elasticsearch.action.ActionResponse;
public final class Elasticsearch6TransportSingletons {
private static final Instrumenter<ElasticTransportRequest, ActionResponse> INSTRUMENTER =
ElasticsearchTransportInstrumenterFactory.create(
"io.opentelemetry.elasticsearch-transport-6.0",
new Elasticsearch6TransportExperimentalAttributesExtractor(),
new Elasticsearch6TransportNetAttributesExtractor());
public static Instrumenter<ElasticTransportRequest, ActionResponse> instrumenter() {
return INSTRUMENTER;
}
private Elasticsearch6TransportSingletons() {}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.v6_0;
import static io.opentelemetry.javaagent.instrumentation.elasticsearch.transport.ElasticsearchTransportClientTracer.tracer;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.bulk.BulkShardResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
/**
* Most of this class is identical to version 5's instrumentation, but they changed an interface to
* an abstract class, so the bytecode isn't directly compatible.
*/
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBoolean("otel.instrumentation.elasticsearch.experimental-span-attributes", false);
private final ActionListener<T> listener;
private final Context context;
private final Context parentContext;
public TransportActionListener(
ActionRequest actionRequest,
ActionListener<T> listener,
Context context,
Context parentContext) {
this.listener = listener;
this.context = context;
this.parentContext = parentContext;
onRequest(actionRequest);
}
private void onRequest(ActionRequest request) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Span span = Span.fromContext(context);
if (request instanceof IndicesRequest) {
IndicesRequest req = (IndicesRequest) request;
String[] indices = req.indices();
if (indices != null && indices.length > 0) {
span.setAttribute("elasticsearch.request.indices", String.join(",", indices));
}
}
if (request instanceof SearchRequest) {
SearchRequest req = (SearchRequest) request;
String[] types = req.types();
if (types != null && types.length > 0) {
span.setAttribute("elasticsearch.request.search.types", String.join(",", types));
}
}
if (request instanceof DocWriteRequest) {
DocWriteRequest<?> req = (DocWriteRequest<?>) request;
span.setAttribute("elasticsearch.request.write.type", req.type());
span.setAttribute("elasticsearch.request.write.routing", req.routing());
span.setAttribute("elasticsearch.request.write.version", req.version());
}
}
}
@Override
public void onResponse(T response) {
Span span = Span.fromContext(context);
if (response.remoteAddress() != null) {
NetPeerAttributes.INSTANCE.setNetPeer(
span,
response.remoteAddress().address().getHostName(),
response.remoteAddress().getAddress());
span.setAttribute(
SemanticAttributes.NET_PEER_PORT, (long) response.remoteAddress().getPort());
}
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
if (response instanceof GetResponse) {
GetResponse resp = (GetResponse) response;
span.setAttribute("elasticsearch.type", resp.getType());
span.setAttribute("elasticsearch.id", resp.getId());
span.setAttribute("elasticsearch.version", resp.getVersion());
}
if (response instanceof BroadcastResponse) {
BroadcastResponse resp = (BroadcastResponse) response;
span.setAttribute("elasticsearch.shard.broadcast.total", resp.getTotalShards());
span.setAttribute("elasticsearch.shard.broadcast.successful", resp.getSuccessfulShards());
span.setAttribute("elasticsearch.shard.broadcast.failed", resp.getFailedShards());
}
if (response instanceof ReplicationResponse) {
ReplicationResponse resp = (ReplicationResponse) response;
span.setAttribute("elasticsearch.shard.replication.total", resp.getShardInfo().getTotal());
span.setAttribute(
"elasticsearch.shard.replication.successful", resp.getShardInfo().getSuccessful());
span.setAttribute(
"elasticsearch.shard.replication.failed", resp.getShardInfo().getFailed());
}
if (response instanceof IndexResponse) {
span.setAttribute(
"elasticsearch.response.status", ((IndexResponse) response).status().getStatus());
}
if (response instanceof BulkShardResponse) {
BulkShardResponse resp = (BulkShardResponse) response;
span.setAttribute("elasticsearch.shard.bulk.id", resp.getShardId().getId());
span.setAttribute("elasticsearch.shard.bulk.index", resp.getShardId().getIndexName());
}
if (response instanceof BaseNodesResponse) {
BaseNodesResponse<?> resp = (BaseNodesResponse<?>) response;
if (resp.hasFailures()) {
span.setAttribute("elasticsearch.node.failures", resp.failures().size());
}
span.setAttribute("elasticsearch.node.cluster.name", resp.getClusterName().value());
}
}
tracer().end(context);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onResponse(response);
}
}
@Override
public void onFailure(Exception e) {
tracer().endExceptionally(context, e);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onFailure(e);
}
}
}

View File

@ -227,7 +227,7 @@ class Elasticsearch6NodeClientTest extends AbstractElasticsearchNodeClientTest {
} }
} }
} }
trace(2, 2) { trace(2, 1) {
span(0) { span(0) {
name "IndexAction" name "IndexAction"
kind CLIENT kind CLIENT
@ -245,17 +245,6 @@ class Elasticsearch6NodeClientTest extends AbstractElasticsearchNodeClientTest {
"elasticsearch.shard.replication.failed" 0 "elasticsearch.shard.replication.failed" 0
} }
} }
span(1) {
name ~/(Auto)?PutMappingAction/
kind CLIENT
childOf span(0)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "elasticsearch"
"${SemanticAttributes.DB_OPERATION.key}" ~/(Auto)?PutMappingAction/
"elasticsearch.action" ~/(Auto)?PutMappingAction/
"elasticsearch.request" "PutMappingRequest"
}
}
} }
trace(3, 1) { trace(3, 1) {
span(0) { span(0) {

View File

@ -2,4 +2,9 @@ plugins {
id("otel.library-instrumentation") id("otel.library-instrumentation")
} }
// No dependencies, elasticsearch library not actually used here. dependencies {
compileOnly("org.elasticsearch.client:transport:5.0.0")
compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
}

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.elasticsearch.action.ActionResponse;
public class ElasticTransportNetAttributesExtractor
extends NetAttributesExtractor<ElasticTransportRequest, ActionResponse> {
@Override
public @Nullable String transport(ElasticTransportRequest elasticTransportRequest) {
return null;
}
@Override
public @Nullable String peerName(
ElasticTransportRequest elasticTransportRequest, @Nullable ActionResponse response) {
if (response != null && response.remoteAddress() != null) {
return response.remoteAddress().getHost();
}
return null;
}
@Override
public @Nullable Integer peerPort(
ElasticTransportRequest elasticTransportRequest, @Nullable ActionResponse response) {
if (response != null && response.remoteAddress() != null) {
return response.remoteAddress().getPort();
}
return null;
}
@Override
public @Nullable String peerIp(
ElasticTransportRequest elasticTransportRequest, @Nullable ActionResponse response) {
if (response != null && response.remoteAddress() != null) {
return response.remoteAddress().getAddress();
}
return null;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class ElasticTransportRequest {
public static ElasticTransportRequest create(Object action, Object request) {
return new AutoValue_ElasticTransportRequest(action, request);
}
public abstract Object getAction();
public abstract Object getRequest();
}

View File

@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbAttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.elasticsearch.action.ActionResponse;
final class ElasticsearchTransportAttributesExtractor
extends DbAttributesExtractor<ElasticTransportRequest, ActionResponse> {
@Override
protected String system(ElasticTransportRequest s) {
return SemanticAttributes.DbSystemValues.ELASTICSEARCH;
}
@Override
protected @Nullable String user(ElasticTransportRequest s) {
return null;
}
@Override
protected @Nullable String name(ElasticTransportRequest s) {
return null;
}
@Override
protected @Nullable String connectionString(ElasticTransportRequest s) {
return null;
}
@Override
protected @Nullable String statement(ElasticTransportRequest s) {
return null;
}
@Override
protected @Nullable String operation(ElasticTransportRequest action) {
return action.getAction().getClass().getSimpleName();
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.DatabaseClientTracer;
import io.opentelemetry.instrumentation.api.tracer.net.NetPeerAttributes;
import java.net.InetSocketAddress;
public class ElasticsearchTransportClientTracer extends DatabaseClientTracer<Void, Object, String> {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBoolean("otel.instrumentation.elasticsearch.experimental-span-attributes", false);
private static final ElasticsearchTransportClientTracer TRACER =
new ElasticsearchTransportClientTracer();
private ElasticsearchTransportClientTracer() {
super(NetPeerAttributes.INSTANCE);
}
public static ElasticsearchTransportClientTracer tracer() {
return TRACER;
}
public void onRequest(Context context, Class<?> action, Class<?> request) {
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
Span span = Span.fromContext(context);
span.setAttribute("elasticsearch.action", action.getSimpleName());
span.setAttribute("elasticsearch.request", request.getSimpleName());
}
}
@Override
protected String sanitizeStatement(Object action) {
return action.getClass().getSimpleName();
}
@Override
protected String dbSystem(Void connection) {
return "elasticsearch";
}
@Override
protected InetSocketAddress peerAddress(Void connection) {
return null;
}
@Override
protected String dbOperation(Void connection, Object action, String operation) {
return operation;
}
@Override
protected String getInstrumentationName() {
return "io.opentelemetry.elasticsearch-transport-common";
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.bulk.BulkShardResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.broadcast.BroadcastResponse;
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
public class ElasticsearchTransportExperimentalAttributesExtractor
extends AttributesExtractor<ElasticTransportRequest, ActionResponse> {
@Override
protected void onStart(AttributesBuilder attributes, ElasticTransportRequest transportRequest) {
Object request = transportRequest.getRequest();
attributes.put("elasticsearch.action", transportRequest.getAction().getClass().getSimpleName());
attributes.put("elasticsearch.request", request.getClass().getSimpleName());
if (request instanceof IndicesRequest) {
IndicesRequest req = (IndicesRequest) request;
String[] indices = req.indices();
if (indices != null && indices.length > 0) {
attributes.put("elasticsearch.request.indices", String.join(",", indices));
}
}
if (request instanceof SearchRequest) {
SearchRequest req = (SearchRequest) request;
String[] types = req.types();
if (types != null && types.length > 0) {
attributes.put("elasticsearch.request.search.types", String.join(",", types));
}
}
}
@Override
protected void onEnd(
AttributesBuilder attributes,
ElasticTransportRequest request,
ActionResponse response,
@Nullable Throwable error) {
if (response instanceof GetResponse) {
GetResponse resp = (GetResponse) response;
attributes.put("elasticsearch.type", resp.getType());
attributes.put("elasticsearch.id", resp.getId());
attributes.put("elasticsearch.version", resp.getVersion());
}
if (response instanceof BroadcastResponse) {
BroadcastResponse resp = (BroadcastResponse) response;
attributes.put("elasticsearch.shard.broadcast.total", resp.getTotalShards());
attributes.put("elasticsearch.shard.broadcast.successful", resp.getSuccessfulShards());
attributes.put("elasticsearch.shard.broadcast.failed", resp.getFailedShards());
}
if (response instanceof ReplicationResponse) {
ReplicationResponse resp = (ReplicationResponse) response;
attributes.put("elasticsearch.shard.replication.total", resp.getShardInfo().getTotal());
attributes.put(
"elasticsearch.shard.replication.successful", resp.getShardInfo().getSuccessful());
attributes.put("elasticsearch.shard.replication.failed", resp.getShardInfo().getFailed());
}
if (response instanceof IndexResponse) {
attributes.put(
"elasticsearch.response.status", ((IndexResponse) response).status().getStatus());
}
if (response instanceof BulkShardResponse) {
BulkShardResponse resp = (BulkShardResponse) response;
attributes.put("elasticsearch.shard.bulk.id", resp.getShardId().getId());
attributes.put("elasticsearch.shard.bulk.index", resp.getShardId().getIndexName());
}
if (response instanceof BaseNodesResponse) {
BaseNodesResponse<?> resp = (BaseNodesResponse<?>) response;
if (resp.hasFailures()) {
attributes.put("elasticsearch.node.failures", resp.failures().size());
}
attributes.put("elasticsearch.node.cluster.name", resp.getClusterName().value());
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
import org.elasticsearch.action.ActionResponse;
public final class ElasticsearchTransportInstrumenterFactory {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
.getBoolean("otel.instrumentation.elasticsearch.experimental-span-attributes", false);
public static Instrumenter<ElasticTransportRequest, ActionResponse> create(
String instrumentationName,
AttributesExtractor<ElasticTransportRequest, ActionResponse> experimentalAttributesExtractor,
NetAttributesExtractor<ElasticTransportRequest, ActionResponse> netAttributesExtractor) {
ElasticsearchTransportAttributesExtractor attributesExtractor =
new ElasticsearchTransportAttributesExtractor();
InstrumenterBuilder<ElasticTransportRequest, ActionResponse> instrumenterBuilder =
Instrumenter.<ElasticTransportRequest, ActionResponse>newBuilder(
GlobalOpenTelemetry.get(),
instrumentationName,
DbSpanNameExtractor.create(attributesExtractor))
.addAttributesExtractor(attributesExtractor)
.addAttributesExtractor(netAttributesExtractor);
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
instrumenterBuilder.addAttributesExtractor(experimentalAttributesExtractor);
}
return instrumenterBuilder.newInstrumenter(SpanKindExtractor.alwaysClient());
}
private ElasticsearchTransportInstrumenterFactory() {}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.elasticsearch.transport;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionResponse;
public class TransportActionListener<T extends ActionResponse> implements ActionListener<T> {
private final Instrumenter<ElasticTransportRequest, ActionResponse> instrumenter;
private final ElasticTransportRequest actionRequest;
private final ActionListener<T> listener;
private final Context context;
private final Context parentContext;
public TransportActionListener(
Instrumenter<ElasticTransportRequest, ActionResponse> instrumenter,
ElasticTransportRequest actionRequest,
ActionListener<T> listener,
Context context,
Context parentContext) {
this.instrumenter = instrumenter;
this.actionRequest = actionRequest;
this.listener = listener;
this.context = context;
this.parentContext = parentContext;
}
@Override
public void onResponse(T response) {
instrumenter.end(context, actionRequest, response, null);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onResponse(response);
}
}
@Override
public void onFailure(Exception e) {
instrumenter.end(context, actionRequest, null, e);
try (Scope ignored = parentContext.makeCurrent()) {
listener.onFailure(e);
}
}
}