diff --git a/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DbSpanDecorator.java b/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DbSpanDecorator.java index f831cf8b61c3..990e6ade635a 100644 --- a/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DbSpanDecorator.java +++ b/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DbSpanDecorator.java @@ -53,6 +53,7 @@ public String getOperationName( switch (component) { case "mongodb": case "elasticsearch": + case "opensearch": Map queryParameters = toQueryParameters(endpoint.getEndpointUri()); if (queryParameters.containsKey("operation")) { return queryParameters.get("operation"); @@ -102,6 +103,7 @@ private String getDbName(Endpoint endpoint) { } return null; case "elasticsearch": + case "opensearch": Map elasticsearchParameters = toQueryParameters(endpoint.getEndpointUri()); if (elasticsearchParameters.containsKey("indexName")) { return elasticsearchParameters.get("indexName"); diff --git a/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DecoratorRegistry.java b/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DecoratorRegistry.java index c8f2ec465ea2..1b052c9b9e97 100644 --- a/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DecoratorRegistry.java +++ b/instrumentation/apache-camel-2.20/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachecamel/decorators/DecoratorRegistry.java @@ -31,6 +31,7 @@ private static Map 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()); diff --git a/instrumentation/opensearch/README.md b/instrumentation/opensearch/README.md new file mode 100644 index 000000000000..a23faee716b8 --- /dev/null +++ b/instrumentation/opensearch/README.md @@ -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. | \ No newline at end of file diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/build.gradle.kts b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/build.gradle.kts new file mode 100644 index 000000000000..460f5ec15e60 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/build.gradle.kts @@ -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) + } +} diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestInstrumentationModule.java b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestInstrumentationModule.java new file mode 100644 index 000000000000..78b372a914b6 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestInstrumentationModule.java @@ -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 classLoaderMatcher() { + // class introduced in 1.0.0 + return hasClassesNamed("org.opensearch.client.RestClient$InternalRequest"); + } + + @Override + public List typeInstrumentations() { + return singletonList(new RestClientInstrumentation()); + } +} diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestSingletons.java b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestSingletons.java new file mode 100644 index 000000000000..455e40f26b14 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/OpenSearchRestSingletons.java @@ -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 INSTRUMENTER = + OpenSearchRestInstrumenterFactory.create("io.opentelemetry.opensearch-rest-1.0"); + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + private OpenSearchRestSingletons() {} +} diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/RestClientInstrumentation.java b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/RestClientInstrumentation.java new file mode 100644 index 000000000000..85748ea152f1 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/v1_0/RestClientInstrumentation.java @@ -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 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 + } + } +} diff --git a/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java new file mode 100644 index 000000000000..70e183364b6b --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-1.0/javaagent/src/test/java/OpenSearchRestTest.java @@ -0,0 +1,196 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLContext; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.ssl.SSLContextBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.opensearch.client.Request; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseListener; +import org.opensearch.client.RestClient; +import org.opensearch.testcontainers.OpensearchContainer; +import org.testcontainers.utility.DockerImageName; + +public class OpenSearchRestTest { + @RegisterExtension + static final AgentInstrumentationExtension testing = AgentInstrumentationExtension.create(); + + static OpensearchContainer opensearch; + static RestClient client; + + static HttpHost httpHost; + + @BeforeAll + static void setUpOpenSearch() + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + + opensearch = + new OpensearchContainer(DockerImageName.parse("opensearchproject/opensearch:1.3.6")) + .withSecurityEnabled(); + // limit memory usage + opensearch.withEnv("OPENSEARCH_JAVA_OPTS", "-Xmx256m -Xms256m"); + opensearch.start(); + + CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(opensearch.getUsername(), opensearch.getPassword())); + + SSLContext sslContext = + SSLContextBuilder.create().loadTrustMaterial(null, new TrustAllStrategy()).build(); + + httpHost = HttpHost.create(opensearch.getHttpHostAddress()); + client = + RestClient.builder(httpHost) + .setHttpClientConfigCallback( + httpClientBuilder -> + httpClientBuilder + .setSSLContext(sslContext) + .setDefaultCredentialsProvider(credentialsProvider)) + .build(); + } + + @AfterAll + static void tearDownOpenSearch() { + opensearch.stop(); + } + + @Test + void shouldGetStatusWithTraces() throws IOException { + + client.performRequest(new Request("GET", "_cluster/health")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "opensearch"), + equalTo(SemanticAttributes.DB_OPERATION, "GET"), + equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health"), + equalTo( + SemanticAttributes.NET_TRANSPORT, + SemanticAttributes.NetTransportValues.IP_TCP)), + span -> + span.hasName("HTTP GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo( + SemanticAttributes.NET_TRANSPORT, + SemanticAttributes.NetTransportValues.IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo( + SemanticAttributes.HTTP_FLAVOR, + SemanticAttributes.HttpFlavorValues.HTTP_1_1), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 415L)))); + } + + @Test + void shouldGetStatusAsyncWithTraces() throws Exception { + AtomicReference requestResponse = new AtomicReference<>(null); + AtomicReference exception = new AtomicReference<>(null); + CountDownLatch countDownLatch = new CountDownLatch(1); + + ResponseListener responseListener = + new ResponseListener() { + @Override + public void onSuccess(Response response) { + testing.runWithSpan( + "callback", + () -> { + requestResponse.set(response); + countDownLatch.countDown(); + }); + } + + @Override + public void onFailure(Exception e) { + testing.runWithSpan( + "callback", + () -> { + exception.set(e); + countDownLatch.countDown(); + }); + } + }; + + testing.runWithSpan( + "client", + () -> { + client.performRequestAsync(new Request("GET", "_cluster/health"), responseListener); + }); + countDownLatch.await(); + + if (exception.get() != null) { + throw exception.get(); + } + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("client").hasKind(SpanKind.INTERNAL), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.DB_SYSTEM, "opensearch"), + equalTo(SemanticAttributes.DB_OPERATION, "GET"), + equalTo(SemanticAttributes.DB_STATEMENT, "GET _cluster/health"), + equalTo( + SemanticAttributes.NET_TRANSPORT, + SemanticAttributes.NetTransportValues.IP_TCP)), + span -> + span.hasName("HTTP GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfyingExactly( + equalTo( + SemanticAttributes.NET_TRANSPORT, + SemanticAttributes.NetTransportValues.IP_TCP), + equalTo(SemanticAttributes.NET_PEER_NAME, httpHost.getHostName()), + equalTo(SemanticAttributes.NET_PEER_PORT, httpHost.getPort()), + equalTo(SemanticAttributes.HTTP_METHOD, "GET"), + equalTo( + SemanticAttributes.HTTP_FLAVOR, + SemanticAttributes.HttpFlavorValues.HTTP_1_1), + equalTo( + SemanticAttributes.HTTP_URL, httpHost.toURI() + "/_cluster/health"), + equalTo(SemanticAttributes.HTTP_STATUS_CODE, 200L), + equalTo(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH, 415L)), + span -> + span.hasName("callback") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)))); + } +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/build.gradle.kts b/instrumentation/opensearch/opensearch-rest-common/javaagent/build.gradle.kts new file mode 100644 index 000000000000..5053d30b7a9f --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +dependencies { + compileOnly("org.opensearch.client:opensearch-rest-client:1.3.6") + compileOnly("com.google.auto.value:auto-value-annotations") + + annotationProcessor("com.google.auto.value:auto-value") +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestAttributesGetter.java b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestAttributesGetter.java new file mode 100644 index 000000000000..1eb7a2bf59b9 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestAttributesGetter.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.opensearch.rest; + +import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; + +final class OpenSearchRestAttributesGetter + implements DbClientAttributesGetter { + + @Override + public String system(OpenSearchRestRequest request) { + return SemanticAttributes.DbSystemValues.OPENSEARCH; + } + + @Override + @Nullable + public String user(OpenSearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String name(OpenSearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String connectionString(OpenSearchRestRequest request) { + return null; + } + + @Override + @Nullable + public String statement(OpenSearchRestRequest request) { + return request.getMethod() + " " + request.getOperation(); + } + + @Override + @Nullable + public String operation(OpenSearchRestRequest request) { + return request.getMethod(); + } +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestInstrumenterFactory.java b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestInstrumenterFactory.java new file mode 100644 index 000000000000..2d05930701b0 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestInstrumenterFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.opensearch.rest; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; +import org.opensearch.client.Response; + +public final class OpenSearchRestInstrumenterFactory { + + public static Instrumenter create(String instrumentationName) { + OpenSearchRestAttributesGetter dbClientAttributesGetter = new OpenSearchRestAttributesGetter(); + OpenSearchRestNetResponseAttributesGetter netAttributesGetter = + new OpenSearchRestNetResponseAttributesGetter(); + + return Instrumenter.builder( + GlobalOpenTelemetry.get(), + instrumentationName, + DbClientSpanNameExtractor.create(dbClientAttributesGetter)) + .addAttributesExtractor(DbClientAttributesExtractor.create(dbClientAttributesGetter)) + .addAttributesExtractor(NetClientAttributesExtractor.create(netAttributesGetter)) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .buildInstrumenter(SpanKindExtractor.alwaysClient()); + } + + private OpenSearchRestInstrumenterFactory() {} +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestNetResponseAttributesGetter.java b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestNetResponseAttributesGetter.java new file mode 100644 index 000000000000..b8a945d9e4b4 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestNetResponseAttributesGetter.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.opensearch.rest; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.Inet6Address; +import javax.annotation.Nullable; +import org.opensearch.client.Response; + +final class OpenSearchRestNetResponseAttributesGetter + implements NetClientAttributesGetter { + + @Override + public String transport(OpenSearchRestRequest request, Response response) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @Override + @Nullable + public String peerName(OpenSearchRestRequest request) { + return null; + } + + @Override + @Nullable + public Integer peerPort(OpenSearchRestRequest request) { + return null; + } + + @Nullable + @Override + public String sockFamily( + OpenSearchRestRequest elasticsearchRestRequest, @Nullable Response response) { + if (response != null && response.getHost().getAddress() instanceof Inet6Address) { + return "inet6"; + } + return null; + } + + @Override + @Nullable + public String sockPeerAddr(OpenSearchRestRequest request, @Nullable Response response) { + if (response != null && response.getHost().getAddress() != null) { + return response.getHost().getAddress().getHostAddress(); + } + return null; + } +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestRequest.java b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestRequest.java new file mode 100644 index 000000000000..856fdbf50d53 --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/OpenSearchRestRequest.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.opensearch.rest; + +import com.google.auto.value.AutoValue; + +@AutoValue +public abstract class OpenSearchRestRequest { + + public static OpenSearchRestRequest create(String method, String endpoint) { + return new AutoValue_OpenSearchRestRequest(method, endpoint); + } + + public abstract String getMethod(); + + public abstract String getOperation(); +} diff --git a/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/RestResponseListener.java b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/RestResponseListener.java new file mode 100644 index 000000000000..fe3925aaa2cc --- /dev/null +++ b/instrumentation/opensearch/opensearch-rest-common/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/opensearch/rest/RestResponseListener.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.opensearch.rest; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import org.opensearch.client.Response; +import org.opensearch.client.ResponseListener; + +public final class RestResponseListener implements ResponseListener { + + private final ResponseListener listener; + private final Context parentContext; + private final Instrumenter instrumenter; + private final Context context; + private final OpenSearchRestRequest request; + + public RestResponseListener( + ResponseListener listener, + Context parentContext, + Instrumenter instrumenter, + Context context, + OpenSearchRestRequest request) { + this.listener = listener; + this.parentContext = parentContext; + this.instrumenter = instrumenter; + this.context = context; + this.request = request; + } + + @Override + public void onSuccess(Response response) { + instrumenter.end(context, request, response, null); + try (Scope ignored = parentContext.makeCurrent()) { + listener.onSuccess(response); + } + } + + @Override + public void onFailure(Exception e) { + instrumenter.end(context, request, null, e); + try (Scope ignored = parentContext.makeCurrent()) { + listener.onFailure(e); + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 18f1f5a16e30..3abaa2bb9452 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -204,9 +204,11 @@ include(":instrumentation:dropwizard:dropwizard-metrics-4.0:javaagent") include(":instrumentation:dropwizard:dropwizard-views-0.7:javaagent") include(":instrumentation:dropwizard:dropwizard-testing") include(":instrumentation:elasticsearch:elasticsearch-rest-common:javaagent") +include(":instrumentation:opensearch:opensearch-rest-common:javaagent") include(":instrumentation:elasticsearch:elasticsearch-rest-5.0:javaagent") include(":instrumentation:elasticsearch:elasticsearch-rest-6.4:javaagent") include(":instrumentation:elasticsearch:elasticsearch-rest-7.0:javaagent") +include(":instrumentation:opensearch:opensearch-rest-1.0:javaagent") include(":instrumentation:elasticsearch:elasticsearch-transport-common:javaagent") include(":instrumentation:elasticsearch:elasticsearch-transport-common:testing") include(":instrumentation:elasticsearch:elasticsearch-transport-5.0:javaagent")