Skip to content

Commit

Permalink
Add support for OpenSearch 1.x and 2.x auto-instrumentation (#6998)
Browse files Browse the repository at this point in the history
This PR adds support for OpenSearch 1.x and 2.x Java clients
auto-instrumentation.

This is made possible by OpenTelemetry specification v1.14.0 and
OpenTelemetry Java SDK v1.19.0.

Testing is being done using
org.opensearch:opensearch-testcontainers:2.0.0
(https://github.com/opensearch-project/opensearch-testcontainers)

Resolves #7007 

Signed-off-by: Cédric Pelvet <cedric.pelvet@gmail.com>

Signed-off-by: Cédric Pelvet <cedric.pelvet@gmail.com>
Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
  • Loading branch information
sharp-pixel and trask authored Nov 10, 2022
1 parent b9b3fea commit 31f4d12
Show file tree
Hide file tree
Showing 15 changed files with 658 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public String getOperationName(
switch (component) {
case "mongodb":
case "elasticsearch":
case "opensearch":
Map<String, String> queryParameters = toQueryParameters(endpoint.getEndpointUri());
if (queryParameters.containsKey("operation")) {
return queryParameters.get("operation");
Expand Down Expand Up @@ -102,6 +103,7 @@ private String getDbName(Endpoint endpoint) {
}
return null;
case "elasticsearch":
case "opensearch":
Map<String, String> elasticsearchParameters = toQueryParameters(endpoint.getEndpointUri());
if (elasticsearchParameters.containsKey("indexName")) {
return elasticsearchParameters.get("indexName");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ private static Map<String, SpanDecorator> loadDecorators() {
result.put("disruptor", new InternalSpanDecorator());
result.put("disruptor-vm", new InternalSpanDecorator());
result.put("elasticsearch", new DbSpanDecorator("elasticsearch", "elasticsearch"));
result.put("opensearch", new DbSpanDecorator("opensearch", "opensearch"));
result.put("http4", new Http4SpanDecorator());
result.put("https4", new Https4SpanDecorator());
result.put("http", new HttpSpanDecorator());
Expand Down
5 changes: 5 additions & 0 deletions instrumentation/opensearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Settings for the OpenSearch instrumentation

| System property | Type | Default | Description |
|----------------------------------------------------------------|-----------|---------|-----------------------------------------------------|
| `otel.instrumentation.opensearch.experimental-span-attributes` | `Boolean` | `false` | Enable the capture of experimental span attributes. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.opensearch.client")
module.set("opensearch-rest-client")
versions.set("[1.0,)")
assertInverse.set(true)
}

fail {
group.set("org.opensearch.client")
module.set("rest")
versions.set("(,)")
}
}

otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}

dependencies {
library("org.opensearch.client:opensearch-rest-client:1.0.0")

implementation(project(":instrumentation:opensearch:opensearch-rest-common:javaagent"))

testInstrumentation(project(":instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent"))
testInstrumentation(project(":instrumentation:apache-httpasyncclient-4.1:javaagent"))

testImplementation("org.apache.logging.log4j:log4j-core:2.18.0")
testImplementation("org.apache.logging.log4j:log4j-api:2.18.0")
testImplementation("org.apache.commons:commons-lang3:3.12.0")
testImplementation("commons-io:commons-io:2.11.0")
testImplementation("org.opensearch:opensearch-testcontainers:2.0.0")
}

tasks {
test {
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opensearch.rest.v1_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Collections.singletonList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public class OpenSearchRestInstrumentationModule extends InstrumentationModule {
public OpenSearchRestInstrumentationModule() {
super("opensearch-rest", "opensearch-rest-1.0", "opensearch");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
// class introduced in 1.0.0
return hasClassesNamed("org.opensearch.client.RestClient$InternalRequest");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new RestClientInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opensearch.rest.v1_0;

import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.instrumentation.opensearch.rest.OpenSearchRestInstrumenterFactory;
import io.opentelemetry.javaagent.instrumentation.opensearch.rest.OpenSearchRestRequest;
import org.opensearch.client.Response;

public final class OpenSearchRestSingletons {

private static final Instrumenter<OpenSearchRestRequest, Response> INSTRUMENTER =
OpenSearchRestInstrumenterFactory.create("io.opentelemetry.opensearch-rest-1.0");

public static Instrumenter<OpenSearchRestRequest, Response> instrumenter() {
return INSTRUMENTER;
}

private OpenSearchRestSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.opensearch.rest.v1_0;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.opensearch.rest.OpenSearchRestRequest;
import io.opentelemetry.javaagent.instrumentation.opensearch.rest.RestResponseListener;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.opensearch.client.Request;
import org.opensearch.client.Response;
import org.opensearch.client.ResponseListener;

public class RestClientInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.opensearch.client.RestClient");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("performRequest"))
.and(takesArguments(1))
.and(takesArgument(0, named("org.opensearch.client.Request"))),
this.getClass().getName() + "$PerformRequestAdvice");
transformer.applyAdviceToMethod(
isMethod()
.and(named("performRequestAsync"))
.and(takesArguments(2))
.and(takesArgument(0, named("org.opensearch.client.Request")))
.and(takesArgument(1, named("org.opensearch.client.ResponseListener"))),
this.getClass().getName() + "$PerformRequestAsyncAdvice");
}

@SuppressWarnings("unused")
public static class PerformRequestAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) Request request,
@Advice.Local("otelRequest") OpenSearchRestRequest otelRequest,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

Context parentContext = currentContext();
otelRequest = OpenSearchRestRequest.create(request.getMethod(), request.getEndpoint());
if (!OpenSearchRestSingletons.instrumenter().shouldStart(parentContext, otelRequest)) {
return;
}

context = OpenSearchRestSingletons.instrumenter().start(parentContext, otelRequest);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Return Response response,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") OpenSearchRestRequest otelRequest,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

if (scope == null) {
return;
}
scope.close();

OpenSearchRestSingletons.instrumenter().end(context, otelRequest, response, throwable);
}
}

@SuppressWarnings("unused")
public static class PerformRequestAsyncAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) Request request,
@Advice.Argument(value = 1, readOnly = false) ResponseListener responseListener,
@Advice.Local("otelRequest") OpenSearchRestRequest otelRequest,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

Context parentContext = currentContext();
otelRequest = OpenSearchRestRequest.create(request.getMethod(), request.getEndpoint());
if (!OpenSearchRestSingletons.instrumenter().shouldStart(parentContext, otelRequest)) {
return;
}

context = OpenSearchRestSingletons.instrumenter().start(parentContext, otelRequest);
scope = context.makeCurrent();

responseListener =
new RestResponseListener(
responseListener,
parentContext,
OpenSearchRestSingletons.instrumenter(),
context,
otelRequest);
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") OpenSearchRestRequest otelRequest,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {

if (scope == null) {
return;
}
scope.close();

if (throwable != null) {
OpenSearchRestSingletons.instrumenter().end(context, otelRequest, null, throwable);
}
// span ended in RestResponseListener
}
}
}
Loading

0 comments on commit 31f4d12

Please sign in to comment.