Compare commits

..

19 Commits

Author SHA1 Message Date
Mark Paluch
c04c3d66a3 Release version 4.0 RC2 (2022.0.0).
See #4215
2022-11-04 15:23:17 +01:00
Mark Paluch
b204c1b33e Prepare 4.0 RC2 (2022.0.0).
See #4215
2022-11-04 15:23:06 +01:00
Christoph Strobl
dfa029b341 Guard transaction proxy creation hints.
Closes: #4225
Related: #4221
2022-11-03 12:48:49 +01:00
Christoph Strobl
04a8c47cda Follow API changes in data-commons
Update imports of moved AOT processing types and update reactive wrapper coordinates to new location.

Closes: #4224
See: spring-projects/spring-data-commons#2708
2022-11-02 11:51:11 +01:00
Christoph Strobl
88ea57f2be Provide native hints to create transaction proxies at runtime.
Closes: #4221
2022-11-02 10:26:45 +01:00
Mark Paluch
521bbd2535 Update CI properties.
See #4215
2022-10-31 10:36:34 +01:00
Mark Paluch
0474632640 Upgrade to Java 17.0.4.1_1 and pin base image distribution.
See #4215
2022-10-31 10:27:36 +01:00
Mark Paluch
8aab5e5a01 Use correct boolean type for JSON Schema creation.
We now use the correct JSON type boolean again when creating schemas. Furthermore, we use the bool type for MongoDB $type queries.

Closes #4220
2022-10-27 10:16:58 +02:00
Christoph Strobl
3db5fc728e Polishing.
See: #4211
Original pull request: #4212
2022-10-24 15:11:25 +02:00
Christoph Strobl
b027f15a4c Add missing runtime hint for QuerydslMongoPredicateExecutor.
Closes: #4211
Original pull request: #4212
2022-10-24 15:11:10 +02:00
Mark Paluch
fd0a554d59 Polishing.
Use existing constants.

See #4218
2022-10-24 15:04:50 +02:00
Marcin Grzejszczak
d4daa305a8 Align the context propagation entries with the rest of the portfolio.
Closes #4218
2022-10-24 15:04:33 +02:00
Mark Paluch
2d63d6006d Align conventions with OpenTelemetry spec.
See: #4216
2022-10-21 11:49:03 +02:00
Mark Paluch
5007e68cc1 Polishing.
See: #4216
2022-10-21 11:49:03 +02:00
Mark Paluch
3ea4e0f9dd Update documentation.
See: #4216
2022-10-21 11:49:03 +02:00
Greg L. Turnquist
e9ac77c058 Improve configuration support for Observability integration.
Closes: #4216
2022-10-21 11:27:58 +02:00
Christoph Strobl
daef8b6e8e Add missing reflection hints for generated cglib proxies.
Closes: #4217
2022-10-20 15:59:06 +02:00
Mark Paluch
f671a9bd43 After release cleanups.
See #4175
2022-10-13 17:31:16 +02:00
Mark Paluch
57b52862c8 Prepare next development iteration.
See #4175
2022-10-13 17:31:15 +02:00
43 changed files with 1052 additions and 754 deletions

View File

@@ -9,10 +9,11 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/ports.ubuntu.com/mirrors.ocf.berkeley.edu/g' /etc/apt/sources.list && \
sed -i -e 's/http/https/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 && \
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 656408E390CFB1F5 && \
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list && \
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list && \
echo ${TZ} > /etc/timezone
RUN apt-get update && \

View File

@@ -9,12 +9,13 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/ports.ubuntu.com/mirrors.ocf.berkeley.edu/g' /etc/apt/sources.list && \
sed -i -e 's/http/https/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 wget && \
# MongoDB 5.0 release signing key
apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv B00A0BD1E2C63C11 && \
# Needed when MongoDB creates a 5.0 folder.
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list && \
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list && \
echo ${TZ} > /etc/timezone
RUN apt-get update && \

View File

@@ -9,12 +9,13 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN set -eux; \
sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
sed -i -e 's/ports.ubuntu.com/mirrors.ocf.berkeley.edu/g' /etc/apt/sources.list && \
sed -i -e 's/http/https/g' /etc/apt/sources.list && \
apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 wget && \
# MongoDB 6.0 release signing key
wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | apt-key add - && \
# Needed when MongoDB creates a 6.0 folder.
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list && \
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list && \
echo ${TZ} > /etc/timezone
RUN apt-get update && \

View File

@@ -1,19 +1,19 @@
# Java versions
java.main.tag=17.0.2_8-jdk
java.main.tag=17.0.4.1_1-jdk-focal
# Docker container images - standard
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
# Supported versions of MongoDB
docker.mongodb.4.4.version=4.4.12
docker.mongodb.5.0.version=5.0.6
docker.mongodb.6.0.version=6.0.0
docker.mongodb.4.4.version=4.4.17
docker.mongodb.5.0.version=5.0.13
docker.mongodb.6.0.version=6.0.2
# Supported versions of Redis
docker.redis.6.version=6.2.6
# Supported versions of Cassandra
docker.cassandra.3.version=3.11.12
docker.cassandra.3.version=3.11.14
# Docker environment settings
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-RC1</version>
<version>4.0.0-RC2</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.0.0-RC1</version>
<version>3.0.0-RC2</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>3.0.0-RC1</springdata.commons>
<springdata.commons>3.0.0-RC2</springdata.commons>
<mongo>4.8.0-beta0</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-RC1</version>
<version>4.0.0-RC2</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-RC1</version>
<version>4.0.0-RC2</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -40,50 +40,30 @@
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-metrics-metadata</id>
<phase>prepare-package</phase>
<id>generate-docs</id>
<phase>generate-resources</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.metrics.DocsFromSources
</mainClass>
</configuration>
</execution>
<execution>
<id>generate-tracing-metadata</id>
<phase>prepare-package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>io.micrometer.docs.spans.DocsFromSources
</mainClass>
<mainClass>io.micrometer.docs.DocsGeneratorCommand</mainClass>
<includePluginDependencies>true</includePluginDependencies>
<arguments>
<argument>${micrometer-docs-generator.inputPath}</argument>
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
<argument>${micrometer-docs-generator.outputPath}</argument>
</arguments>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator-spans</artifactId>
<version>${micrometer-docs-generator}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-docs-generator-metrics</artifactId>
<artifactId>micrometer-docs-generator</artifactId>
<version>${micrometer-docs-generator}</version>
<type>jar</type>
</dependency>
</dependencies>
<configuration>
<includePluginDependencies>true</includePluginDependencies>
<arguments>
<argument>${micrometer-docs-generator.inputPath}</argument>
<argument>${micrometer-docs-generator.inclusionPattern}</argument>
<argument>${micrometer-docs-generator.outputPath}</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.asciidoctor</groupId>

View File

