Skip to content

Commit

Permalink
Adds Observation Granularity
Browse files Browse the repository at this point in the history
- introduces observation levels (same as in logging)
- extends the current API to allow passing of ObservationLevel

fixes gh-3442
  • Loading branch information
marcingrzejszczak committed Feb 7, 2024
1 parent 55a202d commit d223632
Show file tree
Hide file tree
Showing 8 changed files with 358 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ void observation_config_customization() {
.observationHandler(new DefaultMeterObservationHandler(meterRegistry));

// Observation will be ignored because of the name
then(Observation.start("to.ignore", () -> new MyContext("don't ignore"), registry)).isSameAs(Observation.NOOP);
Observation ignoredBecauseOfName = Observation.start("to.ignore", () -> new MyContext("don't ignore"),
registry);
then(ignoredBecauseOfName.isNoop()).isTrue();
// Observation will be ignored because of the entries in MyContext
then(Observation.start("not.to.ignore", () -> new MyContext("user to ignore"), registry))
.isSameAs(Observation.NOOP);
Observation notToIgnore = Observation.start("not.to.ignore", () -> new MyContext("user to ignore"), registry);
then(notToIgnore.isNoop()).isTrue();

// Observation will not be ignored...
MyContext myContext = new MyContext("user not to ignore");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2024 VMware, Inc.
*
* 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 io.micrometer.observation;

/**
* Observation Level.
*
* @author Marcin Grzejszczak
* @since 1.13.0
*/
public enum Level {

ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* @author Marcin Grzejszczak
* @since 1.10.0
*/
final class NoopObservation implements Observation {
class NoopObservation implements Observation {

private static final Context CONTEXT = new Context();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,60 @@ static <T extends Context> Observation start(String name, Supplier<T> contextSup
return createNotStarted(name, contextSupplier, registry).start();
}

/**
* Create and start an {@link Observation} with the given name. All Observations of
* the same type must share the same name.
* <p>
* When no registry is passed or the observation is
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* not applicable}, a no-op observation will be returned.
* @param name name of the observation
* @param level observation level
* @param registry observation registry
* @return a started observation
* @since 1.13.0
*/
static Observation start(String name, ObservationLevel level, @Nullable ObservationRegistry registry) {
return start(name, Context::new, level, registry);
}

/**
* Creates and starts an {@link Observation}. When the {@link ObservationRegistry} is
* null or the no-op registry, this fast returns a no-op {@link Observation} and skips
* the creation of the {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* @param name name of the observation
* @param contextSupplier mutable context supplier
* @param level observation level
* @param registry observation registry
* @return started observation
* @since 1.13.0
*/
static <T extends Context> Observation start(String name, Supplier<T> contextSupplier, ObservationLevel level,
@Nullable ObservationRegistry registry) {
return createNotStarted(name, contextSupplier, level, registry).start();
}

/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When no
* registry is passed or observation is not applicable will return a no-op
* observation.
* @param name name of the observation
* @param level observation level
* @param registry observation registry
* @return created but not started observation
* @since 1.13.0
*/
static Observation createNotStarted(String name, ObservationLevel level, @Nullable ObservationRegistry registry) {
return createNotStarted(name, Context::new, level, registry);
}

/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When no
Expand Down Expand Up @@ -122,13 +176,38 @@ static Observation createNotStarted(String name, @Nullable ObservationRegistry r
*/
static <T extends Context> Observation createNotStarted(String name, Supplier<T> contextSupplier,
@Nullable ObservationRegistry registry) {
return createNotStarted(name, contextSupplier, null, registry);
}

/**
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
* {@link Observation#start()} when you want the measurements to start. When the
* {@link ObservationRegistry} is null or the no-op registry, this fast returns a
* no-op {@link Observation} and skips the creation of the
* {@link Observation.Context}. This check avoids unnecessary
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
* the context rather than the context directly. If the observation is not enabled
* (see
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
* ObservationConfig#observationPredicate}), a no-op observation will also be
* returned.
* @param name name of the observation
* @param contextSupplier supplier for mutable context
* @param level observation level
* @param registry observation registry
* @return created but not started observation
* @since 1.13.0
*/
static <T extends Context> Observation createNotStarted(String name, Supplier<T> contextSupplier,
@Nullable ObservationLevel level, @Nullable ObservationRegistry registry) {
if (registry == null || registry.isNoop()) {
return NOOP;
}
Context context = contextSupplier.get();
context.setParentFromCurrentObservation(registry);
context.setLevel(level != null ? level : null);
if (!registry.observationConfig().isObservationEnabled(name, context)) {
return NOOP;
return new PassthroughNoopObservation(context.getParentObservation());
}
return new SimpleObservation(name, registry, context);
}
Expand Down Expand Up @@ -178,7 +257,7 @@ static <T extends Context> Observation createNotStarted(@Nullable ObservationCon
convention = registry.observationConfig().getObservationConvention(context, defaultConvention);
}
if (!registry.observationConfig().isObservationEnabled(convention.getName(), context)) {
return NOOP;
return new PassthroughNoopObservation(context.getParentObservation());
}
return new SimpleObservation(convention, registry, context);
}
Expand Down Expand Up @@ -316,7 +395,7 @@ static <T extends Context> Observation createNotStarted(ObservationConvention<T>
T context = contextSupplier.get();
context.setParentFromCurrentObservation(registry);
if (!registry.observationConfig().isObservationEnabled(observationConvention.getName(), context)) {
return NOOP;
return new PassthroughNoopObservation(context.getParentObservation());
}
return new SimpleObservation(observationConvention, registry, context);
}
Expand Down Expand Up @@ -417,7 +496,7 @@ default Observation highCardinalityKeyValues(KeyValues keyValues) {
* @return {@code true} when this is a no-op observation
*/
default boolean isNoop() {
return this == NOOP;
return this == NOOP || this instanceof NoopObservation;
}

/**
Expand Down Expand Up @@ -923,7 +1002,10 @@ class Context implements ContextView {
private Throwable error;

@Nullable
private ObservationView parentObservation;
private ObservationView parentObservationView;

@Nullable
private ObservationLevel level;

private final Map<String, KeyValue> lowCardinalityKeyValues = new LinkedHashMap<>();

Expand Down Expand Up @@ -970,15 +1052,15 @@ public void setContextualName(@Nullable String contextualName) {
*/
@Nullable
public ObservationView getParentObservation() {
return parentObservation;
return parentObservationView;
}

/**
* Sets the parent {@link ObservationView}.
* @param parentObservation parent observation to set
*/
public void setParentObservation(@Nullable ObservationView parentObservation) {
this.parentObservation = parentObservation;
this.parentObservationView = parentObservation;
}

/**
Expand All @@ -987,7 +1069,7 @@ public void setParentObservation(@Nullable ObservationView parentObservation) {
* @param registry the {@link ObservationRegistry} in using
*/
void setParentFromCurrentObservation(ObservationRegistry registry) {
if (this.parentObservation == null) {
if (this.parentObservationView == null) {
Observation currentObservation = registry.getCurrentObservation();
if (currentObservation != null) {
setParentObservation(currentObservation);
Expand Down Expand Up @@ -1232,12 +1314,21 @@ public KeyValues getAllKeyValues() {
return getLowCardinalityKeyValues().and(getHighCardinalityKeyValues());
}

@Nullable
public ObservationLevel getLevel() {
return level;
}

void setLevel(ObservationLevel level) {
this.level = level;
}

@Override
public String toString() {
return "name='" + name + '\'' + ", contextualName='" + contextualName + '\'' + ", error='" + error + '\''
+ ", lowCardinalityKeyValues=" + toString(getLowCardinalityKeyValues())
+ ", highCardinalityKeyValues=" + toString(getHighCardinalityKeyValues()) + ", map=" + toString(map)
+ ", parentObservation=" + parentObservation;
+ ", parentObservation=" + parentObservationView + ", observationLevel=" + level;
}

private String toString(KeyValues keyValues) {
Expand Down Expand Up @@ -1452,6 +1543,14 @@ default <T> T getOrDefault(Object key, Supplier<T> defaultObjectSupplier) {
@NonNull
KeyValues getAllKeyValues();

/**
* Returns the observation level.
* @return observation level
*/
default Level getObservationLevel() {
return Level.ALL;
}

}

/**
Expand Down Expand Up @@ -1487,4 +1586,103 @@ interface CheckedFunction<T, R, E extends Throwable> {

}

/**
* Mapping of {@link Level} to {@link Class}.
*
* @author Marcin Grzejszczak
* @since 1.13.0
*/
class ObservationLevel {

private final Level level;

private final Class<?> clazz;

public ObservationLevel(Level level, Class<?> clazz) {
this.level = level;
this.clazz = clazz;
}

public Level getLevel() {
return level;
}

public Class<?> getClazz() {
return clazz;
}

/**
* Sets {@link Level#ALL} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel all(Class<?> clazz) {
return new ObservationLevel(Level.ALL, clazz);
}

/**
* Sets {@link Level#TRACE} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel trace(Class<?> clazz) {
return new ObservationLevel(Level.TRACE, clazz);
}

/**
* Sets {@link Level#DEBUG} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel debug(Class<?> clazz) {
return new ObservationLevel(Level.DEBUG, clazz);
}

/**
* Sets {@link Level#INFO} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel info(Class<?> clazz) {
return new ObservationLevel(Level.INFO, clazz);
}

/**
* Sets {@link Level#WARN} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel warn(Class<?> clazz) {
return new ObservationLevel(Level.WARN, clazz);
}

/**
* Sets {@link Level#ERROR} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel error(Class<?> clazz) {
return new ObservationLevel(Level.ERROR, clazz);
}

/**
* Sets {@link Level#FATAL} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel fatal(Class<?> clazz) {
return new ObservationLevel(Level.FATAL, clazz);
}

/**
* Sets {@link Level#OFF} for observation of the given classs.
* @param clazz class to observe
* @return observation level
*/
public static ObservationLevel off(Class<?> clazz) {
return new ObservationLevel(Level.OFF, clazz);
}

}

}
Loading

0 comments on commit d223632

Please sign in to comment.