Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OpenSearch 1.x and 2.x auto-instrumentation #6998

Merged
merged 12 commits into from
Nov 10, 2022
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