Skip to content

Observability with Micrometer & Micrometer Tracing #3942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Observation API introduction
  • Loading branch information
marcingrzejszczak committed Feb 3, 2022
commit 472f379a885449483fa211b0a475f35f1196784b
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@
import com.mongodb.event.CommandListener;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.api.instrument.MeterRegistry;
import io.micrometer.api.instrument.Tag;
import io.micrometer.api.instrument.Tags;
import io.micrometer.api.instrument.Timer;
import io.micrometer.api.instrument.observation.Observation;
import io.micrometer.api.instrument.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.BsonDocument;
Expand All @@ -54,22 +53,22 @@ public final class MicrometerMongoCommandListener implements CommandListener {
"insert", "update", "collMod", "compact", "convertToCapped", "create", "createIndexes", "drop", "dropIndexes",
"killCursors", "listIndexes", "reIndex"));
private static final Log log = LogFactory.getLog(MicrometerMongoCommandListener.class);
private final MeterRegistry registry;
private final ObservationRegistry registry;

public MicrometerMongoCommandListener(MeterRegistry registry) {
public MicrometerMongoCommandListener(ObservationRegistry registry) {
this.registry = registry;
}

private static Timer.Sample sampleFromContext(RequestContext context) {
Timer.Sample sample = context.getOrDefault(Timer.Sample.class, null);
if (sample != null) {
private static Observation observationFromContext(RequestContext context) {
Observation observation = context.getOrDefault(Observation.class, null);
if (observation != null) {
if (log.isDebugEnabled()) {
log.debug("Found a sample in mongo context [" + sample + "]");
log.debug("Found a observation in mongo context [" + observation + "]");
}
return sample;
return observation;
}
if (log.isDebugEnabled()) {
log.debug("No sample was found - will not create any child spans");
log.debug("No observation was found - will not create any child spans");
}
return null;
}
Expand Down Expand Up @@ -105,9 +104,9 @@ static String getMetricName(String commandName, @Nullable String collectionName)
if (requestContext == null) {
return;
}
Timer.Sample parent = sampleFromContext(requestContext);
Observation parent = observationFromContext(requestContext);
if (log.isDebugEnabled()) {
log.debug("Found the following sample passed from the mongo context [" + parent + "]");
log.debug("Found the following observation passed from the mongo context [" + parent + "]");
}
if (parent == null) {
return;
Expand All @@ -119,34 +118,21 @@ private void setupObservability(CommandStartedEvent event, RequestContext reques
String commandName = event.getCommandName();
BsonDocument command = event.getCommand();
String collectionName = getCollectionName(command, commandName);
Timer.Builder timerBuilder = MongoSample.MONGODB_COMMAND.toBuilder();
MongoHandlerContext mongoHandlerContext = new MongoHandlerContext(event) {
@Override public String getContextualName() {
return getMetricName(commandName, collectionName);
}

@Override public Tags getLowCardinalityTags() {
Tags tags = Tags.empty();
if (collectionName != null) {
tags = tags.and(MongoSample.LowCardinalityCommandTags.MONGODB_COLLECTION.of(collectionName));
}
Tag tag = connectionTag(event);
if (tag == null) {
return tags;
}
return tags.and(tag);
}

@Override public Tags getHighCardinalityTags() {
return Tags.of(MongoSample.HighCardinalityCommandTags.MONGODB_COMMAND.of(commandName));
}
};
Timer.Sample child = Timer.start(this.registry, mongoHandlerContext);
requestContext.put(Timer.Sample.class, child);
MongoHandlerContext mongoHandlerContext = new MongoHandlerContext(event, requestContext);
Observation observation = MongoObservation.MONGODB_COMMAND.observation(this.registry, mongoHandlerContext)
.contextualName(getMetricName(commandName, collectionName));
if (collectionName != null) {
observation.lowCardinalityTag(MongoObservation.LowCardinalityCommandTags.MONGODB_COLLECTION.of(collectionName));
}
Tag tag = connectionTag(event);
if (tag != null) {
observation.lowCardinalityTag(tag);
}
observation.highCardinalityTag(MongoObservation.HighCardinalityCommandTags.MONGODB_COMMAND.of(commandName));
requestContext.put(Observation.class, observation.start());
requestContext.put(MongoHandlerContext.class, mongoHandlerContext);
requestContext.put(Timer.Builder.class, timerBuilder);
if (log.isDebugEnabled()) {
log.debug("Created a child sample [" + child + "] for mongo instrumentation and put it in mongo context");
log.debug("Created a child observation [" + observation + "] for mongo instrumentation and put it in mongo context");
}
}

Expand All @@ -155,7 +141,7 @@ private Tag connectionTag(CommandStartedEvent event) {
if (connectionDescription != null) {
ConnectionId connectionId = connectionDescription.getConnectionId();
if (connectionId != null) {
return MongoSample.LowCardinalityCommandTags.MONGODB_CLUSTER_ID.of(connectionId.getServerId().getClusterId().getValue());
return MongoObservation.LowCardinalityCommandTags.MONGODB_CLUSTER_ID.of(connectionId.getServerId().getClusterId().getValue());
}
}
return null;
Expand All @@ -166,40 +152,34 @@ private Tag connectionTag(CommandStartedEvent event) {
if (requestContext == null) {
return;
}
Timer.Sample sample = requestContext.getOrDefault(Timer.Sample.class, null);
if (sample == null) {
Observation observation = requestContext.getOrDefault(Observation.class, null);
if (observation == null) {
return;
}
MongoHandlerContext context = requestContext.get(MongoHandlerContext.class);
context.setCommandSucceededEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command succeeded - will stop sample [" + sample + "]");
log.debug("Command succeeded - will stop observation [" + observation + "]");
}
Timer.Builder builder = requestContext.get(Timer.Builder.class);
sample.stop(builder);
requestContext.delete(Timer.Sample.class);
requestContext.delete(MongoHandlerContext.class);
observation.stop();
}

@Override public void commandFailed(CommandFailedEvent event) {
RequestContext requestContext = event.getRequestContext();
if (requestContext == null) {
return;
}
Timer.Sample sample = requestContext.getOrDefault(Timer.Sample.class, null);
if (sample == null) {
Observation observation = requestContext.getOrDefault(Observation.class, null);
if (observation == null) {
return;
}
MongoHandlerContext context = requestContext.get(MongoHandlerContext.class);
context.setCommandFailedEvent(event);
if (log.isDebugEnabled()) {
log.debug("Command failed - will stop sample [" + sample + "]");
log.debug("Command failed - will stop observation [" + observation + "]");
}
sample.error(event.getThrowable());
Timer.Builder builder = requestContext.get(Timer.Builder.class);
sample.stop(builder);
requestContext.delete(Timer.Sample.class);
requestContext.delete(MongoHandlerContext.class);
observation.error(event.getThrowable());
observation.stop();
}

@Nullable private String getCollectionName(BsonDocument command, String commandName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@

package org.springframework.data.mongodb.observability;

import com.mongodb.RequestContext;
import com.mongodb.event.CommandFailedEvent;
import com.mongodb.event.CommandStartedEvent;
import com.mongodb.event.CommandSucceededEvent;
import io.micrometer.api.instrument.Timer;
import io.micrometer.api.instrument.observation.Observation;

/**
* A {@link Timer.HandlerContext} that contains Mongo events.
* A {@link Observation.Context} that contains Mongo events.
*
* @author Marcin Grzejszczak
* @since 4.0.0
*/
public class MongoHandlerContext extends Timer.HandlerContext {
public class MongoHandlerContext extends Observation.Context {

private final CommandStartedEvent commandStartedEvent;

private final RequestContext requestContext;

private CommandSucceededEvent commandSucceededEvent;

private CommandFailedEvent commandFailedEvent;

public MongoHandlerContext(CommandStartedEvent commandStartedEvent) {
public MongoHandlerContext(CommandStartedEvent commandStartedEvent, RequestContext requestContext) {
this.commandStartedEvent = commandStartedEvent;
this.requestContext = requestContext;
}

public CommandStartedEvent getCommandStartedEvent() {
Expand All @@ -58,4 +62,8 @@ public CommandFailedEvent getCommandFailedEvent() {
public void setCommandFailedEvent(CommandFailedEvent commandFailedEvent) {
this.commandFailedEvent = commandFailedEvent;
}

public RequestContext getRequestContext() {
return requestContext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2013-2021 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.api.instrument.observation.Observation;
import io.micrometer.api.instrument.observation.ObservationHandler;

/**
* A {@link ObservationHandler} that handles {@link MongoHandlerContext}.
*
* @author Marcin Grzejszczak
* @since 4.0.0
*/
public class MongoObservabilityHandler implements ObservationHandler<MongoHandlerContext> {

@Override public void onStop(MongoHandlerContext context) {
context.getRequestContext().delete(Observation.class);
context.getRequestContext().delete(MongoHandlerContext.class);
}

@Override public boolean supportsContext(Observation.Context context) {
return context instanceof MongoHandlerContext;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package org.springframework.data.mongodb.observability;

import io.micrometer.api.instrument.docs.DocumentedSample;
import io.micrometer.api.instrument.docs.DocumentedObservation;
import io.micrometer.api.instrument.docs.TagKey;

/**
* @author Marcin Grzejszczak
* @since 1.0.0
*/
enum MongoSample implements DocumentedSample {
enum MongoObservation implements DocumentedObservation {

/**
* Timer created around a MongoDB command execution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package org.springframework.data.mongodb.observability;

import io.micrometer.api.instrument.docs.DocumentedSample;
import io.micrometer.api.instrument.docs.DocumentedObservation;
import io.micrometer.tracing.docs.DocumentedSpan;

/**
* Represents all spans created for MongoDB instrumentation.
*
* @author Marcin Grzejszczak
* @since 1.0.0
* @since 4.0.0
*/
enum MongoSpan implements DocumentedSpan {
/**
Expand All @@ -34,8 +34,8 @@ enum MongoSpan implements DocumentedSpan {
return "%s";
}

@Override public DocumentedSample overridesDefaultSpanFrom() {
return MongoSample.MONGODB_COMMAND;
@Override public DocumentedObservation overridesDefaultSpanFrom() {
return MongoObservation.MONGODB_COMMAND;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@
package org.springframework.data.mongodb.observability;

import java.net.InetSocketAddress;
import java.time.Duration;

import com.mongodb.MongoSocketException;
import com.mongodb.connection.ConnectionDescription;
import com.mongodb.event.CommandStartedEvent;
import io.micrometer.api.instrument.Timer;
import io.micrometer.api.instrument.observation.Observation;
import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingRecordingHandler;
Expand Down Expand Up @@ -52,7 +51,7 @@ public MongoTracingRecordingHandler(Tracer tracer) {
return this.tracer;
}

@Override public void onStart(Timer.Sample sample, MongoHandlerContext context) {
@Override public void onStart(MongoHandlerContext context) {
CommandStartedEvent event = context.getCommandStartedEvent();
String databaseName = event.getDatabaseName();
Span.Builder builder = this.tracer.spanBuilder().kind(Span.Kind.CLIENT)
Expand All @@ -77,19 +76,19 @@ private void setRemoteIpAndPort(CommandStartedEvent event, Span.Builder spanBuil
}
}

@Override public void onError(Timer.Sample sample, MongoHandlerContext context, Throwable throwable) {
getTracingContext(context).getSpan().error(throwable);
@Override public void onError(MongoHandlerContext context) {
context.getError().ifPresent(throwable -> getTracingContext(context).getSpan().error(throwable));
}

@Override public void onStop(Timer.Sample sample, MongoHandlerContext context, Timer timer, Duration duration) {
@Override public void onStop(MongoHandlerContext context) {
TracingContext tracingContext = getTracingContext(context);
Span span = tracingContext.getSpan();
span.name(MongoSpan.MONGODB_COMMAND_SPAN.getName(context.getContextualName()));
tagSpan(context, timer.getId(), span);
tagSpan(context, span);
span.end();
}

@Override public boolean supportsContext(Timer.HandlerContext context) {
@Override public boolean supportsContext(Observation.Context context) {
return context instanceof MongoHandlerContext;
}

Expand Down
Loading