|
18 | 18 | import io.micrometer.common.KeyValue;
|
19 | 19 | import io.micrometer.common.lang.Nullable;
|
20 | 20 | import io.micrometer.common.util.StringUtils;
|
| 21 | +import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; |
21 | 22 |
|
22 | 23 | import java.util.ArrayDeque;
|
23 | 24 | import java.util.Collection;
|
@@ -46,6 +47,8 @@ class SimpleObservation implements Observation {
|
46 | 47 |
|
47 | 48 | private final Collection<ObservationFilter> filters;
|
48 | 49 |
|
| 50 | + private final ThreadLocal<Deque<Scope>> enclosingScopes = ThreadLocal.withInitial(ArrayDeque::new); |
| 51 | + |
49 | 52 | SimpleObservation(@Nullable String name, ObservationRegistry registry, Context context) {
|
50 | 53 | this.registry = registry;
|
51 | 54 | this.context = context;
|
@@ -186,15 +189,31 @@ public void stop() {
|
186 | 189 | }
|
187 | 190 |
|
188 | 191 | notifyOnObservationStopped(modifiedContext);
|
| 192 | + this.enclosingScopes.remove(); |
189 | 193 | }
|
190 | 194 |
|
191 | 195 | @Override
|
192 | 196 | public Scope openScope() {
|
| 197 | + Deque<Scope> scopes = enclosingScopes.get(); |
| 198 | + Scope currentScope = registry.getCurrentObservationScope(); |
| 199 | + if (currentScope != null) { |
| 200 | + scopes.addFirst(currentScope); |
| 201 | + } |
193 | 202 | Scope scope = new SimpleScope(this.registry, this);
|
194 | 203 | notifyOnScopeOpened();
|
195 | 204 | return scope;
|
196 | 205 | }
|
197 | 206 |
|
| 207 | + @Nullable |
| 208 | + @Override |
| 209 | + public Scope getEnclosingScope() { |
| 210 | + Deque<Scope> scopes = enclosingScopes.get(); |
| 211 | + if (!scopes.isEmpty()) { |
| 212 | + return scopes.getFirst(); |
| 213 | + } |
| 214 | + return null; |
| 215 | + } |
| 216 | + |
198 | 217 | @Override
|
199 | 218 | public String toString() {
|
200 | 219 | return "{" + "name=" + this.context.getName() + "(" + this.context.getContextualName() + ")" + ", error="
|
@@ -228,6 +247,11 @@ private void notifyOnScopeClosed() {
|
228 | 247 | this.handlers.descendingIterator().forEachRemaining(handler -> handler.onScopeClosed(this.context));
|
229 | 248 | }
|
230 | 249 |
|
| 250 | + @SuppressWarnings("unchecked") |
| 251 | + private void notifyOnScopeMakeCurrent() { |
| 252 | + this.handlers.forEach(handler -> handler.onScopeOpened(this.context)); |
| 253 | + } |
| 254 | + |
231 | 255 | @SuppressWarnings("unchecked")
|
232 | 256 | private void notifyOnScopeReset() {
|
233 | 257 | this.handlers.forEach(handler -> handler.onScopeReset(this.context));
|
@@ -263,14 +287,67 @@ public Observation getCurrentObservation() {
|
263 | 287 |
|
264 | 288 | @Override
|
265 | 289 | public void close() {
|
| 290 | + Deque<Scope> enclosingScopes = this.currentObservation.enclosingScopes.get(); |
| 291 | + // If we're closing a scope then we have to remove an enclosing scope from the |
| 292 | + // deque |
| 293 | + if (!enclosingScopes.isEmpty()) { |
| 294 | + enclosingScopes.removeFirst(); |
| 295 | + } |
266 | 296 | this.registry.setCurrentObservationScope(previousObservationScope);
|
267 | 297 | this.currentObservation.notifyOnScopeClosed();
|
268 | 298 | }
|
269 | 299 |
|
270 | 300 | @Override
|
271 | 301 | public void reset() {
|
272 | 302 | this.registry.setCurrentObservationScope(null);
|
| 303 | + SimpleScope scope = this; |
| 304 | + while (scope != null) { |
| 305 | + // We don't want to remove any enclosing scopes when resetting |
| 306 | + // we just want to remove any scopes if they are present (that's why we're |
| 307 | + // not calling scope#close) |
| 308 | + this.registry.setCurrentObservationScope(scope.previousObservationScope); |
| 309 | + scope.currentObservation.notifyOnScopeReset(); |
| 310 | + SimpleScope simpleScope = scope; |
| 311 | + scope = (SimpleScope) simpleScope.previousObservationScope; |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + /** |
| 316 | + * This method is called e.g. via |
| 317 | + * {@link ObservationThreadLocalAccessor#restore(Observation)}. In that case, |
| 318 | + * we're calling {@link ObservationThreadLocalAccessor#reset()} first, and we're |
| 319 | + * closing all the scopes, HOWEVER those are called on the Observation scope that |
| 320 | + * was present there in thread local at the time of calling the method, NOT on the |
| 321 | + * scope that we want to make current (that one can contain some leftovers from |
| 322 | + * previous scope openings like creation of e.g. Brave scope in the TracingContext |
| 323 | + * that is there inside the Observation's Context. |
| 324 | + * |
| 325 | + * When we want to go back to the enclosing scope and want to make that scope |
| 326 | + * current we need to be sure that there are no remaining scoped objects inside |
| 327 | + * Observation's context. This is why BEFORE rebuilding the scope structure we |
| 328 | + * need to notify the handlers to clear them first (again this is a separate scope |
| 329 | + * to the one that was cleared by the reset method) via calling |
| 330 | + * {@link ObservationHandler#onScopeReset(Context)}. |
| 331 | + */ |
| 332 | + @Override |
| 333 | + public void makeCurrent() { |
273 | 334 | this.currentObservation.notifyOnScopeReset();
|
| 335 | + // When we make an enclosing scope current we must remove it from the top of |
| 336 | + // the |
| 337 | + // deque of enclosing scopes (since it will no longer be enclosing) |
| 338 | + Deque<Scope> scopeDeque = this.currentObservation.enclosingScopes.get(); |
| 339 | + if (!scopeDeque.isEmpty()) { |
| 340 | + scopeDeque.removeFirst(); |
| 341 | + } |
| 342 | + Deque<SimpleScope> scopes = new ArrayDeque<>(); |
| 343 | + SimpleScope scope = this; |
| 344 | + while (scope != null) { |
| 345 | + scopes.addFirst(scope); |
| 346 | + scope = (SimpleScope) scope.previousObservationScope; |
| 347 | + } |
| 348 | + for (SimpleScope simpleScope : scopes) { |
| 349 | + simpleScope.currentObservation.notifyOnScopeMakeCurrent(); |
| 350 | + } |
274 | 351 | }
|
275 | 352 |
|
276 | 353 | }
|
|
0 commit comments