Improve configuration support for Observability integration.
Closes: #4216
This commit is contained in:
committed by
Mark Paluch
parent
daef8b6e8e
commit
e9ac77c058
@@ -0,0 +1,16 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation to active Spring Data MongoDB's usage of Micrometer's Observation API.
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Import(MongoMetricsConfiguration.class)
|
||||||
|
public @interface EnableMongoObservability {
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import io.micrometer.tracing.Tracer;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to configure needed beans for MongoDB + Micrometer.
|
||||||
|
*/
|
||||||
|
public class MongoMetricsConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MongoObservationCommandListener mongoObservationCommandListener(ObservationRegistry registry) {
|
||||||
|
return new MongoObservationCommandListener(registry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MongoTracingObservationHandler mongoTracingObservationHandler(Tracer tracer) {
|
||||||
|
return new MongoTracingObservationHandler(tracer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import io.micrometer.observation.Observation;
|
||||||
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import io.micrometer.tracing.Tracer;
|
||||||
|
|
||||||
|
import com.mongodb.client.SynchronousContextProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions to ease registration of Spring Data MongoDB's observability.
|
||||||
|
*/
|
||||||
|
public class MongoMetricsConfigurationHelper {
|
||||||
|
|
||||||
|
public static SynchronousContextProvider synchronousContextProvider(Tracer tracer, ObservationRegistry registry) {
|
||||||
|
return () -> new SynchronousTraceRequestContext(tracer).withObservation(Observation.start("name", registry));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addObservationHandler(ObservationRegistry registry, Tracer tracer) {
|
||||||
|
registry.observationConfig().observationHandler(new MongoTracingObservationHandler(tracer));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import io.micrometer.observation.Observation;
|
||||||
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import reactor.core.CoreSubscriber;
|
||||||
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
|
import com.mongodb.reactivestreams.client.ReactiveContextProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions to ease registration of Spring Data MongoDB's observability.
|
||||||
|
*/
|
||||||
|
public class MongoMetricsReactiveConfigurationHelper {
|
||||||
|
|
||||||
|
public static ReactiveContextProvider reactiveContextProvider(ObservationRegistry registry) {
|
||||||
|
return subscriber -> {
|
||||||
|
if (subscriber instanceof CoreSubscriber<?> coreSubscriber) {
|
||||||
|
return new ReactiveTraceRequestContext(coreSubscriber.currentContext())
|
||||||
|
.withObservation(Observation.start("name", registry));
|
||||||
|
}
|
||||||
|
return new ReactiveTraceRequestContext(Context.empty()).withObservation(Observation.start("name", registry));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
package org.springframework.data.mongodb.observability;
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
import io.micrometer.observation.Observation;
|
import io.micrometer.observation.Observation;
|
||||||
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
import io.micrometer.tracing.Span;
|
import io.micrometer.tracing.Span;
|
||||||
import io.micrometer.tracing.Tracer;
|
import io.micrometer.tracing.Tracer;
|
||||||
import io.micrometer.tracing.handler.TracingObservationHandler;
|
import io.micrometer.tracing.handler.TracingObservationHandler;
|
||||||
@@ -49,6 +50,10 @@ public class MongoTracingObservationHandler implements TracingObservationHandler
|
|||||||
this.tracer = tracer;
|
this.tracer = tracer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void register(ObservationRegistry observationRegistry) {
|
||||||
|
observationRegistry.observationConfig().observationHandler(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Tracer getTracer() {
|
public Tracer getTracer() {
|
||||||
return this.tracer;
|
return this.tracer;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import io.micrometer.observation.Observation;
|
||||||
|
import reactor.util.context.ContextView;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
class ReactiveTraceRequestContext extends TraceRequestContext {
|
||||||
|
|
||||||
|
ReactiveTraceRequestContext withObservation(Observation value) {
|
||||||
|
|
||||||
|
put(Observation.class, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactiveTraceRequestContext(ContextView context) {
|
||||||
|
super(context.stream().collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
|
import io.micrometer.observation.Observation;
|
||||||
|
import io.micrometer.tracing.Span;
|
||||||
|
import io.micrometer.tracing.TraceContext;
|
||||||
|
import io.micrometer.tracing.Tracer;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
class SynchronousTraceRequestContext extends TraceRequestContext {
|
||||||
|
|
||||||
|
SynchronousTraceRequestContext(Tracer tracer) {
|
||||||
|
super(context(tracer));
|
||||||
|
}
|
||||||
|
|
||||||
|
SynchronousTraceRequestContext withObservation(Observation value) {
|
||||||
|
|
||||||
|
put(Observation.class, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Object, Object> context(Tracer tracer) {
|
||||||
|
|
||||||
|
Map<Object, Object> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
Span currentSpan = tracer.currentSpan();
|
||||||
|
|
||||||
|
if (currentSpan == null) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.put(Span.class, currentSpan);
|
||||||
|
map.put(TraceContext.class, currentSpan.context());
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import com.mongodb.RequestContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link Map}-based {@link RequestContext}.
|
||||||
|
*
|
||||||
|
* @author Marcin Grzejszczak
|
||||||
|
* @author Greg Turnquist
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
class TraceRequestContext implements RequestContext {
|
||||||
|
|
||||||
|
private final Map<Object, Object> map;
|
||||||
|
|
||||||
|
public TraceRequestContext() {
|
||||||
|
this(new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TraceRequestContext(Map<Object, Object> context) {
|
||||||
|
this.map = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(Object key) {
|
||||||
|
return (T) map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasKey(Object key) {
|
||||||
|
return map.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(Object key, Object value) {
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(Object key) {
|
||||||
|
map.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<Map.Entry<Object, Object>> stream() {
|
||||||
|
return map.entrySet().stream();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,10 +77,10 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() {
|
void successfullyCompletedCommandShouldCreateSpanWhenParentSampleInRequestContext() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
commandStartedAndSucceeded(testRequestContext);
|
commandStartedAndSucceeded(traceRequestContext);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet();
|
assertThatMongoSpanIsClientWithTags().hasIpThatIsBlank().hasPortThatIsNotSet();
|
||||||
@@ -91,10 +91,10 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
handler.setSetRemoteIpAndPortEnabled(true);
|
handler.setSetRemoteIpAndPortEnabled(true);
|
||||||
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
commandStartedAndSucceeded(testRequestContext);
|
commandStartedAndSucceeded(traceRequestContext);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet();
|
assertThatMongoSpanIsClientWithTags().hasIpThatIsNotBlank().hasPortThatIsSet();
|
||||||
@@ -104,10 +104,10 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() {
|
void commandWithErrorShouldCreateTimerWhenParentSampleInRequestContext() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
TestRequestContext testRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
TraceRequestContext traceRequestContext = createTestRequestContextWithParentObservationAndStartIt();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
|
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
|
||||||
new ConnectionDescription( //
|
new ConnectionDescription( //
|
||||||
new ServerId( //
|
new ServerId( //
|
||||||
new ClusterId("description"), //
|
new ClusterId("description"), //
|
||||||
@@ -115,17 +115,17 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
"database", "insert", //
|
"database", "insert", //
|
||||||
new BsonDocument("collection", new BsonString("user"))));
|
new BsonDocument("collection", new BsonString("user"))));
|
||||||
listener.commandFailed( //
|
listener.commandFailed( //
|
||||||
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
|
new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThatMongoSpanIsClientWithTags().assertThatThrowable().isInstanceOf(IllegalAccessException.class);
|
assertThatMongoSpanIsClientWithTags().assertThatThrowable().isInstanceOf(IllegalAccessException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a parent {@link Observation} then wrap it inside a {@link TestRequestContext}.
|
* Create a parent {@link Observation} then wrap it inside a {@link TraceRequestContext}.
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
private TestRequestContext createTestRequestContextWithParentObservationAndStartIt() {
|
private TraceRequestContext createTestRequestContextWithParentObservationAndStartIt() {
|
||||||
|
|
||||||
Observation parent = Observation.start("name", observationRegistry);
|
Observation parent = Observation.start("name", observationRegistry);
|
||||||
return TestRequestContext.withObservation(parent);
|
return TestRequestContext.withObservation(parent);
|
||||||
@@ -134,13 +134,13 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
/**
|
/**
|
||||||
* Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and
|
* Execute MongoDB's {@link com.mongodb.event.CommandListener#commandStarted(CommandStartedEvent)} and
|
||||||
* {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the
|
* {@link com.mongodb.event.CommandListener#commandSucceeded(CommandSucceededEvent)} operations against the
|
||||||
* {@link TestRequestContext} in order to inject some test data.
|
* {@link TraceRequestContext} in order to inject some test data.
|
||||||
*
|
*
|
||||||
* @param testRequestContext
|
* @param traceRequestContext
|
||||||
*/
|
*/
|
||||||
private void commandStartedAndSucceeded(TestRequestContext testRequestContext) {
|
private void commandStartedAndSucceeded(TraceRequestContext traceRequestContext) {
|
||||||
|
|
||||||
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
|
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
|
||||||
new ConnectionDescription( //
|
new ConnectionDescription( //
|
||||||
new ServerId( //
|
new ServerId( //
|
||||||
new ClusterId("description"), //
|
new ClusterId("description"), //
|
||||||
@@ -148,7 +148,7 @@ class MongoObservationCommandListenerForTracingTests {
|
|||||||
"database", "insert", //
|
"database", "insert", //
|
||||||
new BsonDocument("collection", new BsonString("user"))));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class MongoObservationCommandListenerTests {
|
|||||||
void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() {
|
void commandStartedShouldNotInstrumentWhenNoParentSampleInRequestContext() {
|
||||||
|
|
||||||
// when
|
// when
|
||||||
listener.commandStarted(new CommandStartedEvent(new TestRequestContext(), 0, null, "some name", "", null));
|
listener.commandStarted(new CommandStartedEvent(new TraceRequestContext(), 0, null, "some name", "", null));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(meterRegistry).hasNoMetrics();
|
assertThat(meterRegistry).hasNoMetrics();
|
||||||
@@ -98,17 +98,17 @@ class MongoObservationCommandListenerTests {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
Observation parent = Observation.start("name", observationRegistry);
|
Observation parent = Observation.start("name", observationRegistry);
|
||||||
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
|
TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
|
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
|
||||||
new ConnectionDescription( //
|
new ConnectionDescription( //
|
||||||
new ServerId( //
|
new ServerId( //
|
||||||
new ClusterId("description"), //
|
new ClusterId("description"), //
|
||||||
new ServerAddress("localhost", 1234))),
|
new ServerAddress("localhost", 1234))),
|
||||||
"database", "insert", //
|
"database", "insert", //
|
||||||
new BsonDocument("collection", new BsonString("user"))));
|
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
|
// then
|
||||||
assertThatTimerRegisteredWithTags();
|
assertThatTimerRegisteredWithTags();
|
||||||
@@ -119,17 +119,17 @@ class MongoObservationCommandListenerTests {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
Observation parent = Observation.start("name", observationRegistry);
|
Observation parent = Observation.start("name", observationRegistry);
|
||||||
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
|
TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
|
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
|
||||||
new ConnectionDescription( //
|
new ConnectionDescription( //
|
||||||
new ServerId( //
|
new ServerId( //
|
||||||
new ClusterId("description"), //
|
new ClusterId("description"), //
|
||||||
new ServerAddress("localhost", 1234))), //
|
new ServerAddress("localhost", 1234))), //
|
||||||
"database", "aggregate", //
|
"database", "aggregate", //
|
||||||
new BsonDocument("aggregate", new BsonString("user"))));
|
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
|
// then
|
||||||
assertThatTimerRegisteredWithTags();
|
assertThatTimerRegisteredWithTags();
|
||||||
@@ -140,12 +140,12 @@ class MongoObservationCommandListenerTests {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
Observation parent = Observation.start("name", observationRegistry);
|
Observation parent = Observation.start("name", observationRegistry);
|
||||||
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
|
TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent);
|
||||||
|
|
||||||
// when
|
// 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"))));
|
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
|
// then
|
||||||
assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(),
|
assertThat(meterRegistry).hasTimerWithNameAndTags(HighCardinalityCommandKeyNames.MONGODB_COMMAND.asString(),
|
||||||
@@ -157,10 +157,10 @@ class MongoObservationCommandListenerTests {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
Observation parent = Observation.start("name", observationRegistry);
|
Observation parent = Observation.start("name", observationRegistry);
|
||||||
TestRequestContext testRequestContext = TestRequestContext.withObservation(parent);
|
TraceRequestContext traceRequestContext = TestRequestContext.withObservation(parent);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
listener.commandStarted(new CommandStartedEvent(testRequestContext, 0, //
|
listener.commandStarted(new CommandStartedEvent(traceRequestContext, 0, //
|
||||||
new ConnectionDescription( //
|
new ConnectionDescription( //
|
||||||
new ServerId( //
|
new ServerId( //
|
||||||
new ClusterId("description"), //
|
new ClusterId("description"), //
|
||||||
@@ -168,7 +168,7 @@ class MongoObservationCommandListenerTests {
|
|||||||
"database", "insert", //
|
"database", "insert", //
|
||||||
new BsonDocument("collection", new BsonString("user"))));
|
new BsonDocument("collection", new BsonString("user"))));
|
||||||
listener.commandFailed( //
|
listener.commandFailed( //
|
||||||
new CommandFailedEvent(testRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
|
new CommandFailedEvent(traceRequestContext, 0, null, "insert", 0, new IllegalAccessException()));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThatTimerRegisteredWithTags();
|
assertThatTimerRegisteredWithTags();
|
||||||
|
|||||||
@@ -1,78 +1,26 @@
|
|||||||
/*
|
|
||||||
* 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;
|
package org.springframework.data.mongodb.observability;
|
||||||
|
|
||||||
import io.micrometer.observation.Observation;
|
import io.micrometer.observation.Observation;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import com.mongodb.RequestContext;
|
class TestRequestContext extends TraceRequestContext {
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link Map}-based {@link RequestContext}. (For test purposes only).
|
|
||||||
*
|
|
||||||
* @author Marcin Grzejszczak
|
|
||||||
* @author Greg Turnquist
|
|
||||||
* @since 4.0.0
|
|
||||||
*/
|
|
||||||
class TestRequestContext implements RequestContext {
|
|
||||||
|
|
||||||
private final Map<Object, Object> map = new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T get(Object key) {
|
|
||||||
return (T) map.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasKey(Object key) {
|
|
||||||
return map.containsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
return map.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(Object key, Object value) {
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete(Object key) {
|
|
||||||
map.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return map.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<Map.Entry<Object, Object>> stream() {
|
|
||||||
return map.entrySet().stream();
|
|
||||||
}
|
|
||||||
|
|
||||||
static TestRequestContext withObservation(Observation value) {
|
static TestRequestContext withObservation(Observation value) {
|
||||||
|
return new TestRequestContext(value);
|
||||||
|
}
|
||||||
|
|
||||||
TestRequestContext testRequestContext = new TestRequestContext();
|
private TestRequestContext(Observation value) {
|
||||||
testRequestContext.put(Observation.class, value);
|
super(context(value));
|
||||||
return testRequestContext;
|
}
|
||||||
|
|
||||||
|
private static Map<Object, Object> context(Observation value) {
|
||||||
|
|
||||||
|
Map<Object, Object> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
map.put(Observation.class, value);
|
||||||
|
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,16 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
|||||||
import io.micrometer.observation.Observation;
|
import io.micrometer.observation.Observation;
|
||||||
import io.micrometer.observation.ObservationHandler;
|
import io.micrometer.observation.ObservationHandler;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
|
import io.micrometer.tracing.Tracer;
|
||||||
import io.micrometer.tracing.test.SampleTestRunner;
|
import io.micrometer.tracing.test.SampleTestRunner;
|
||||||
import io.micrometer.tracing.test.reporter.BuildingBlocks;
|
import io.micrometer.tracing.test.reporter.BuildingBlocks;
|
||||||
|
import io.micrometer.tracing.test.simple.SimpleTracer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||||
@@ -56,8 +57,8 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
import com.mongodb.ConnectionString;
|
import com.mongodb.ConnectionString;
|
||||||
|
import com.mongodb.ContextProvider;
|
||||||
import com.mongodb.MongoClientSettings;
|
import com.mongodb.MongoClientSettings;
|
||||||
import com.mongodb.RequestContext;
|
|
||||||
import com.mongodb.WriteConcern;
|
import com.mongodb.WriteConcern;
|
||||||
import com.mongodb.client.MongoClients;
|
import com.mongodb.client.MongoClients;
|
||||||
import com.mongodb.client.SynchronousContextProvider;
|
import com.mongodb.client.SynchronousContextProvider;
|
||||||
@@ -71,7 +72,6 @@ import com.mongodb.client.SynchronousContextProvider;
|
|||||||
* @author Greg Turnquist
|
* @author Greg Turnquist
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
@Disabled("Run this manually to visually test spans in Zipkin")
|
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration
|
@ContextConfiguration
|
||||||
public class ZipkinIntegrationTests extends SampleTestRunner {
|
public class ZipkinIntegrationTests extends SampleTestRunner {
|
||||||
@@ -139,33 +139,41 @@ public class ZipkinIntegrationTests extends SampleTestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MongoDatabaseFactory mongoDatabaseFactory(MongoObservationCommandListener commandListener,
|
MongoDatabaseFactory mongoDatabaseFactory(MongoClientSettings settings) {
|
||||||
ObservationRegistry registry) {
|
return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MongoClientSettings mongoClientSettings(MongoObservationCommandListener commandListener,
|
||||||
|
ContextProvider contextProvider) {
|
||||||
|
|
||||||
ConnectionString connectionString = new ConnectionString(
|
ConnectionString connectionString = new ConnectionString(
|
||||||
String.format("mongodb://%s:%s/?w=majority&uuidrepresentation=javaLegacy", "127.0.0.1", 27017));
|
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() //
|
MongoClientSettings settings = MongoClientSettings.builder() //
|
||||||
.addCommandListener(commandListener) //
|
.addCommandListener(commandListener) //
|
||||||
.contextProvider(contextProvider) //
|
.contextProvider(contextProvider) //
|
||||||
.applyConnectionString(connectionString) //
|
.applyConnectionString(connectionString) //
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable");
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MappingMongoConverter mongoConverter(MongoDatabaseFactory factory) {
|
SynchronousContextProvider contextProvider(ObservationRegistry registry) {
|
||||||
|
return () -> TestRequestContext.withObservation(Observation.start("name", registry));
|
||||||
MongoMappingContext mappingContext = new MongoMappingContext();
|
}
|
||||||
mappingContext.afterPropertiesSet();
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MappingMongoConverter mongoConverter(MongoMappingContext mappingContext, MongoDatabaseFactory factory) {
|
||||||
return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
|
return new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MongoMappingContext mappingContext() {
|
||||||
|
return new MongoMappingContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
|
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) {
|
||||||
|
|
||||||
@@ -203,5 +211,11 @@ public class ZipkinIntegrationTests extends SampleTestRunner {
|
|||||||
ObservationRegistry registry() {
|
ObservationRegistry registry() {
|
||||||
return OBSERVATION_REGISTRY;
|
return OBSERVATION_REGISTRY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
Tracer tracer() {
|
||||||
|
return new SimpleTracer();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,87 @@ include::{root-target}_conventions.adoc[]
|
|||||||
include::{root-target}_metrics.adoc[]
|
include::{root-target}_metrics.adoc[]
|
||||||
|
|
||||||
include::{root-target}_spans.adoc[]
|
include::{root-target}_spans.adoc[]
|
||||||
|
|
||||||
|
[[observability.registration]]
|
||||||
|
== Observability Registration
|
||||||
|
|
||||||
|
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 adding the `@EnableMongoObservability` to either your `@SpringBootApplication` class or one of your configuration classes.
|
||||||
|
. Your project must include *Spring Boot Actuator*.
|
||||||
|
. Next you must add one of the following bean definitions based on whether you're using non-reactive or reactive Spring Data MongoDB.
|
||||||
|
+
|
||||||
|
.Registering a synchronous (non-reactive) MongoDB Micrometer setup
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
MongoClientSettingsBuilderCustomizer mongoMetricsSynchronousContextProvider(Tracer tracer,
|
||||||
|
ObservationRegistry registry) {
|
||||||
|
return (clientSettingsBuilder) -> {
|
||||||
|
clientSettingsBuilder.contextProvider( //
|
||||||
|
MongoMetricsConfigurationHelper.synchronousContextProvider(tracer, registry));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
+
|
||||||
|
.Registering a reactive MongoDB Micrometer setup
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
MongoClientSettingsBuilderCustomizer mongoMetricsReactiveContextProvider(ObservationRegistry registry) {
|
||||||
|
return (clientSettingsBuilder) -> {
|
||||||
|
clientSettingsBuilder.contextProvider( //
|
||||||
|
MongoMetricsReactiveConfigurationHelper.reactiveContextProvider(registry));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
+
|
||||||
|
IMPORTANT: ONLY add one of these two bean definitions!
|
||||||
|
. Add the following bean definition to listen for MongoDB command events and record them with Micrometer.
|
||||||
|
+
|
||||||
|
.Registering to listen for MongoDB commands.
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
MongoClientSettingsBuilderCustomizer mongoObservationCommandListenerCustomizer(MongoDBContainer mongoDBContainer,
|
||||||
|
MongoObservationCommandListener commandListener) {
|
||||||
|
return (clientSettingsBuilder) -> clientSettingsBuilder //
|
||||||
|
.addCommandListener(commandListener);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
. Add the following bean definition to register Spring Data MongoDB's trace observation handler
|
||||||
|
+
|
||||||
|
.Registering
|
||||||
|
====
|
||||||
|
[source,java]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
ObservationRegistryCustomizer<ObservationRegistry> mongoTracingHandlerCustomizer(
|
||||||
|
MongoTracingObservationHandler handler) {
|
||||||
|
return handler::register;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
====
|
||||||
|
. 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user