@@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-RC1</version>
<version>4.0.0-RC2</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -23,26 +23,27 @@ import java.util.List;
import java.util.Set;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.annotation.Reference;
import org.springframework.data.aot.TypeUtils;
import org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory;
import org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory.LazyLoadingInterceptor;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import org.springframework.data.util.TypeUtils;
/**
* @author Christoph Strobl
* @since 4.0
*/
class LazyLoadingProxyAotProcessor {
public class LazyLoadingProxyAotProcessor {
private boolean generalLazyLoadingProxyContributed = false;
void registerLazyLoadingProxyIfNeeded(Class<?> type, GenerationContext generationContext) {
public void registerLazyLoadingProxyIfNeeded(Class<?> type, GenerationContext generationContext) {
Set<Field> refFields = getFieldsWithAnnotationPresent(type, Reference.class);
if (refFields.isEmpty()) {
@@ -74,7 +75,13 @@ class LazyLoadingProxyAotProcessor {
generationContext.getRuntimeHints().proxies().registerJdkProxy(interfaces.toArray(Class[]::new));
} else {
LazyLoadingProxyFactory.resolveProxyType(field.getType(), () -> LazyLoadingInterceptor.none());
Class<?> proxyClass = LazyLoadingProxyFactory.resolveProxyType(field.getType(),
() -> LazyLoadingInterceptor.none());
// see: spring-projects/spring-framework/issues/29309
generationContext.getRuntimeHints().reflection().registerType(proxyClass,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS);
}
});
}

View File

@@ -17,15 +17,29 @@ package org.springframework.data.mongodb.aot;
import java.util.function.Predicate;
import org.springframework.data.aot.TypeUtils;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.ReactiveWrappers.ReactiveLibrary;
import org.springframework.data.util.TypeUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* @author Christoph Strobl
* @since 4.0
*/
class MongoAotPredicates {
public class MongoAotPredicates {
static final Predicate<Class<?>> IS_SIMPLE_TYPE = (type) -> MongoSimpleTypes.HOLDER.isSimpleType(type) || TypeUtils.type(type).isPartOf("org.bson");
public static final Predicate<Class<?>> IS_SIMPLE_TYPE = (type) -> MongoSimpleTypes.HOLDER.isSimpleType(type) || TypeUtils.type(type).isPartOf("org.bson");
public static final Predicate<ReactiveLibrary> IS_REACTIVE_LIBARARY_AVAILABLE = (lib) -> ReactiveWrappers.isAvailable(lib);
public static final Predicate<ClassLoader> IS_SYNC_CLIENT_PRESENT = (classLoader) -> ClassUtils.isPresent("com.mongodb.client.MongoClient", classLoader);
public static boolean isReactorPresent() {
return IS_REACTIVE_LIBARARY_AVAILABLE.test(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
}
public static boolean isSyncClientPresent(@Nullable ClassLoader classLoader) {
return IS_SYNC_CLIENT_PRESENT.test(classLoader);
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.aot;
import static org.springframework.data.mongodb.aot.MongoAotPredicates.*;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
@@ -29,8 +31,8 @@ import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertC
import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterSaveCallback;
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback;
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCallback;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} for repository types and entity callbacks.
@@ -41,28 +43,39 @@ import org.springframework.lang.Nullable;
*/
class MongoRuntimeHints implements RuntimeHintsRegistrar {
private static final boolean PROJECT_REACTOR_PRESENT = ReactiveWrappers
.isAvailable(ReactiveWrappers.ReactiveLibrary.PROJECT_REACTOR);
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerTypes(
Arrays.asList(TypeReference.of("org.springframework.data.mongodb.repository.support.SimpleMongoRepository"),
TypeReference.of(BeforeConvertCallback.class), TypeReference.of(BeforeSaveCallback.class),
Arrays.asList(TypeReference.of(BeforeConvertCallback.class), TypeReference.of(BeforeSaveCallback.class),
TypeReference.of(AfterConvertCallback.class), TypeReference.of(AfterSaveCallback.class)),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
if (PROJECT_REACTOR_PRESENT) {
registerTransactionProxyHints(hints, classLoader);
if (isReactorPresent()) {
hints.reflection()
.registerTypes(Arrays.asList(
TypeReference.of("org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository"),
TypeReference.of(ReactiveBeforeConvertCallback.class), TypeReference.of(ReactiveBeforeSaveCallback.class),
TypeReference.of(ReactiveAfterConvertCallback.class), TypeReference.of(ReactiveAfterSaveCallback.class)),
.registerTypes(Arrays.asList(TypeReference.of(ReactiveBeforeConvertCallback.class),
TypeReference.of(ReactiveBeforeSaveCallback.class), TypeReference.of(ReactiveAfterConvertCallback.class),
TypeReference.of(ReactiveAfterSaveCallback.class)),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
}
}
private static void registerTransactionProxyHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
if (MongoAotPredicates.isSyncClientPresent(classLoader) && ClassUtils.isPresent("org.springframework.aop.SpringProxy", classLoader)) {
hints.proxies().registerJdkProxy(TypeReference.of("com.mongodb.client.MongoDatabase"),
TypeReference.of("org.springframework.aop.SpringProxy"),
TypeReference.of("org.springframework.core.DecoratingProxy"));
hints.proxies().registerJdkProxy(TypeReference.of("com.mongodb.client.MongoCollection"),
TypeReference.of("org.springframework.aop.SpringProxy"),
TypeReference.of("org.springframework.core.DecoratingProxy"));
}
}
}

View File

@@ -450,7 +450,7 @@ public class Criteria implements CriteriaDefinition {
Assert.notNull(types, "Types must not be null");
criteria.put("$type", types.stream().map(Type::value).collect(Collectors.toList()));
criteria.put("$type", types.stream().map(Type::toBsonType).map(Type::value).collect(Collectors.toList()));
return this;
}

View File

@@ -295,7 +295,7 @@ public interface JsonSchemaObject {
Type OBJECT = jsonTypeOf("object");
Type ARRAY = jsonTypeOf("array");
Type NUMBER = jsonTypeOf("number");
Type BOOLEAN = jsonTypeOf("bool");
Type BOOLEAN = jsonTypeOf("boolean");
Type STRING = jsonTypeOf("string");
Type NULL = jsonTypeOf("null");

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import reactor.core.CoreSubscriber;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import org.reactivestreams.Subscriber;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.ReactiveWrappers.ReactiveLibrary;
import org.springframework.util.ClassUtils;
import com.mongodb.ContextProvider;
import com.mongodb.RequestContext;
import com.mongodb.client.SynchronousContextProvider;
import com.mongodb.reactivestreams.client.ReactiveContextProvider;
/**
* Factory to create a {@link ContextProvider} to propagate the request context across tasks. Requires either
* {@link SynchronousContextProvider} or {@link ReactiveContextProvider} to be present.
*
* @author Mark Paluch
* @since 3.0
*/
public class ContextProviderFactory {
private static final boolean SYNCHRONOUS_PRESENT = ClassUtils
.isPresent("com.mongodb.client.SynchronousContextProvider", ContextProviderFactory.class.getClassLoader());
private static final boolean REACTIVE_PRESENT = ClassUtils.isPresent(
"com.mongodb.reactivestreams.client.ReactiveContextProvider", ContextProviderFactory.class.getClassLoader())
&& ReactiveWrappers.isAvailable(ReactiveLibrary.PROJECT_REACTOR);
/**
* Create a {@link ContextProvider} given {@link ObservationRegistry}. The factory method attempts to create a
* {@link ContextProvider} that is capable to propagate request contexts across imperative or reactive usage,
* depending on their class path presence.
*
* @param observationRegistry must not be {@literal null}.
* @return
*/
public static ContextProvider create(ObservationRegistry observationRegistry) {
if (SYNCHRONOUS_PRESENT && REACTIVE_PRESENT) {
return new CompositeContextProvider(observationRegistry);
}
if (SYNCHRONOUS_PRESENT) {
return new DefaultSynchronousContextProvider(observationRegistry);
}
if (REACTIVE_PRESENT) {
return DefaultReactiveContextProvider.INSTANCE;
}
throw new IllegalStateException(
"Cannot create ContextProvider. Neither SynchronousContextProvider nor ReactiveContextProvider is on the class path.");
}
record DefaultSynchronousContextProvider(
ObservationRegistry observationRegistry) implements SynchronousContextProvider {
@Override
public RequestContext getContext() {
MapRequestContext requestContext = new MapRequestContext();
Observation currentObservation = observationRegistry.getCurrentObservation();
if (currentObservation != null) {
requestContext.put(ObservationThreadLocalAccessor.KEY, currentObservation);
}
return requestContext;
}
}
enum DefaultReactiveContextProvider implements ReactiveContextProvider {
INSTANCE;
@Override
public RequestContext getContext(Subscriber<?> subscriber) {
if (subscriber instanceof CoreSubscriber<?> cs) {
Map<Object, Object> map = cs.currentContext().stream()
.collect(Collectors.toConcurrentMap(Entry::getKey, Entry::getValue));
return new MapRequestContext(map);
}
return new MapRequestContext();
}
}
record CompositeContextProvider(DefaultSynchronousContextProvider synchronousContextProvider)
implements
SynchronousContextProvider,
ReactiveContextProvider {
CompositeContextProvider(ObservationRegistry observationRegistry) {
this(new DefaultSynchronousContextProvider(observationRegistry));
}
@Override
public RequestContext getContext() {
return synchronousContextProvider.getContext();
}
@Override
public RequestContext getContext(Subscriber<?> subscriber) {
return DefaultReactiveContextProvider.INSTANCE.getContext(subscriber);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,37 +15,84 @@
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import java.net.InetSocketAddress;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import org.springframework.util.ObjectUtils;
import com.mongodb.ConnectionString;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ConnectionId;
import com.mongodb.event.CommandStartedEvent;
import io.micrometer.common.KeyValues;
/**
* Default {@link MongoHandlerObservationConvention} implementation.
*
* @author Greg Turnquist
* @since 4.0.0
* @author Mark Paluch
* @since 4.0
*/
public class DefaultMongoHandlerObservationConvention implements MongoHandlerObservationConvention {
class DefaultMongoHandlerObservationConvention implements MongoHandlerObservationConvention {
@Override
public KeyValues getLowCardinalityKeyValues(MongoHandlerContext context) {
KeyValues keyValues = KeyValues.empty();
KeyValues keyValues = KeyValues.of(LowCardinalityCommandKeyNames.DB_SYSTEM.withValue("mongodb"),
LowCardinalityCommandKeyNames.MONGODB_COMMAND.withValue(context.getCommandName()));
if (context.getCollectionName() != null) {
ConnectionString connectionString = context.getConnectionString();
if (connectionString != null) {
keyValues = keyValues
.and(LowCardinalityCommandKeyNames.DB_CONNECTION_STRING.withValue(connectionString.getConnectionString()));
String user = connectionString.getUsername();
if (!ObjectUtils.isEmpty(user)) {
keyValues = keyValues.and(LowCardinalityCommandKeyNames.DB_USER.withValue(user));
}
}
if (!ObjectUtils.isEmpty(context.getDatabaseName())) {
keyValues = keyValues.and(LowCardinalityCommandKeyNames.DB_NAME.withValue(context.getDatabaseName()));
}
if (!ObjectUtils.isEmpty(context.getCollectionName())) {
keyValues = keyValues
.and(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue(context.getCollectionName()));
}
KeyValue connectionTag = connectionTag(context.getCommandStartedEvent());
if (connectionTag != null) {
keyValues = keyValues.and(connectionTag);
ConnectionDescription connectionDescription = context.getCommandStartedEvent().getConnectionDescription();
if (connectionDescription != null) {
ServerAddress serverAddress = connectionDescription.getServerAddress();
if (serverAddress != null) {
keyValues = keyValues.and(LowCardinalityCommandKeyNames.NET_TRANSPORT.withValue("IP.TCP"),
LowCardinalityCommandKeyNames.NET_PEER_NAME.withValue(serverAddress.getHost()),
LowCardinalityCommandKeyNames.NET_PEER_PORT.withValue("" + serverAddress.getPort()));
InetSocketAddress socketAddress = serverAddress.getSocketAddress();
if (socketAddress != null) {
keyValues = keyValues.and(
LowCardinalityCommandKeyNames.NET_SOCK_PEER_ADDR.withValue(socketAddress.getHostName()),
LowCardinalityCommandKeyNames.NET_SOCK_PEER_PORT.withValue("" + socketAddress.getPort()));
}
}
ConnectionId connectionId = connectionDescription.getConnectionId();
if (connectionId != null) {
keyValues = keyValues.and(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID
.withValue(connectionId.getServerId().getClusterId().getValue()));
}
}
return keyValues;
@@ -53,30 +100,20 @@ public class DefaultMongoHandlerObservationConvention implements MongoHandlerObs
@Override
public KeyValues getHighCardinalityKeyValues(MongoHandlerContext context) {
return KeyValues.of(
HighCardinalityCommandKeyNames.MONGODB_COMMAND.withValue(context.getCommandStartedEvent().getCommandName()));
return KeyValues.empty();
}
/**
* Extract connection details for a MongoDB connection into a {@link KeyValue}.
*
* @param event
* @return
*/
private static KeyValue connectionTag(CommandStartedEvent event) {
@Override
public String getContextualName(MongoHandlerContext context) {
ConnectionDescription connectionDescription = event.getConnectionDescription();
String collectionName = context.getCollectionName();
CommandStartedEvent commandStartedEvent = context.getCommandStartedEvent();
if (connectionDescription != null) {
ConnectionId connectionId = connectionDescription.getConnectionId();
if (connectionId != null) {
return LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID
.withValue(connectionId.getServerId().getClusterId().getValue());
}
if (ObjectUtils.isEmpty(collectionName)) {
return commandStartedEvent.getCommandName();
}
return null;
return collectionName + "." + commandStartedEvent.getCommandName();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
@@ -24,15 +22,23 @@ import java.util.stream.Stream;
import com.mongodb.RequestContext;
/**
* A {@link Map}-based {@link RequestContext}. (For test purposes only).
* A {@link Map}-based {@link RequestContext}.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class TestRequestContext implements RequestContext {
class MapRequestContext implements RequestContext {
private final Map<Object, Object> map = new HashMap<>();
private final Map<Object, Object> map;
public MapRequestContext() {
this(new HashMap<>());
}
public MapRequestContext(Map<Object, Object> context) {
this.map = context;
}
@Override
public <T> T get(Object key) {
@@ -68,11 +74,4 @@ class TestRequestContext implements RequestContext {
public Stream<Map.Entry<Object, Object>> stream() {
return map.entrySet().stream();
}
static TestRequestContext withObservation(Observation value) {
TestRequestContext testRequestContext = new TestRequestContext();
testRequestContext.put(Observation.class, value);
return testRequestContext;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -25,28 +23,37 @@ import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.springframework.lang.Nullable;
import com.mongodb.ConnectionString;
import com.mongodb.RequestContext;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.observation.Observation;
import io.micrometer.observation.transport.Kind;
import io.micrometer.observation.transport.SenderContext;
/**
* A {@link Observation.Context} that contains MongoDB events.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
* @author Mark Paluch
* @since 4.0
*/
public class MongoHandlerContext extends Observation.Context {
class MongoHandlerContext extends SenderContext<Object> {
/**
* @see https://docs.mongodb.com/manual/reference/command for the command reference
* @see <a href=
* "https://docs.mongodb.com/manual/reference/command">https://docs.mongodb.com/manual/reference/command</a> for
* the command reference
*/
private static final Set<String> COMMANDS_WITH_COLLECTION_NAME = new LinkedHashSet<>(
Arrays.asList("aggregate", "count", "distinct", "mapReduce", "geoSearch", "delete", "find", "findAndModify",
"insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop", "dropIndexes",
"killCursors", "listIndexes", "reIndex"));
private final @Nullable ConnectionString connectionString;
private final CommandStartedEvent commandStartedEvent;
private final RequestContext requestContext;
private final String collectionName;
@@ -54,8 +61,11 @@ public class MongoHandlerContext extends Observation.Context {
private CommandSucceededEvent commandSucceededEvent;
private CommandFailedEvent commandFailedEvent;
public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) {
public MongoHandlerContext(@Nullable ConnectionString connectionString, CommandStartedEvent commandStartedEvent,
RequestContext requestContext) {
super((carrier, key, value) -> {}, Kind.CLIENT);
this.connectionString = connectionString;
this.commandStartedEvent = commandStartedEvent;
this.requestContext = requestContext;
this.collectionName = getCollectionName(commandStartedEvent);
@@ -69,17 +79,21 @@ public class MongoHandlerContext extends Observation.Context {
return this.requestContext;
}
public String getDatabaseName() {
return commandStartedEvent.getDatabaseName();
}
public String getCollectionName() {
return this.collectionName;
}
public String getContextualName() {
public String getCommandName() {
return commandStartedEvent.getCommandName();
}
if (this.collectionName == null) {
return this.commandStartedEvent.getCommandName();
}
return this.commandStartedEvent.getCommandName() + " " + this.collectionName;
@Nullable
public ConnectionString getConnectionString() {
return connectionString;
}
public void setCommandSucceededEvent(CommandSucceededEvent commandSucceededEvent) {
@@ -112,7 +126,7 @@ public class MongoHandlerContext extends Observation.Context {
}
// Some other commands, like getMore, have a field like {"collection": collectionName}.
return getNonEmptyBsonString(command.get("collection"));
return command == null ? "" : getNonEmptyBsonString(command.get("collection"));
}
/**
@@ -121,7 +135,7 @@ public class MongoHandlerContext extends Observation.Context {
* @return trimmed string from {@code bsonValue} or null if the trimmed string was empty or the value wasn't a string
*/
@Nullable
private static String getNonEmptyBsonString(BsonValue bsonValue) {
private static String getNonEmptyBsonString(@Nullable BsonValue bsonValue) {
if (bsonValue == null || !bsonValue.isString()) {
return null;
@@ -131,4 +145,5 @@ public class MongoHandlerContext extends Observation.Context {
return stringValue.isEmpty() ? null : stringValue;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@ import io.micrometer.observation.ObservationConvention;
* {@link ObservationConvention} for {@link MongoHandlerContext}.
*
* @author Greg Turnquist
* @since 4.0.0
* @since 4
*/
public interface MongoHandlerObservationConvention extends ObservationConvention<MongoHandlerContext> {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ import io.micrometer.observation.docs.ObservationDocumentation;
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
* @since 4.0
*/
enum MongoObservation implements ObservationDocumentation {
@@ -44,13 +44,9 @@ enum MongoObservation implements ObservationDocumentation {
@Override
public KeyName[] getHighCardinalityKeyNames() {
return HighCardinalityCommandKeyNames.values();
return new KeyName[0];
}
@Override
public String getPrefix() {
return "spring.data.mongodb";
}
};
/**
@@ -58,13 +54,103 @@ enum MongoObservation implements ObservationDocumentation {
*/
enum LowCardinalityCommandKeyNames implements KeyName {
/**
* MongoDB database system.
*/
DB_SYSTEM {
@Override
public String asString() {
return "db.system";
}
},
/**
* MongoDB connection string.
*/
DB_CONNECTION_STRING {
@Override
public String asString() {
return "db.connection_string";
}
},
/**
* Network transport.
*/
NET_TRANSPORT {
@Override
public String asString() {
return "net.transport";
}
},
/**
* Name of the database host.
*/
NET_PEER_NAME {
@Override
public String asString() {
return "net.peer.name";
}
},
/**
* Logical remote port number.
*/
NET_PEER_PORT {
@Override
public String asString() {
return "net.peer.port";
}
},
/**
* Mongo peer address.
*/
NET_SOCK_PEER_ADDR {
@Override
public String asString() {
return "net.sock.peer.addr";
}
},
/**
* Mongo peer port.
*/
NET_SOCK_PEER_PORT {
@Override
public String asString() {
return "net.sock.peer.port";
}
},
/**
* MongoDB user.
*/
DB_USER {
@Override
public String asString() {
return "db.user";
}
},
/**
* MongoDB database name.
*/
DB_NAME {
@Override
public String asString() {
return "db.name";
}
},
/**
* MongoDB collection name.
*/
MONGODB_COLLECTION {
@Override
public String asString() {
return "spring.data.mongodb.collection";
return "db.mongodb.collection";
}
},
@@ -76,13 +162,7 @@ enum MongoObservation implements ObservationDocumentation {
public String asString() {
return "spring.data.mongodb.cluster_id";
}
}
}
/**
* Enums related to high cardinality key names for MongoDB commands.
*/
enum HighCardinalityCommandKeyNames implements KeyName {
},
/**
* MongoDB command value.
@@ -90,8 +170,9 @@ enum MongoObservation implements ObservationDocumentation {
MONGODB_COMMAND {
@Override
public String asString() {
return "spring.data.mongodb.command";
return "db.operation";
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2022 the original author or authors.
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,39 +15,66 @@
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.mongodb.ConnectionString;
import com.mongodb.RequestContext;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
/**
* Implement MongoDB's {@link CommandListener} using Micrometer's {@link Observation} API.
*
* @see https://github.com/openzipkin/brave/blob/release-5.13.0/instrumentation/mongodb/src/main/java/brave/mongodb/TraceMongoCommandListener.java
* @author OpenZipkin Brave Authors
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
* @since 4.0
*/
public final class MongoObservationCommandListener implements CommandListener {
public class MongoObservationCommandListener implements CommandListener {
private static final Log log = LogFactory.getLog(MongoObservationCommandListener.class);
private final ObservationRegistry observationRegistry;
private final @Nullable ConnectionString connectionString;
private MongoHandlerObservationConvention observationConvention;
private final MongoHandlerObservationConvention observationConvention = new DefaultMongoHandlerObservationConvention();
/**
* Create a new {@link MongoObservationCommandListener} to record {@link Observation}s.
*
* @param observationRegistry must not be {@literal null}
*/
public MongoObservationCommandListener(ObservationRegistry observationRegistry) {
Assert.notNull(observationRegistry, "ObservationRegistry must not be null");
this.observationRegistry = observationRegistry;
this.observationConvention = new DefaultMongoHandlerObservationConvention();
this.connectionString = null;
}
/**
* Create a new {@link MongoObservationCommandListener} to record {@link Observation}s. This constructor attaches the
* {@link ConnectionString} to every {@link Observation}.
*
* @param observationRegistry must not be {@literal null}
* @param connectionString must not be {@literal null}
*/
public MongoObservationCommandListener(ObservationRegistry observationRegistry, ConnectionString connectionString) {
Assert.notNull(observationRegistry, "ObservationRegistry must not be null");
Assert.notNull(connectionString, "ConnectionString must not be null");
this.observationRegistry = observationRegistry;
this.connectionString = connectionString;
}
@Override
@@ -75,26 +102,42 @@ public final class MongoObservationCommandListener implements CommandListener {
log.debug("Found the following observation passed from the mongo context [" + parent + "]");
}
if (parent == null) {
return;
MongoHandlerContext observationContext = new MongoHandlerContext(connectionString, event, requestContext);
observationContext.setRemoteServiceName("mongo");
Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION
.observation(this.observationRegistry, () -> observationContext) //
.observationConvention(this.observationConvention);
if (parent != null) {
observation.parentObservation(parent);
}
setupObservability(event, requestContext);
observation.start();
requestContext.put(ObservationThreadLocalAccessor.KEY, observation);
if (log.isDebugEnabled()) {
log.debug(
"Created a child observation [" + observation + "] for Mongo instrumentation and put it in Mongo context");
}
}
@Override
public void commandSucceeded(CommandSucceededEvent event) {
if (event.getRequestContext() == null) {
RequestContext requestContext = event.getRequestContext();
if (requestContext == null) {
return;
}
Observation observation = event.getRequestContext().getOrDefault(Observation.class, null);
Observation observation = requestContext.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
if (observation == null) {
return;
}
MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class);
MongoHandlerContext context = (MongoHandlerContext) observation.getContext();
context.setCommandSucceededEvent(event);
if (log.isDebugEnabled()) {
@@ -107,16 +150,18 @@ public final class MongoObservationCommandListener implements CommandListener {
@Override
public void commandFailed(CommandFailedEvent event) {
if (event.getRequestContext() == null) {
RequestContext requestContext = event.getRequestContext();
if (requestContext == null) {
return;
}
Observation observation = event.getRequestContext().getOrDefault(Observation.class, null);
Observation observation = requestContext.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
if (observation == null) {
return;
}
MongoHandlerContext context = event.getRequestContext().get(MongoHandlerContext.class);
MongoHandlerContext context = (MongoHandlerContext) observation.getContext();
context.setCommandFailedEvent(event);
if (log.isDebugEnabled()) {
@@ -133,41 +178,23 @@ public final class MongoObservationCommandListener implements CommandListener {
* @param context
* @return
*/
@Nullable
private static Observation observationFromContext(RequestContext context) {
Observation observation = context.getOrDefault(Observation.class, null);
Observation observation = context.getOrDefault(ObservationThreadLocalAccessor.KEY, null);
if (observation != null) {
if (log.isDebugEnabled()) {
log.debug("Found a observation in mongo context [" + observation + "]");
log.debug("Found a observation in Mongo context [" + observation + "]");
}
return observation;
}
if (log.isDebugEnabled()) {
log.debug("No observation was found - will not create any child spans");
log.debug("No observation was found - will not create any child observations");
}
return null;
}
private void setupObservability(CommandStartedEvent event, RequestContext requestContext) {
MongoHandlerContext observationContext = new MongoHandlerContext(event, requestContext);
Observation observation = MongoObservation.MONGODB_COMMAND_OBSERVATION
.observation(this.observationRegistry, () -> observationContext) //
.contextualName(observationContext.getContextualName()) //
.observationConvention(this.observationConvention) //
.start();
requestContext.put(Observation.class, observation);
requestContext.put(MongoHandlerContext.class, observationContext);
if (log.isDebugEnabled()) {
log.debug(
"Created a child observation [" + observation + "] for mongo instrumentation and put it in mongo context");
}
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.observation.Observation;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingObservationHandler;
import java.net.InetSocketAddress;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.mongodb.MongoSocketException;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.event.CommandStartedEvent;
/**
* A {@link TracingObservationHandler} that handles {@link MongoHandlerContext}. It configures a span specific to Mongo
* operations.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
public class MongoTracingObservationHandler implements TracingObservationHandler<MongoHandlerContext> {
private static final Log log = LogFactory.getLog(MongoTracingObservationHandler.class);
private final Tracer tracer;
private boolean setRemoteIpAndPortEnabled;
public MongoTracingObservationHandler(Tracer tracer) {
this.tracer = tracer;
}
@Override
public Tracer getTracer() {
return this.tracer;
}
@Override
public void onStart(MongoHandlerContext context) {
CommandStartedEvent event = context.getCommandStartedEvent();
Span.Builder builder = this.tracer.spanBuilder() //
.name(context.getContextualName()) //
.kind(Span.Kind.CLIENT) //
.remoteServiceName("mongodb-" + event.getDatabaseName());
if (this.setRemoteIpAndPortEnabled) {
ConnectionDescription connectionDescription = event.getConnectionDescription();
if (connectionDescription != null) {
try {
InetSocketAddress socketAddress = connectionDescription.getServerAddress().getSocketAddress();
builder.remoteIpAndPort(socketAddress.getAddress().getHostAddress(), socketAddress.getPort());
} catch (MongoSocketException e) {
if (log.isDebugEnabled()) {
log.debug("Ignored exception when setting remote ip and port", e);
}
}
}
}
getTracingContext(context).setSpan(builder.start());
}
@Override
public void onStop(MongoHandlerContext context) {
Span span = getRequiredSpan(context);
tagSpan(context, span);
context.getRequestContext().delete(Observation.class);
context.getRequestContext().delete(MongoHandlerContext.class);
span.end();
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof MongoHandlerContext;
}
/**
* Should remote ip and port be set on the span.
*
* @return {@code true} when the remote ip and port should be set
*/
public boolean isSetRemoteIpAndPortEnabled() {
return this.setRemoteIpAndPortEnabled;
}
public void setSetRemoteIpAndPortEnabled(boolean setRemoteIpAndPortEnabled) {
this.setRemoteIpAndPortEnabled = setRemoteIpAndPortEnabled;
}
}

View File

@@ -0,0 +1,5 @@
/**
* Infrastructure to provide driver observability using Micrometer.
*/
@org.springframework.lang.NonNullApi
package org.springframework.data.mongodb.observability;

View File

@@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.aot;
package org.springframework.data.mongodb.repository.aot;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.data.aot.TypeContributor;
import org.springframework.data.repository.aot.AotRepositoryContext;
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessor;
import org.springframework.data.mongodb.aot.LazyLoadingProxyAotProcessor;
import org.springframework.data.mongodb.aot.MongoAotPredicates;
import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
import org.springframework.data.util.TypeContributor;
/**
* @author Christoph Strobl

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.aot;
import static org.springframework.data.mongodb.aot.MongoAotPredicates.*;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.QuerydslUtils;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
* @since 4.0
*/
class RepositoryRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.reflection().registerTypes(
Arrays.asList(TypeReference.of("org.springframework.data.mongodb.repository.support.SimpleMongoRepository")),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
if (isReactorPresent()) {
hints.reflection().registerTypes(
Arrays.asList(
TypeReference.of("org.springframework.data.mongodb.repository.support.SimpleReactiveMongoRepository")),
builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_METHODS));
}
if (QuerydslUtils.QUERY_DSL_PRESENT) {
hints.reflection().registerType(
TypeReference.of("org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor"),
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)
.onReachableType(QuerydslPredicateExecutor.class));
}
}
}

View File

@@ -23,7 +23,7 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.data.config.ParsingUtils;
import org.springframework.data.mongodb.aot.AotMongoRepositoryPostProcessor;
import org.springframework.data.mongodb.repository.aot.AotMongoRepositoryPostProcessor;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;

View File

@@ -40,8 +40,8 @@ import org.springframework.data.mongodb.repository.Update;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

View File

@@ -25,9 +25,8 @@ import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.reactivestreams.Publisher;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.ReactiveWrappers;
/**
* Reactive {@link org.springframework.data.repository.query.ParametersParameterAccessor} implementation that subscribes

View File

@@ -34,7 +34,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;

View File

@@ -32,8 +32,8 @@ import org.springframework.data.mongodb.repository.query.MongoParameters.MongoPa
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.ReactiveWrappers;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ClassUtils;

View File

@@ -1,5 +1,6 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.data.mongodb.aot.MongoRuntimeHints
org.springframework.data.mongodb.aot.MongoRuntimeHints,\
org.springframework.data.mongodb.repository.aot.RepositoryRuntimeHints
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.data.mongodb.aot.MongoManagedTypesBeanRegistrationAotProcessor

View File

@@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.data.mongodb.test.util.Client;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
@@ -83,6 +85,28 @@ public class JsonSchemaQueryTests {
template.save(jellyBelly);
template.save(roseSpringHeart);
template.save(kazmardBoombub);
}
@Test // DATAMONGO-1835
public void createsWorkingSchema() {
try {
template.dropCollection("person_schema");
} catch (Exception e) {}
MongoJsonSchema schema = MongoJsonSchemaCreator.create(template.getConverter()).createSchemaFor(Person.class);
template.createCollection("person_schema", CollectionOptions.empty().schema(schema));
}
@Test // DATAMONGO-1835
public void queriesBooleanType() {
MongoJsonSchema schema = MongoJsonSchema.builder().properties(JsonSchemaProperty.bool("alive")).build();
assertThat(template.find(query(matchingDocumentStructure(schema)), Person.class)).hasSize(3);
assertThat(template.find(query(Criteria.where("alive").type(Type.BOOLEAN)), Person.class)).hasSize(3);
}
@Test // DATAMONGO-1835
@@ -201,6 +225,8 @@ public class JsonSchemaQueryTests {
Gender gender;
Address address;
Object value;
boolean alive;
}
@Data

View File

@@ -292,7 +292,7 @@ class MappingMongoJsonSchemaCreatorUnitTests {
" 're-named-property' : { 'type' : 'string' }," + //
" 'retypedProperty' : { 'bsonType' : 'javascript' }," + //
" 'primitiveInt' : { 'bsonType' : 'int' }," + //
" 'booleanProperty' : { 'type' : 'bool' }," + //
" 'booleanProperty' : { 'type' : 'boolean' }," + //
" 'longProperty' : { 'bsonType' : 'long' }," + //
" 'intProperty' : { 'bsonType' : 'int' }," + //
" 'dateProperty' : { 'bsonType' : 'date' }," + //

View File

@@ -23,11 +23,11 @@ import java.util.Collections;
import org.bson.Document;
import org.junit.Test;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.geo.GeoJsonLineString;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
/**
@@ -90,8 +90,7 @@ public class CriteriaUnitTests {
@Test // GH-3286
public void shouldBuildCorrectAndOperator() {
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true),
Criteria.where("y").is(42),
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true), Criteria.where("y").is(42),
Criteria.where("z").is("value"));
Criteria criteria = Criteria.where("foo").is("bar").andOperator(operatorCriteria);
@@ -103,8 +102,7 @@ public class CriteriaUnitTests {
@Test // GH-3286
public void shouldBuildCorrectOrOperator() {
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true),
Criteria.where("y").is(42),
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true), Criteria.where("y").is(42),
Criteria.where("z").is("value"));
Criteria criteria = Criteria.where("foo").is("bar").orOperator(operatorCriteria);
@@ -116,8 +114,7 @@ public class CriteriaUnitTests {
@Test // GH-3286
public void shouldBuildCorrectNorOperator() {
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true),
Criteria.where("y").is(42),
Collection<Criteria> operatorCriteria = Arrays.asList(Criteria.where("x").is(true), Criteria.where("y").is(42),
Criteria.where("z").is("value"));
Criteria criteria = Criteria.where("foo").is("bar").norOperator(operatorCriteria);
@@ -205,6 +202,14 @@ public class CriteriaUnitTests {
assertThat(document).isEqualTo(new Document().append("$not", new Document("$lt", "foo")));
}
@Test // GH-4220
public void usesCorrectBsonType() {
Document document = new Criteria("foo").type(Type.BOOLEAN).getCriteriaObject();
assertThat(document).containsEntry("foo.$type", Collections.singletonList("bool"));
}
@Test // DATAMONGO-1135
public void geoJsonTypesShouldBeWrappedInGeometry() {
@@ -302,8 +307,7 @@ public class CriteriaUnitTests {
Criteria numericBitmaskCriteria = new Criteria("field").bits().allClear(0b101);
assertThat(numericBitmaskCriteria.getCriteriaObject())
.isEqualTo("{ \"field\" : { \"$bitsAllClear\" : 5} }");
assertThat(numericBitmaskCriteria.getCriteriaObject()).isEqualTo("{ \"field\" : { \"$bitsAllClear\" : 5} }");
}
@Test // DATAMONGO-1808
@@ -320,8 +324,7 @@ public class CriteriaUnitTests {
Criteria numericBitmaskCriteria = new Criteria("field").bits().allSet(0b101);
assertThat(numericBitmaskCriteria.getCriteriaObject())
.isEqualTo("{ \"field\" : { \"$bitsAllSet\" : 5} }");
assertThat(numericBitmaskCriteria.getCriteriaObject()).isEqualTo("{ \"field\" : { \"$bitsAllSet\" : 5} }");
}
@Test // DATAMONGO-1808
@@ -338,8 +341,7 @@ public class CriteriaUnitTests {
Criteria numericBitmaskCriteria = new Criteria("field").bits().anyClear(0b101);
assertThat(numericBitmaskCriteria.getCriteriaObject())
.isEqualTo("{ \"field\" : { \"$bitsAnyClear\" : 5} }");
assertThat(numericBitmaskCriteria.getCriteriaObject()).isEqualTo("{ \"field\" : { \"$bitsAnyClear\" : 5} }");
}
@Test // DATAMONGO-1808
@@ -356,8 +358,7 @@ public class CriteriaUnitTests {
Criteria numericBitmaskCriteria = new Criteria("field").bits().anySet(0b101);
assertThat(numericBitmaskCriteria.getCriteriaObject())
.isEqualTo("{ \"field\" : { \"$bitsAnySet\" : 5} }");
assertThat(numericBitmaskCriteria.getCriteriaObject()).isEqualTo("{ \"field\" : { \"$bitsAnySet\" : 5} }");
}
@Test // DATAMONGO-1808
@@ -429,14 +430,10 @@ public class CriteriaUnitTests {
@Test // GH-3414
public void shouldEqualForNestedPattern() {
Criteria left = new Criteria("a").orOperator(
new Criteria("foo").regex("value", "i"),
new Criteria("bar").regex("value")
);
Criteria right = new Criteria("a").orOperator(
new Criteria("foo").regex("value", "i"),
new Criteria("bar").regex("value")
);
Criteria left = new Criteria("a").orOperator(new Criteria("foo").regex("value", "i"),
new Criteria("bar").regex("value"));
Criteria right = new Criteria("a").orOperator(new Criteria("foo").regex("value", "i"),
new Criteria("bar").regex("value"));
assertThat(left).isEqualTo(right);
}

View File

@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.schema;
import static org.springframework.data.domain.Range.from;
import static org.springframework.data.domain.Range.Bound.*;
import static org.springframework.data.mongodb.core.schema.JsonSchemaObject.*;
import static org.springframework.data.mongodb.core.schema.JsonSchemaObject.array;
import static org.springframework.data.mongodb.core.schema.JsonSchemaObject.of;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.util.Arrays;
@@ -131,7 +133,7 @@ class JsonSchemaObjectUnitTests {
.append("description", "Must be an object defining restrictions for name, active.").append("properties",
new Document("name", new Document("type", "string")
.append("description", "Must be a string with length unbounded-10].").append("maxLength", 10))
.append("active", new Document("type", "bool")));
.append("active", new Document("type", "boolean")));
assertThat(object().generatedDescription()
.properties(JsonSchemaProperty.string("name").maxLength(10).generatedDescription(),
@@ -264,7 +266,7 @@ class JsonSchemaObjectUnitTests {
void arrayObjectShouldRenderItemsCorrectly() {
assertThat(array().items(Arrays.asList(string(), bool())).toDocument()).isEqualTo(new Document("type", "array")
.append("items", Arrays.asList(new Document("type", "string"), new Document("type", "bool"))));
.append("items", Arrays.asList(new Document("type", "string"), new Document("type", "boolean"))));
}
@Test // DATAMONGO-2613
@@ -314,7 +316,7 @@ class JsonSchemaObjectUnitTests {
void booleanShouldRenderCorrectly() {
assertThat(bool().generatedDescription().toDocument())
.isEqualTo(new Document("type", "bool").append("description", "Must be a boolean"));
.isEqualTo(new Document("type", "boolean").append("description", "Must be a boolean"));
}
// -----------------

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import java.util.List;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.PersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.exporter.FinishedSpan;
import io.micrometer.tracing.test.SampleTestRunner;
/**
* Collection of tests that log metrics and tracing with an external tracing tool.
*
* @author Greg Turnquist
* @author Mark Paluch
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestConfig.class)
public class ImperativeIntegrationTests extends SampleTestRunner {
@Autowired PersonRepository repository;
ImperativeIntegrationTests() {
super(SampleRunnerConfig.builder().build());
}
@Override
protected MeterRegistry createMeterRegistry() {
return TestConfig.METER_REGISTRY;
}
@Override
protected ObservationRegistry createObservationRegistry() {
return TestConfig.OBSERVATION_REGISTRY;
}
@Override
public SampleTestRunnerConsumer yourCode() {
return (tracer, meterRegistry) -> {
repository.deleteAll();
repository.save(new Person("Dave", "Matthews", 42));
List<Person> people = repository.findByLastname("Matthews");
assertThat(people).hasSize(1);
assertThat(people.get(0)).extracting("firstname", "lastname").containsExactly("Dave", "Matthews");
repository.deleteAll();
System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString());
assertThat(tracer.getFinishedSpans()).hasSize(5).extracting(FinishedSpan::getName).contains("person.delete",
"person.update", "person.find");
for (FinishedSpan span : tracer.getFinishedSpans()) {
assertThat(span.getTags()).containsEntry("db.system", "mongodb").containsEntry("net.transport", "IP.TCP");
assertThat(span.getTags()).containsKeys("db.connection_string", "db.name", "db.operation",
"db.mongodb.collection", "net.peer.name", "net.peer.port", "net.sock.peer.addr", "net.sock.peer.port");
}
};
}
}

View File

@@ -1,170 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.test.simple.SimpleTracer;
import io.micrometer.tracing.test.simple.SpanAssert;
import io.micrometer.tracing.test.simple.TracerAssert;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* Series of test cases exercising {@link MongoObservationCommandListener} to ensure proper creation of {@link Span}s.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class MongoObservationCommandListenerForTracingTests {
SimpleTracer simpleTracer;
MongoTracingObservationHandler handler;
MeterRegistry meterRegistry;
ObservationRegistry observationRegistry;
MongoObservationCommandListener listener;
@BeforeEach
void setup() {
this.simpleTracer = new SimpleTracer();
this.handler = new MongoTracingObservationHandler(simpleTracer);
this.meterRegistry = new SimpleMeterRegistry();
this.observationRegistry = ObservationRegistry.create();
this.observationRegistry.observationConfig().observationHandler(new DefaultMeterObservationHandler(meterRegistry));
this.observationRegistry.observationConfig().observationHandler(handler);
this.listener = new MongoObservationCommandListener(observationRegistry);
}
@Test
void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() {
// given
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
commandStartedAndSucceeded(testRequestContext);
// then
assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet();
}
@Test
void successfullyCompletedCommandShouldCreateSpanWithAddressInfoWhenParentSampleInRequestContextAndHandlerAddressInfoEnabled() {
// given
handler.setSetRemoteIpAndPortEnabled(true);
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
commandStartedAndSucceeded(testRequestContext);
// then
assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet();
}
@Test
void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() {
// given
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandFailed( //
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
// then
assertThatMongoSpanIsClientWithTags().assertThatThrowable().isInstanceOf(IllegalAccessException.class);
}
/**
* Create a parent {@link Observation} then wrap it inside a {@link TestRequestContext}.
*/
@NotNull
private TestRequestContext createTestRequestContextWithParentObservationAndStartIt() {
Observation parent = Observation.start("name", observationRegistry);
return TestRequestContext.withObservation(parent);
}
/**
* Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and
* {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the
* {@link TestRequestContext} in order to inject some test data.
*
* @param testRequestContext
*/
private void commandStartedAndSucceeded(TestRequestContext testRequestContext) {
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
}
/**
* Create a base MongoDB-based {@link SpanAssert} using Micrometer Tracing's fluent API. Other test methods can apply
* additional assertions.
*
* @return
*/
private SpanAssert assertThatMongoSpanIsClientWithTags() {
return TracerAssert.assertThat(simpleTracer).onlySpan() //
.hasNameEqualTo("insert user") //
.hasKindEqualTo(Span.Kind.CLIENT) //
.hasRemoteServiceNameEqualTo("mongodb-database") //
.hasTag(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(), "insert") //
.hasTag(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.asString(), "user") //
.hasTagWithKey(LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.asString());
}
}

View File

@@ -15,7 +15,23 @@
*/
package org.springframework.data.mongodb.observability;
import static io.micrometer.core.tck.MeterRegistryAssert.assertThat;
import static io.micrometer.core.tck.MeterRegistryAssert.*;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.RequestContext;
import com.mongodb.ServerAddress;
import com.mongodb.client.SynchronousContextProvider;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.MeterRegistry;
@@ -24,27 +40,11 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.observability.MongoObservation.HighCardinalityCommandKeyNames;
import org.springframework.data.mongodb.observability.MongoObservation.LowCardinalityCommandKeyNames;
import com.mongodb.ServerAddress;
import com.mongodb.connection.ClusterId;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.connection.ServerId;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
/**
* Series of test cases exercising {@link MongoObservationCommandListener}.
*
* @author Marcin Grzejszczak
* @author Greg Turnquist
* @since 4.0.0
*/
class MongoObservationCommandListenerTests {
@@ -87,10 +87,10 @@ class MongoObservationCommandListenerTests {
void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() {
// when
listener.commandStarted(new CommandStartedEvent(new TestRequestContext(), 0, null, "some name", "", null));
listener.commandStarted(new CommandStartedEvent(new MapRequestContext(), 0, null, "some name", "", null));
// then
assertThat(meterRegistry).hasNoMetrics();
assertThat(meterRegistry).hasMeterWithName("spring.data.mongodb.command.active");
}
@Test
@@ -98,17 +98,17 @@ class MongoObservationCommandListenerTests {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
RequestContext traceRequestContext = getContext();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))),
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0));
// then
assertThatTimerRegisteredWithTags();
@@ -119,17 +119,17 @@ class MongoObservationCommandListenerTests {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
RequestContext traceRequestContext = getContext();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
new ServerAddress("localhost", 1234))), //
"database", "aggregate", //
new BsonDocument("aggregate", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "aggregate", null, 0));
listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "aggregate", null, 0));
// then
assertThatTimerRegisteredWithTags();
@@ -140,16 +140,18 @@ class MongoObservationCommandListenerTests {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
RequestContext traceRequestContext = getContext();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, null, "database", "insert",
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, null, "database", "insert",
new BsonDocument("collection", new BsonString("user"))));
listener.commandSucceeded(new CommandSucceededEvent(testRequestContext, 0, null, "insert", null, 0));
listener.commandSucceeded(new CommandSucceededEvent(traceRequestContext, 0, null, "insert", null, 0));
// then
assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(),
KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user")));
assertThat(meterRegistry).hasTimerWithNameAndTags(MongoObservation.MONGODB_COMMAND_OBSERVATION.getName(),
KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"),
LowCardinalityCommandKeyNames.DB_NAME.withValue("database"),
LowCardinalityCommandKeyNames.MONGODB_COMMAND.withValue("insert"),
LowCardinalityCommandKeyNames.DB_SYSTEM.withValue("mongodb")).and("error", "none"));
}
@Test
@@ -157,10 +159,10 @@ class MongoObservationCommandListenerTests {
// given
Observation parent = Observation.start("name", observationRegistry);
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
RequestContext traceRequestContext = getContext();
// when
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
new ConnectionDescription( //
new ServerId( //
new ClusterId("description"), //
@@ -168,19 +170,21 @@ class MongoObservationCommandListenerTests {
"database", "insert", //
new BsonDocument("collection", new BsonString("user"))));
listener.commandFailed( //
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
// then
assertThatTimerRegisteredWithTags();
}
private RequestContext getContext() {
return ((SynchronousContextProvider) ContextProviderFactory.create(observationRegistry)).getContext();
}
private void assertThatTimerRegisteredWithTags() {
assertThat(meterRegistry) //
.hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(),
KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user"))) //
.hasTimerWithNameAndTagKeys(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(),
LowCardinalityCommandKeyNames.MONGODB_CLUSTER_ID.asString());
.hasTimerWithNameAndTags(MongoObservation.MONGODB_COMMAND_OBSERVATION.getName(),
KeyValues.of(LowCardinalityCommandKeyNames.MONGODB_COLLECTION.withValue("user")));
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.ReactivePersonRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import io.micrometer.tracing.exporter.FinishedSpan;
import io.micrometer.tracing.test.SampleTestRunner;
import reactor.test.StepVerifier;
import reactor.util.context.Context;
/**
* Collection of tests that log metrics and tracing with an external tracing tool.
*
* @author Mark Paluch
*/
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TestConfig.class)
public class ReactiveIntegrationTests extends SampleTestRunner {
@Autowired ReactivePersonRepository repository;
ReactiveIntegrationTests() {
super(SampleRunnerConfig.builder().build());
}
@Override
protected MeterRegistry createMeterRegistry() {
return TestConfig.METER_REGISTRY;
}
@Override
protected ObservationRegistry createObservationRegistry() {
return TestConfig.OBSERVATION_REGISTRY;
}
@Override
public SampleTestRunnerConsumer yourCode() {
return (tracer, meterRegistry) -> {
Observation intermediate = Observation.start("intermediate", createObservationRegistry());
repository.deleteAll() //
.then(repository.save(new Person("Dave", "Matthews", 42))) //
.contextWrite(Context.of(ObservationThreadLocalAccessor.KEY, intermediate)) //
.as(StepVerifier::create).expectNextCount(1)//
.verifyComplete();
repository.findByLastname("Matthews") //
.contextWrite(Context.of(ObservationThreadLocalAccessor.KEY, intermediate)) //
.as(StepVerifier::create).assertNext(actual -> {
assertThat(actual).extracting("firstname", "lastname").containsExactly("Dave", "Matthews");
}).verifyComplete();
intermediate.stop();
System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString());
assertThat(tracer.getFinishedSpans()).hasSize(5).extracting(FinishedSpan::getName).contains("person.delete",
"person.update", "person.find");
};
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import java.util.Properties;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.PersonRepository;
import org.springframework.data.mongodb.repository.ReactivePersonRepository;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactoryBean;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClients;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.test.simple.SimpleTracer;
/**
* @author Mark Paluch
*/
@Configuration
class TestConfig {
static final MeterRegistry METER_REGISTRY = new SimpleMeterRegistry();
static final ObservationRegistry OBSERVATION_REGISTRY = ObservationRegistry.create();
static {
OBSERVATION_REGISTRY.observationConfig().observationHandler(new DefaultMeterObservationHandler(METER_REGISTRY));
}
@Bean
MongoDatabaseFactory mongoDatabaseFactory(MongoClientSettings settings) {
return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable");
}
@Bean
ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClientSettings settings) {
return new SimpleReactiveMongoDatabaseFactory(com.mongodb.reactivestreams.client.MongoClients.create(settings),
"observable");
}
@Bean
MongoClientSettings mongoClientSettings(ObservationRegistry observationRegistry) {
ConnectionString connectionString = new ConnectionString(
String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017));
MongoClientSettings settings = MongoClientSettings.builder() //
.addCommandListener(new MongoObservationCommandListener(observationRegistry, connectionString)) //
.contextProvider(ContextProviderFactory.create(observationRegistry)) //
.applyConnectionString(connectionString) //
.build();
return settings;
}
@Bean
MappingMongoConverter mongoConverter(MongoMappingContext mappingContext, MongoDatabaseFactory factory) {
return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
}
@Bean
MongoMappingContext mappingContext() {
return new MongoMappingContext();
}
@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter);
return template;
}
@Bean
ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory,
MongoConverter mongoConverter) {
ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDatabaseFactory, mongoConverter);
return template;
}
@Bean
public PropertiesFactoryBean namedQueriesProperties() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("META-INF/mongo-named-queries.properties"));
return bean;
}
@Bean
MongoRepositoryFactoryBean<PersonRepository, Person, String> personRepositoryFactoryBean(MongoOperations operations,
Properties namedQueriesProperties) {
MongoRepositoryFactoryBean<PersonRepository, Person, String> factoryBean = new MongoRepositoryFactoryBean<>(
PersonRepository.class);
factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties));
factoryBean.setMongoOperations(operations);
factoryBean.setCreateIndexesForQueryMethods(true);
return factoryBean;
}
@Bean
ReactiveMongoRepositoryFactoryBean<ReactivePersonRepository, Person, String> reactivePersonRepositoryFactoryBean(
ReactiveMongoOperations operations, Properties namedQueriesProperties) {
ReactiveMongoRepositoryFactoryBean<ReactivePersonRepository, Person, String> factoryBean = new ReactiveMongoRepositoryFactoryBean<>(
ReactivePersonRepository.class);
factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties));
factoryBean.setReactiveMongoOperations(operations);
factoryBean.setCreateIndexesForQueryMethods(true);
return factoryBean;
}
@Bean
SampleEvaluationContextExtension contextExtension() {
return new SampleEvaluationContextExtension();
}
@Bean
ObservationRegistry registry() {
return OBSERVATION_REGISTRY;
}
@Bean
Tracer tracer() {
return new SimpleTracer();
}
}

View File

@@ -1,207 +0,0 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.observability;
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.test.SampleTestRunner;
import io.micrometer.tracing.test.reporter.BuildingBlocks;
import java.io.IOException;
import java.util.Deque;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.PersonRepository;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.RequestContext;
import com.mongodb.WriteConcern;
import com.mongodb.client.MongoClients;
import com.mongodb.client.SynchronousContextProvider;
/**
* Collection of tests that log metrics and tracing with an external tracing tool. Since this external tool must be up
* and running after the test is completed, this test is ONLY run manually. Needed:
* {@code docker run -p 9411:9411 openzipkin/zipkin} and {@code docker run -p 27017:27017 mongo:latest} (either from
* Docker Desktop or within separate shells).
*
* @author Greg Turnquist
* @since 4.0.0
*/
@Disabled("Run this manually to visually test spans in Zipkin")
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class ZipkinIntegrationTests extends SampleTestRunner {
private static final MeterRegistry METER_REGISTRY = new SimpleMeterRegistry();
private static final ObservationRegistry OBSERVATION_REGISTRY = ObservationRegistry.create();
static {
OBSERVATION_REGISTRY.observationConfig().observationHandler(new DefaultMeterObservationHandler(METER_REGISTRY));
}
@Autowired PersonRepository repository;
ZipkinIntegrationTests() {
super(SampleRunnerConfig.builder().build());
}
@Override
protected MeterRegistry createMeterRegistry() {
return METER_REGISTRY;
}
@Override
protected ObservationRegistry createObservationRegistry() {
return OBSERVATION_REGISTRY;
}
@Override
public BiConsumer<BuildingBlocks, Deque<ObservationHandler<? extends Observation.Context>>> customizeObservationHandlers() {
return (buildingBlocks, observationHandlers) -> observationHandlers
.addLast(new MongoTracingObservationHandler(buildingBlocks.getTracer()));
}
@Override
public TracingSetup[] getTracingSetup() {
return new TracingSetup[] { TracingSetup.ZIPKIN_BRAVE };
}
@Override
public SampleTestRunnerConsumer yourCode() {
return (tracer, meterRegistry) -> {
repository.deleteAll();
repository.save(new Person("Dave", "Matthews", 42));
List<Person> people = repository.findByLastname("Matthews");
assertThat(people).hasSize(1);
assertThat(people.get(0)).extracting("firstname", "lastname").containsExactly("Dave", "Matthews");
repository.deleteAll();
System.out.println(((SimpleMeterRegistry) meterRegistry).getMetersAsString());
};
}
@Configuration
@EnableMongoRepositories
static class TestConfig {
@Bean
MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) {
return new MongoObservationCommandListener(registry);
}
@Bean
MongoDatabaseFactory mongoDatabaseFactory(MongoObservationCommandListener commandListener,
ObservationRegistry registry) {
ConnectionString connectionString = new ConnectionString(
String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017));
RequestContext requestContext = TestRequestContext.withObservation(Observation.start("name", registry));
SynchronousContextProvider contextProvider = () -> requestContext;
MongoClientSettings settings = MongoClientSettings.builder() //
.addCommandListener(commandListener) //
.contextProvider(contextProvider) //
.applyConnectionString(connectionString) //
.build();
return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable");
}
@Bean
MappingMongoConverter mongoConverter(MongoDatabaseFactory factory) {
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.afterPropertiesSet();
return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
}
@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter);
template.setWriteConcern(WriteConcern.JOURNALED);
return template;
}
@Bean
public PropertiesFactoryBean namedQueriesProperties() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("META-INF/mongo-named-queries.properties"));
return bean;
}
@Bean
MongoRepositoryFactoryBean<PersonRepository, Person, String> repositoryFactoryBean(MongoOperations operations,
PropertiesFactoryBean namedQueriesProperties) throws IOException {
MongoRepositoryFactoryBean<PersonRepository, Person, String> factoryBean = new MongoRepositoryFactoryBean<>(
PersonRepository.class);
factoryBean.setMongoOperations(operations);
factoryBean.setNamedQueries(new PropertiesBasedNamedQueries(namedQueriesProperties.getObject()));
factoryBean.setCreateIndexesForQueryMethods(true);
return factoryBean;
}
@Bean
SampleEvaluationContextExtension contextExtension() {
return new SampleEvaluationContextExtension();
}
@Bean
ObservationRegistry registry() {
return OBSERVATION_REGISTRY;
}
}
}

View File

@@ -23,6 +23,7 @@ include::{spring-data-commons-docs}/repositories.adoc[leveloffset=+1]
include::reference/introduction.adoc[leveloffset=+1]
include::reference/mongodb.adoc[leveloffset=+1]
include::reference/observability.adoc[leveloffset=+1]
include::reference/client-session-transactions.adoc[leveloffset=+1]
include::reference/reactive-mongodb.adoc[leveloffset=+1]
include::reference/mongo-repositories.adoc[leveloffset=+1]
@@ -42,4 +43,3 @@ include::{spring-data-commons-docs}/repository-namespace-reference.adoc[leveloff
include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[leveloffset=+1]
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[leveloffset=+1]
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[leveloffset=+1]
include::reference/observability.adoc[leveloffset=+1]

View File

@@ -1,10 +1,49 @@
:root-target: ../../../../target/
[[observability]]
== Observability metadata
[[mongodb.observability]]
== Observability
Spring Data MongoDB currently has the most up-to-date code to support Observability in your MongoDB application.
These changes, however, haven't been picked up by Spring Boot (yet).
Until those changes are applied, if you wish to use Spring Data MongoDB's flavor of Observability, you must carry out the following steps.
. First of all, you must opt into Spring Data MongoDB's configuration settings by customizing `MongoClientSettings` through either your `@SpringBootApplication` class or one of your configuration classes.
+
.Registering MongoDB Micrometer customizer setup
====
[source,java]
----
@Bean
MongoClientSettingsBuilderCustomizer mongoMetricsSynchronousContextProvider(ObservationRegistry registry) {
return (clientSettingsBuilder) -> {
clientSettingsBuilder.contextProvider(ContextProviderFactory.create(registry))
.addCommandListener(new MongoObservationCommandListener(registry));
};
}
----
====
+
. Your project must include *Spring Boot Actuator*.
. Disable Spring Boot's autoconfigured MongoDB command listener and enable tracing manually by adding the following properties to your `application.properties`
+
.Custom settings to apply
====
[source]
----
# Disable Spring Boot's autoconfigured tracing
management.metrics.mongo.command.enabled=false
# Enable it manually
management.tracing.enabled=true
----
Be sure to add any other relevant settings needed to configure the tracer you are using based upon Micrometer's reference documentation.
====
This should do it! You are now running with Spring Data MongoDB's usage of Spring Observability's `Observation` API.
include::{root-target}_conventions.adoc[]
include::{root-target}_metrics.adoc[]
include::{root-target}_spans.adoc[]
See also https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/database/#mongodb[OpenTelemetry Semantic Conventions] for further reference.

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 4.0 RC1 (2022.0.0)
Spring Data MongoDB 4.0 RC2 (2022.0.0)
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -38,5 +38,6 @@ conditions of the subcomponent's license, as noted in the LICENSE file.