Skip to content

Commit

Permalink
Add automatic MongoDB instrumentation to the OTel starter (#11211)
Browse files Browse the repository at this point in the history
Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
  • Loading branch information
zeitlinger and jeanbisutti committed May 16, 2024
1 parent 39c373e commit 6b66434
Show file tree
Hide file tree
Showing 19 changed files with 270 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .github/graal-native-docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
services:
mongodb:
image: mongo:4.0
ports:
- "27017:27017"

zookeeper:
image: confluentinc/cp-zookeeper:6.2.10
environment:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,\
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions smoke-tests-otel-starter/spring-boot-2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {}
3 changes: 2 additions & 1 deletion smoke-tests-otel-starter/spring-boot-3/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
});
}
}
Original file line number Diff line number Diff line change
@@ -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 {}
Original file line number Diff line number Diff line change
@@ -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 {}
2 changes: 2 additions & 0 deletions smoke-tests-otel-starter/spring-boot-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ abstract class AbstractKafkaSpringStarterSmokeTest extends AbstractSpringStarter

@Test
void shouldInstrumentProducerAndConsumer() {
testing.clearAllExportedData(); // ignore data from application startup

testing.runWithSpan(
"producer",
() -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@
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()},
Expand All @@ -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)
Expand Down

This file was deleted.

Loading

0 comments on commit 6b66434

Please sign in to comment.