Add automatic MongoDB instrumentation to the OTel starter (#11211)
Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
This commit is contained in:
parent
39c373e5bd
commit
6b66434258
|
@ -1,4 +1,9 @@
|
|||
services:
|
||||
mongodb:
|
||||
image: mongo:4.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
|
||||
zookeeper:
|
||||
image: confluentinc/cp-zookeeper:6.2.10
|
||||
environment:
|
||||
|
|
|
@ -29,6 +29,7 @@ dependencies {
|
|||
|
||||
implementation(project(":instrumentation-annotations-support"))
|
||||
implementation(project(":instrumentation:kafka:kafka-clients:kafka-clients-2.6:library"))
|
||||
implementation(project(":instrumentation:mongo:mongo-3.1:library"))
|
||||
compileOnly(project(path = ":instrumentation:r2dbc-1.0:library-instrumentation-shaded", configuration = "shadow"))
|
||||
implementation(project(":instrumentation:spring:spring-kafka-2.7:library"))
|
||||
implementation(project(":instrumentation:spring:spring-web:spring-web-3.1:library"))
|
||||
|
@ -49,6 +50,7 @@ dependencies {
|
|||
library("org.springframework.boot:spring-boot-starter-aop:$springBootVersion")
|
||||
library("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
||||
library("org.springframework.boot:spring-boot-starter-webflux:$springBootVersion")
|
||||
library("org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion")
|
||||
library("org.springframework.boot:spring-boot-starter-data-r2dbc:$springBootVersion")
|
||||
|
||||
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.mongo;
|
||||
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
|
||||
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.SdkEnabled;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@ConditionalOnBean(OpenTelemetry.class)
|
||||
@ConditionalOnClass(MongoClientSettings.class)
|
||||
@ConditionalOnProperty(name = "otel.instrumentation.mongo.enabled", matchIfMissing = true)
|
||||
@Conditional(SdkEnabled.class)
|
||||
@Configuration
|
||||
public class MongoClientInstrumentationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public MongoClientSettingsBuilderCustomizer customizer(
|
||||
OpenTelemetry openTelemetry, ConfigProperties config) {
|
||||
return builder ->
|
||||
builder.addCommandListener(
|
||||
MongoTelemetry.builder(openTelemetry)
|
||||
.setStatementSanitizationEnabled(
|
||||
config.getBoolean(
|
||||
"otel.instrumentation.mongo.statement-sanitizer.enabled", true))
|
||||
.build()
|
||||
.newCommandListener());
|
||||
}
|
||||
}
|
|
@ -291,6 +291,18 @@
|
|||
"description": "Enable the capture of experimental Kafka span attributes.",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.mongo.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enable the Mongo client instrumentation.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.mongo.statement-sanitizer.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the DB statement sanitization.",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "otel.instrumentation.log4j-appender.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
|
|
@ -2,6 +2,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|||
io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka.KafkaInstrumentationAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration,\
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.micrometer.MicrometerBridgeAutoConfiguration,\
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.annotations.InstrumentationAnnotationsAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.kafka.KafkaInstrumentationAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.logging.OpenTelemetryAppenderAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.jdbc.JdbcInstrumentationAutoConfiguration
|
||||
io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.micrometer.MicrometerBridgeAutoConfiguration
|
||||
|
|
|
@ -11,12 +11,14 @@ dependencies {
|
|||
runtimeOnly("com.h2database:h2")
|
||||
implementation("org.apache.commons:commons-dbcp2")
|
||||
implementation("org.springframework.kafka:spring-kafka")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
|
||||
implementation(project(":smoke-tests-otel-starter:spring-boot-common"))
|
||||
|
||||
testImplementation("org.testcontainers:junit-jupiter")
|
||||
testImplementation("org.testcontainers:kafka")
|
||||
testImplementation("org.testcontainers:mongodb")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
|
||||
@DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test
|
||||
public class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest {}
|
|
@ -16,12 +16,13 @@ dependencies {
|
|||
runtimeOnly("com.h2database:h2")
|
||||
implementation("org.apache.commons:commons-dbcp2")
|
||||
implementation("org.springframework.kafka:spring-kafka")
|
||||
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
|
||||
implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES))
|
||||
|
||||
implementation(project(":smoke-tests-otel-starter:spring-boot-common"))
|
||||
|
||||
testImplementation("org.testcontainers:junit-jupiter")
|
||||
testImplementation("org.testcontainers:kafka")
|
||||
testImplementation("org.testcontainers:mongodb")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
|
||||
// Necessary for GraalVM native test
|
||||
public class RuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
@ -14,5 +16,16 @@ public class RuntimeHints implements RuntimeHintsRegistrar {
|
|||
public void registerHints(
|
||||
org.springframework.aot.hint.RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerResourceBundle("org.apache.commons.dbcp2.LocalStrings");
|
||||
|
||||
// To avoid Spring native issue with MongoDB: java.lang.ClassNotFoundException:
|
||||
// org.springframework.data.mongodb.core.aggregation.AggregationOperation
|
||||
hints
|
||||
.reflection()
|
||||
.registerType(
|
||||
TypeReference.of(
|
||||
"org.springframework.data.mongodb.core.aggregation.AggregationOperation"),
|
||||
hint -> {
|
||||
hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import org.junit.jupiter.api.condition.EnabledInNativeImage;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
/**
|
||||
* GraalVM native image doesn't support Testcontainers in our case, so the docker container is
|
||||
* started manually before running the tests.
|
||||
*
|
||||
* <p>In other cases, it does work, e.g. <a
|
||||
* href="https://info.michael-simons.eu/2023/10/25/run-your-integration-tests-against-testcontainers-with-graalvm-native-image/">here</a>,
|
||||
* it's not yet clear why it doesn't work in our case.
|
||||
*
|
||||
* <p>In CI, this is done in reusable-native-tests.yml. If you want to run the tests locally, you
|
||||
* need to start the container manually: see .github/workflows/reusable-native-tests.yml for the
|
||||
* command.
|
||||
*/
|
||||
@SpringBootTest(
|
||||
classes = {OtelSpringStarterSmokeTestApplication.class, SpringSmokeOtelConfiguration.class},
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@EnabledInNativeImage // see JvmMongodbSpringStarterSmokeTest for the JVM test
|
||||
@EnabledInGithubActions
|
||||
public class GraalVmNativeMongodbSpringStarterSmokeTest
|
||||
extends AbstractMongodbSpringStarterSmokeTest {}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import org.junit.jupiter.api.condition.DisabledInNativeImage;
|
||||
|
||||
@DisabledInNativeImage // See GraalVmNativeMongodbSpringStarterSmokeTest for the GraalVM native test
|
||||
public class MongoSpringStarterSmokeTest extends AbstractJvmMongodbSpringStarterSmokeTest {}
|
|
@ -15,8 +15,10 @@ dependencies {
|
|||
compileOnly("org.springframework.boot:spring-boot-starter-data-jdbc")
|
||||
compileOnly("org.apache.commons:commons-dbcp2")
|
||||
compileOnly("org.springframework.kafka:spring-kafka")
|
||||
compileOnly("org.springframework.boot:spring-boot-starter-data-mongodb")
|
||||
compileOnly("org.testcontainers:junit-jupiter")
|
||||
compileOnly("org.testcontainers:kafka")
|
||||
compileOnly("org.testcontainers:mongodb")
|
||||
|
||||
api(project(":smoke-tests-otel-starter:spring-smoke-testing"))
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
|
||||
import io.opentelemetry.instrumentation.spring.autoconfigure.instrumentation.mongo.MongoClientInstrumentationAutoConfiguration;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.testcontainers.containers.MongoDBContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
|
||||
/** Spring has a test container integration, but that doesn't work for Spring Boot 2 */
|
||||
public class AbstractJvmMongodbSpringStarterSmokeTest
|
||||
extends AbstractMongodbSpringStarterSmokeTest {
|
||||
|
||||
@Container static MongoDBContainer container;
|
||||
|
||||
private ApplicationContextRunner contextRunner;
|
||||
|
||||
@BeforeAll
|
||||
static void setUpContainer() {
|
||||
container = new MongoDBContainer("mongo:4.0");
|
||||
container.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDownContainer() {
|
||||
container.stop();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext() {
|
||||
contextRunner =
|
||||
new ApplicationContextRunner()
|
||||
.withAllowBeanDefinitionOverriding(true)
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(
|
||||
OpenTelemetryAutoConfiguration.class,
|
||||
SpringSmokeOtelConfiguration.class,
|
||||
MongoAutoConfiguration.class,
|
||||
MongoClientInstrumentationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.data.mongodb.uri=" + container.getReplicaSetUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
void mongodb() {
|
||||
contextRunner.run(
|
||||
applicationContext -> {
|
||||
testing = new SpringSmokeTestRunner(applicationContext.getBean(OpenTelemetry.class));
|
||||
mongoClient = applicationContext.getBean(MongoClient.class);
|
||||
super.mongodb();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -30,8 +30,6 @@ abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarter
|
|||
|
||||
@Test
|
||||
void shouldInstrumentProducerAndConsumer() {
|
||||
testing.clearAllExportedData(); // ignore data from application startup
|
||||
|
||||
testing.runWithSpan(
|
||||
"producer",
|
||||
() -> {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
|
||||
import java.util.ArrayList;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
abstract class AbstractMongodbSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest {
|
||||
|
||||
@Autowired protected MongoClient mongoClient;
|
||||
|
||||
@Test
|
||||
void mongodb() {
|
||||
testing.runWithSpan(
|
||||
"server",
|
||||
() -> {
|
||||
mongoClient.listDatabaseNames().into(new ArrayList<>());
|
||||
});
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("server"),
|
||||
span ->
|
||||
span.hasKind(SpanKind.CLIENT)
|
||||
.hasName("listDatabases admin")
|
||||
.hasAttribute(
|
||||
DbIncubatingAttributes.DB_SYSTEM,
|
||||
DbIncubatingAttributes.DbSystemValues.MONGODB)));
|
||||
}
|
||||
}
|
|
@ -31,12 +31,16 @@ import org.assertj.core.api.AbstractIterableAssert;
|
|||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
/**
|
||||
* This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()},
|
||||
|
@ -54,6 +58,14 @@ class AbstractOtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest
|
|||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
@Autowired private ObjectProvider<JdbcTemplate> jdbcTemplate;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void loadData() {
|
||||
jdbcTemplate
|
||||
.getObject()
|
||||
.execute("create table test_table (id bigint not null, primary key (id))");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(1)
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SqlExecutor {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public SqlExecutor(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void loadData() {
|
||||
jdbcTemplate.execute("create table test_table (id bigint not null, primary key (id))");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.spring.smoketest;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@EnabledIfEnvironmentVariable(
|
||||
named = "GITHUB_ACTIONS",
|
||||
matches = "true",
|
||||
disabledReason =
|
||||
"Not currently executing within GitHub actions where the required external "
|
||||
+ "services (e.g. mongodb) are started using docker.")
|
||||
public @interface EnabledInGithubActions {}
|
Loading…
Reference in New Issue