Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c04c3d66a3 | ||
|
|
b204c1b33e | ||
|
|
dfa029b341 | ||
|
|
04a8c47cda | ||
|
|
88ea57f2be | ||
|
|
521bbd2535 | ||
|
|
0474632640 | ||
|
|
8aab5e5a01 | ||
|
|
3db5fc728e | ||
|
|
b027f15a4c | ||
|
|
fd0a554d59 | ||
|
|
d4daa305a8 | ||
|
|
2d63d6006d | ||
|
|
5007e68cc1 | ||
|
|
3ea4e0f9dd | ||
|
|
e9ac77c058 | ||
|
|
daef8b6e8e | ||
|
|
f671a9bd43 | ||
|
|
57b52862c8 |
@@ -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 && \
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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 && \
|
||||
|
||||
@@ -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
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Infrastructure to provide driver observability using Micrometer.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.mongodb.observability;
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }," + //
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
// -----------------
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user