Skip to content

Commit d144742

Browse files
Adds Observation Granularity
- introduces observation levels (same as in logging) - extends the current API to allow passing of ObservationLevel fixes gh-3442
1 parent 2124ccf commit d144742

File tree

8 files changed

+358
-21
lines changed

8 files changed

+358
-21
lines changed

docs/src/test/java/io/micrometer/docs/observation/ObservationConfiguringTests.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ void observation_config_customization() {
7676
.observationHandler(new DefaultMeterObservationHandler(meterRegistry));
7777

7878
// Observation will be ignored because of the name
79-
then(Observation.start("to.ignore", () -> new MyContext("don't ignore"), registry)).isSameAs(Observation.NOOP);
79+
Observation ignoredBecauseOfName = Observation.start("to.ignore", () -> new MyContext("don't ignore"),
80+
registry);
81+
then(ignoredBecauseOfName.isNoop()).isTrue();
8082
// Observation will be ignored because of the entries in MyContext
81-
then(Observation.start("not.to.ignore", () -> new MyContext("user to ignore"), registry))
82-
.isSameAs(Observation.NOOP);
83+
Observation notToIgnore = Observation.start("not.to.ignore", () -> new MyContext("user to ignore"), registry);
84+
then(notToIgnore.isNoop()).isTrue();
8385

8486
// Observation will not be ignored...
8587
MyContext myContext = new MyContext("user not to ignore");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2024 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.observation;
17+
18+
/**
19+
* Observation Level.
20+
*
21+
* @author Marcin Grzejszczak
22+
* @since 1.13.0
23+
*/
24+
public enum Level {
25+
26+
ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
27+
28+
}

micrometer-observation/src/main/java/io/micrometer/observation/NoopObservation.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* @author Marcin Grzejszczak
2828
* @since 1.10.0
2929
*/
30-
final class NoopObservation implements Observation {
30+
class NoopObservation implements Observation {
3131

3232
private static final Context CONTEXT = new Context();
3333

micrometer-observation/src/main/java/io/micrometer/observation/Observation.java

+207-9
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,60 @@ static <T extends Context> Observation start(String name, Supplier<T> contextSup
9090
return createNotStarted(name, contextSupplier, registry).start();
9191
}
9292

93+
/**
94+
* Create and start an {@link Observation} with the given name. All Observations of
95+
* the same type must share the same name.
96+
* <p>
97+
* When no registry is passed or the observation is
98+
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
99+
* not applicable}, a no-op observation will be returned.
100+
* @param name name of the observation
101+
* @param level observation level
102+
* @param registry observation registry
103+
* @return a started observation
104+
* @since 1.13.0
105+
*/
106+
static Observation start(String name, ObservationLevel level, @Nullable ObservationRegistry registry) {
107+
return start(name, Context::new, level, registry);
108+
}
109+
110+
/**
111+
* Creates and starts an {@link Observation}. When the {@link ObservationRegistry} is
112+
* null or the no-op registry, this fast returns a no-op {@link Observation} and skips
113+
* the creation of the {@link Observation.Context}. This check avoids unnecessary
114+
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
115+
* the context rather than the context directly. If the observation is not enabled
116+
* (see
117+
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
118+
* ObservationConfig#observationPredicate}), a no-op observation will also be
119+
* returned.
120+
* @param name name of the observation
121+
* @param contextSupplier mutable context supplier
122+
* @param level observation level
123+
* @param registry observation registry
124+
* @return started observation
125+
* @since 1.13.0
126+
*/
127+
static <T extends Context> Observation start(String name, Supplier<T> contextSupplier, ObservationLevel level,
128+
@Nullable ObservationRegistry registry) {
129+
return createNotStarted(name, contextSupplier, level, registry).start();
130+
}
131+
132+
/**
133+
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
134+
* {@link Observation#start()} when you want the measurements to start. When no
135+
* registry is passed or observation is not applicable will return a no-op
136+
* observation.
137+
* @param name name of the observation
138+
* @param level observation level
139+
* @param registry observation registry
140+
* @return created but not started observation
141+
* @since 1.13.0
142+
*/
143+
static Observation createNotStarted(String name, ObservationLevel level, @Nullable ObservationRegistry registry) {
144+
return createNotStarted(name, Context::new, level, registry);
145+
}
146+
93147
/**
94148
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
95149
* {@link Observation#start()} when you want the measurements to start. When no
@@ -122,13 +176,38 @@ static Observation createNotStarted(String name, @Nullable ObservationRegistry r
122176
*/
123177
static <T extends Context> Observation createNotStarted(String name, Supplier<T> contextSupplier,
124178
@Nullable ObservationRegistry registry) {
179+
return createNotStarted(name, contextSupplier, null, registry);
180+
}
181+
182+
/**
183+
* Creates but <b>does not start</b> an {@link Observation}. Remember to call
184+
* {@link Observation#start()} when you want the measurements to start. When the
185+
* {@link ObservationRegistry} is null or the no-op registry, this fast returns a
186+
* no-op {@link Observation} and skips the creation of the
187+
* {@link Observation.Context}. This check avoids unnecessary
188+
* {@link Observation.Context} creation, which is why it takes a {@link Supplier} for
189+
* the context rather than the context directly. If the observation is not enabled
190+
* (see
191+
* {@link ObservationRegistry.ObservationConfig#observationPredicate(ObservationPredicate)
192+
* ObservationConfig#observationPredicate}), a no-op observation will also be
193+
* returned.
194+
* @param name name of the observation
195+
* @param contextSupplier supplier for mutable context
196+
* @param level observation level
197+
* @param registry observation registry
198+
* @return created but not started observation
199+
* @since 1.13.0
200+
*/
201+
static <T extends Context> Observation createNotStarted(String name, Supplier<T> contextSupplier,
202+
@Nullable ObservationLevel level, @Nullable ObservationRegistry registry) {
125203
if (registry == null || registry.isNoop()) {
126204
return NOOP;
127205
}
128206
Context context = contextSupplier.get();
129207
context.setParentFromCurrentObservation(registry);
208+
context.setLevel(level != null ? level : null);
130209
if (!registry.observationConfig().isObservationEnabled(name, context)) {
131-
return NOOP;
210+
return new PassthroughNoopObservation(context.getParentObservation());
132211
}
133212
return new SimpleObservation(name, registry, context);
134213
}
@@ -178,7 +257,7 @@ static <T extends Context> Observation createNotStarted(@Nullable ObservationCon
178257
convention = registry.observationConfig().getObservationConvention(context, defaultConvention);
179258
}
180259
if (!registry.observationConfig().isObservationEnabled(convention.getName(), context)) {
181-
return NOOP;
260+
return new PassthroughNoopObservation(context.getParentObservation());
182261
}
183262
return new SimpleObservation(convention, registry, context);
184263
}
@@ -316,7 +395,7 @@ static <T extends Context> Observation createNotStarted(ObservationConvention<T>
316395
T context = contextSupplier.get();
317396
context.setParentFromCurrentObservation(registry);
318397
if (!registry.observationConfig().isObservationEnabled(observationConvention.getName(), context)) {
319-
return NOOP;
398+
return new PassthroughNoopObservation(context.getParentObservation());
320399
}
321400
return new SimpleObservation(observationConvention, registry, context);
322401
}
@@ -417,7 +496,7 @@ default Observation highCardinalityKeyValues(KeyValues keyValues) {
417496
* @return {@code true} when this is a no-op observation
418497
*/
419498
default boolean isNoop() {
420-
return this == NOOP;
499+
return this == NOOP || this instanceof NoopObservation;
421500
}
422501

423502
/**
@@ -923,7 +1002,10 @@ class Context implements ContextView {
9231002
private Throwable error;
9241003

9251004
@Nullable
926-
private ObservationView parentObservation;
1005+
private ObservationView parentObservationView;
1006+
1007+
@Nullable
1008+
private ObservationLevel level;
9271009

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

@@ -970,15 +1052,15 @@ public void setContextualName(@Nullable String contextualName) {
9701052
*/
9711053
@Nullable
9721054
public ObservationView getParentObservation() {
973-
return parentObservation;
1055+
return parentObservationView;
9741056
}
9751057

9761058
/**
9771059
* Sets the parent {@link ObservationView}.
9781060
* @param parentObservation parent observation to set
9791061
*/
9801062
public void setParentObservation(@Nullable ObservationView parentObservation) {
981-
this.parentObservation = parentObservation;
1063+
this.parentObservationView = parentObservation;
9821064
}
9831065

9841066
/**
@@ -987,7 +1069,7 @@ public void setParentObservation(@Nullable ObservationView parentObservation) {
9871069
* @param registry the {@link ObservationRegistry} in using
9881070
*/
9891071
void setParentFromCurrentObservation(ObservationRegistry registry) {
990-
if (this.parentObservation == null) {
1072+
if (this.parentObservationView == null) {
9911073
Observation currentObservation = registry.getCurrentObservation();
9921074
if (currentObservation != null) {
9931075
setParentObservation(currentObservation);
@@ -1232,12 +1314,21 @@ public KeyValues getAllKeyValues() {
12321314
return getLowCardinalityKeyValues().and(getHighCardinalityKeyValues());
12331315
}
12341316

1317+
@Nullable
1318+
public ObservationLevel getLevel() {
1319+
return level;
1320+
}
1321+
1322+
void setLevel(ObservationLevel level) {
1323+
this.level = level;
1324+
}
1325+
12351326
@Override
12361327
public String toString() {
12371328
return "name='" + name + '\'' + ", contextualName='" + contextualName + '\'' + ", error='" + error + '\''
12381329
+ ", lowCardinalityKeyValues=" + toString(getLowCardinalityKeyValues())
12391330
+ ", highCardinalityKeyValues=" + toString(getHighCardinalityKeyValues()) + ", map=" + toString(map)
1240-
+ ", parentObservation=" + parentObservation;
1331+
+ ", parentObservation=" + parentObservationView + ", observationLevel=" + level;
12411332
}
12421333

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

1546+
/**
1547+
* Returns the observation level.
1548+
* @return observation level
1549+
*/
1550+
default Level getObservationLevel() {
1551+
return Level.ALL;
1552+
}
1553+
14551554
}
14561555

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

14881587
}
14891588

1589+
/**
1590+
* Mapping of {@link Level} to {@link Class}.
1591+
*
1592+
* @author Marcin Grzejszczak
1593+
* @since 1.13.0
1594+
*/
1595+
class ObservationLevel {
1596+
1597+
private final Level level;
1598+
1599+
private final Class<?> clazz;
1600+
1601+
public ObservationLevel(Level level, Class<?> clazz) {
1602+
this.level = level;
1603+
this.clazz = clazz;
1604+
}
1605+
1606+
public Level getLevel() {
1607+
return level;
1608+
}
1609+
1610+
public Class<?> getClazz() {
1611+
return clazz;
1612+
}
1613+
1614+
/**
1615+
* Sets {@link Level#ALL} for observation of the given classs.
1616+
* @param clazz class to observe
1617+
* @return observation level
1618+
*/
1619+
public static ObservationLevel all(Class<?> clazz) {
1620+
return new ObservationLevel(Level.ALL, clazz);
1621+
}
1622+
1623+
/**
1624+
* Sets {@link Level#TRACE} for observation of the given classs.
1625+
* @param clazz class to observe
1626+
* @return observation level
1627+
*/
1628+
public static ObservationLevel trace(Class<?> clazz) {
1629+
return new ObservationLevel(Level.TRACE, clazz);
1630+
}
1631+
1632+
/**
1633+
* Sets {@link Level#DEBUG} for observation of the given classs.
1634+
* @param clazz class to observe
1635+
* @return observation level
1636+
*/
1637+
public static ObservationLevel debug(Class<?> clazz) {
1638+
return new ObservationLevel(Level.DEBUG, clazz);
1639+
}
1640+
1641+
/**
1642+
* Sets {@link Level#INFO} for observation of the given classs.
1643+
* @param clazz class to observe
1644+
* @return observation level
1645+
*/
1646+
public static ObservationLevel info(Class<?> clazz) {
1647+
return new ObservationLevel(Level.INFO, clazz);
1648+
}
1649+
1650+
/**
1651+
* Sets {@link Level#WARN} for observation of the given classs.
1652+
* @param clazz class to observe
1653+
* @return observation level
1654+
*/
1655+
public static ObservationLevel warn(Class<?> clazz) {
1656+
return new ObservationLevel(Level.WARN, clazz);
1657+
}
1658+
1659+
/**
1660+
* Sets {@link Level#ERROR} for observation of the given classs.
1661+
* @param clazz class to observe
1662+
* @return observation level
1663+
*/
1664+
public static ObservationLevel error(Class<?> clazz) {
1665+
return new ObservationLevel(Level.ERROR, clazz);
1666+
}
1667+
1668+
/**
1669+
* Sets {@link Level#FATAL} for observation of the given classs.
1670+
* @param clazz class to observe
1671+
* @return observation level
1672+
*/
1673+
public static ObservationLevel fatal(Class<?> clazz) {
1674+
return new ObservationLevel(Level.FATAL, clazz);
1675+
}
1676+
1677+
/**
1678+
* Sets {@link Level#OFF} for observation of the given classs.
1679+
* @param clazz class to observe
1680+
* @return observation level
1681+
*/
1682+
public static ObservationLevel off(Class<?> clazz) {
1683+
return new ObservationLevel(Level.OFF, clazz);
1684+
}
1685+
1686+
}
1687+
14901688
}

0 commit comments

Comments
 (0)