From 4b2a93e74e5b37aaebd7b3e10f92b291498095fd Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Mon, 20 May 2024 17:50:47 +0200 Subject: [PATCH] Added an option to have baggage through Observation API (#688) whenever a key-value matches a preconfigured baggage config and an Observation started a scope then automatically baggages will also be started fixes gh-455 --- docs/modules/ROOT/pages/api.adoc | 15 +++++ .../brave/bridge/BraveBaggageManager.java | 38 +++++++++-- .../tracing/brave/bridge/BraveTracer.java | 6 ++ .../tracing/brave/bridge/BaggageTests.java | 43 +++++++++++- .../otel/bridge/OtelBaggageManager.java | 29 ++++---- .../tracing/otel/bridge/OtelTracer.java | 8 ++- .../tracing/otel/bridge/BaggageTests.java | 66 +++++++++++++++++-- .../test/simple/SimpleBaggageManager.java | 30 +++++++-- .../tracing/test/simple/SimpleTracer.java | 8 ++- .../io/micrometer/tracing/BaggageManager.java | 12 +++- .../tracing/handler/RevertingScope.java | 61 ++++++++++++++++- .../handler/TracingObservationHandler.java | 21 +++++- 12 files changed, 298 insertions(+), 39 deletions(-) diff --git a/docs/modules/ROOT/pages/api.adoc b/docs/modules/ROOT/pages/api.adoc index bfcc48ad..6759a289 100644 --- a/docs/modules/ROOT/pages/api.adoc +++ b/docs/modules/ROOT/pages/api.adoc @@ -65,6 +65,21 @@ IMPORTANT: For Brave, remember to set up the `PropagationFactory` so that it con include::{include-java}/tracing/TracingApiTests.java[tags=baggage_brave_setup,indent=0] ----- +=== Baggage with Micrometer Observation API + +If you're using Micrometer Observation API, there's no notion of baggage. If you set up a `BaggageManager` to have the baggage fields configured, we will assume that when the Observation gets put in scope, whatever low and high cardinality keys are set on the Observation will be put in scope as baggage (assuming that their names match with the configuration on the `BaggageManager`). Below you can find example of such setup with OpenTelemetry `BaggageManager`. + +[source,java,subs=+attributes] +----- +include::{include-bridges-java}/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java[tags=baggageManager,indent=0] + +include::{include-bridges-java}/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java[tags=observationRegistrySetup,indent=0] + +include::{include-bridges-java}/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java[tags=observation,indent=0] + +include::{include-bridges-java}/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java[tags=observationScope,indent=0] +----- + == Aspect Oriented Programming Micrometer Tracing contains `@NewSpan`, `@ContinueSpan`, and `@SpanTag` annotations that frameworks can use to create or customize spans for either specific types of methods such as those serving web request endpoints or, more generally, to all methods. diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java index c939a845..995eaead 100755 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveBaggageManager.java @@ -1,5 +1,5 @@ /** - * Copyright 2022 the original author or authors. + * Copyright 2024 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. @@ -22,9 +22,7 @@ import io.micrometer.tracing.*; import java.io.Closeable; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; /** * Brave implementation of a {@link BaggageManager}. @@ -36,15 +34,28 @@ public class BraveBaggageManager implements Closeable, BaggageManager { private final List tagFields; + private final List remoteFields; + + private final List baggageFields; + @Nullable private Tracer tracer; /** * Create an instance of {@link BraveBaggageManager}. * @param tagFields fields of baggage keys that should become tags on a span + * @param remoteFields fields of baggage keys that should be propagated over the wire */ - public BraveBaggageManager(List tagFields) { + public BraveBaggageManager(List tagFields, List remoteFields) { this.tagFields = tagFields; + this.remoteFields = remoteFields; + this.baggageFields = baggageFields(tagFields, remoteFields); + } + + private static List baggageFields(List tagFields, List remoteFields) { + Set combined = new HashSet<>(tagFields); + combined.addAll(remoteFields); + return new ArrayList<>(combined); } /** @@ -53,6 +64,18 @@ public BraveBaggageManager(List tagFields) { */ public BraveBaggageManager() { this.tagFields = Collections.emptyList(); + this.remoteFields = Collections.emptyList(); + this.baggageFields = Collections.emptyList(); + } + + /** + * Create an instance of {@link BraveBaggageManager}. + * @param tagFields fields of baggage keys that should become tags on a span + */ + public BraveBaggageManager(List tagFields) { + this.tagFields = tagFields; + this.remoteFields = Collections.emptyList(); + this.baggageFields = new ArrayList<>(tagFields); } @Override @@ -136,4 +159,9 @@ void setTracer(Tracer tracer) { this.tracer = tracer; } + @Override + public List getBaggageFields() { + return this.baggageFields; + } + } diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java index 9d2bb9a5..793d46c3 100755 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/main/java/io/micrometer/tracing/brave/bridge/BraveTracer.java @@ -18,6 +18,7 @@ import brave.propagation.TraceContextOrSamplingFlags; import io.micrometer.tracing.*; +import java.util.List; import java.util.Map; /** @@ -156,6 +157,11 @@ public CurrentTraceContext currentTraceContext() { return this.currentTraceContext; } + @Override + public List getBaggageFields() { + return this.braveBaggageManager.getBaggageFields(); + } + } class BraveSpanInScope implements Tracer.SpanInScope { diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BaggageTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BaggageTests.java index fb15cfea..c697ef99 100644 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BaggageTests.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-brave/src/test/java/io/micrometer/tracing/brave/bridge/BaggageTests.java @@ -1,5 +1,5 @@ /** - * Copyright 2022 the original author or authors. + * Copyright 2024 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. @@ -27,13 +27,17 @@ import io.micrometer.common.util.internal.logging.InternalLogger; import io.micrometer.common.util.internal.logging.InternalLoggerFactory; import io.micrometer.context.ContextRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import io.micrometer.tracing.*; import io.micrometer.tracing.contextpropagation.ObservationAwareSpanThreadLocalAccessor; +import io.micrometer.tracing.handler.DefaultTracingObservationHandler; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.observability.micrometer.Micrometer; import reactor.core.publisher.Hooks; @@ -42,6 +46,7 @@ import reactor.core.scheduler.Schedulers; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -61,6 +66,10 @@ class BaggageTests { public static final String TAG_VALUE = "tagValue"; + public static final String OBSERVATION_BAGGAGE_KEY = "observationKey"; + + public static final String OBSERVATION_BAGGAGE_VALUE = "observationValue"; + TestSpanHandler spanHandler = new TestSpanHandler(); StrictCurrentTraceContext braveCurrentTraceContext = StrictCurrentTraceContext.create(); @@ -73,6 +82,7 @@ class BaggageTests { .traceId128Bit(true) .propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY) .add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(KEY_1))) + .add(BaggagePropagationConfig.SingleBaggageField.remote(BaggageField.create(OBSERVATION_BAGGAGE_KEY))) .add(BaggagePropagationConfig.SingleBaggageField.local(BaggageField.create(TAG_KEY))) .build()) .sampler(Sampler.ALWAYS_SAMPLE) @@ -84,10 +94,15 @@ class BaggageTests { BravePropagator propagator = new BravePropagator(tracing); Tracer tracer = new BraveTracer(this.braveTracer, this.bridgeContext, - new BraveBaggageManager(Collections.singletonList(TAG_KEY))); + new BraveBaggageManager(Collections.singletonList(TAG_KEY), Arrays.asList(KEY_1, OBSERVATION_BAGGAGE_KEY))); ObservationRegistry observationRegistry = ObservationThreadLocalAccessor.getInstance().getObservationRegistry(); + @BeforeEach + void setupHandler() { + observationRegistry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer)); + } + @AfterEach void cleanup() { tracing.close(); @@ -258,6 +273,30 @@ void baggageTagKey() { then(mutableSpan.tags().get(TAG_KEY)).isEqualTo(TAG_VALUE); } + @Test + void baggageWithObservationApiWithRemoteFields() { + Observation observation = Observation.start("foo", observationRegistry) + .lowCardinalityKeyValue(KEY_1, TAG_VALUE) + .highCardinalityKeyValue(OBSERVATION_BAGGAGE_KEY, OBSERVATION_BAGGAGE_VALUE); + then(tracer.getBaggage(KEY_1).get()).isNull(); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isNull(); + + try (Scope scope = observation.openScope()) { + then(tracer.getBaggage(KEY_1).get()).isEqualTo(TAG_VALUE); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isEqualTo(OBSERVATION_BAGGAGE_VALUE); + } + + then(tracer.currentSpan()).isNull(); + then(tracer.getBaggage(KEY_1).get()).isNull(); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isNull(); + observation.stop(); + + then(spanHandler.spans()).hasSize(1); + MutableSpan mutableSpan = spanHandler.spans().get(0); + then(mutableSpan.tags().get(KEY_1)).isEqualTo(TAG_VALUE); + then(mutableSpan.tags().get(OBSERVATION_BAGGAGE_KEY)).isEqualTo(OBSERVATION_BAGGAGE_VALUE); + } + @Test void baggageTagKeyWithLegacyApi() { ScopedSpan span = this.tracer.startScopedSpan("call1"); diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java index aabceb9d..7ff0e637 100644 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelBaggageManager.java @@ -15,18 +15,6 @@ */ package io.micrometer.tracing.otel.bridge; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiConsumer; - import io.micrometer.common.lang.Nullable; import io.micrometer.tracing.BaggageInScope; import io.micrometer.tracing.BaggageManager; @@ -38,6 +26,9 @@ import io.opentelemetry.api.baggage.BaggageEntryMetadata; import io.opentelemetry.context.Context; +import java.util.*; +import java.util.function.BiConsumer; + import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableMap; import static java.util.function.Function.identity; @@ -60,6 +51,8 @@ public class OtelBaggageManager implements BaggageManager { private final List remoteFields; + private final List baggageFields; + private final List tagFields; /** @@ -73,6 +66,13 @@ public OtelBaggageManager(CurrentTraceContext currentTraceContext, List this.currentTraceContext = currentTraceContext; this.remoteFields = remoteFields; this.tagFields = tagFields; + this.baggageFields = baggageFields(tagFields, remoteFields); + } + + private static List baggageFields(List tagFields, List remoteFields) { + Set combined = new HashSet<>(tagFields); + combined.addAll(remoteFields); + return new ArrayList<>(combined); } @Override @@ -213,6 +213,11 @@ private String propagationString(boolean remoteField) { return propagation; } + @Override + public List getBaggageFields() { + return this.remoteFields; + } + } class CompositeBaggage implements io.opentelemetry.api.baggage.Baggage { diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java index 4d457ff5..a57357f5 100644 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/main/java/io/micrometer/tracing/otel/bridge/OtelTracer.java @@ -1,5 +1,5 @@ /** - * Copyright 2022 the original author or authors. + * Copyright 2024 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. @@ -19,6 +19,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import java.util.List; import java.util.Map; /** @@ -196,6 +197,11 @@ public BaggageInScope createBaggageInScope(TraceContext traceContext, String nam return this.otelBaggageManager.createBaggageInScope(traceContext, name, value); } + @Override + public List getBaggageFields() { + return this.otelBaggageManager.getBaggageFields(); + } + /** * Publisher of events. */ diff --git a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java index 4f45500b..f4d033c4 100644 --- a/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java +++ b/micrometer-tracing-bridges/micrometer-tracing-bridge-otel/src/test/java/io/micrometer/tracing/otel/bridge/BaggageTests.java @@ -1,12 +1,12 @@ /** - * Copyright 2022 the original author or authors. - *

+ * Copyright 2024 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. @@ -18,6 +18,8 @@ import io.micrometer.common.util.internal.logging.InternalLogger; import io.micrometer.common.util.internal.logging.InternalLoggerFactory; import io.micrometer.context.ContextRegistry; +import io.micrometer.observation.Observation; +import io.micrometer.observation.Observation.Scope; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import io.micrometer.tracing.BaggageInScope; @@ -25,6 +27,7 @@ import io.micrometer.tracing.Span; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.contextpropagation.ObservationAwareSpanThreadLocalAccessor; +import io.micrometer.tracing.handler.DefaultTracingObservationHandler; import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.common.AttributeKey; @@ -38,6 +41,7 @@ import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import reactor.core.observability.micrometer.Micrometer; import reactor.core.publisher.Hooks; @@ -46,6 +50,7 @@ import reactor.core.scheduler.Schedulers; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -69,6 +74,10 @@ class BaggageTests { public static final String TAG_VALUE = "tagValue"; + public static final String OBSERVATION_BAGGAGE_KEY = "observationKey"; + + public static final String OBSERVATION_BAGGAGE_VALUE = "observationValue"; + ArrayListSpanProcessor spanExporter = new ArrayListSpanProcessor(); SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() @@ -85,12 +94,16 @@ class BaggageTests { OtelCurrentTraceContext otelCurrentTraceContext = new OtelCurrentTraceContext(); + // tag::baggageManager[] + // There will be 3 baggage keys in total, 2 for remote fields and 1 as tag field OtelBaggageManager otelBaggageManager = new OtelBaggageManager(otelCurrentTraceContext, - Collections.singletonList(KEY_1), Collections.singletonList(TAG_KEY)); + Arrays.asList(KEY_1, OBSERVATION_BAGGAGE_KEY), Collections.singletonList(TAG_KEY)); + + // end::baggageManager[] ContextPropagators contextPropagators = ContextPropagators .create(TextMapPropagator.composite(W3CBaggagePropagator.getInstance(), W3CTraceContextPropagator.getInstance(), - new BaggageTextMapPropagator(Collections.singletonList(KEY_1), otelBaggageManager))); + new BaggageTextMapPropagator(Arrays.asList(KEY_1, OBSERVATION_BAGGAGE_KEY), otelBaggageManager))); OtelPropagator propagator = new OtelPropagator(contextPropagators, otelTracer); @@ -99,6 +112,14 @@ class BaggageTests { ObservationRegistry observationRegistry = ObservationThreadLocalAccessor.getInstance().getObservationRegistry(); + @BeforeEach + void setupHandler() { + // tag::observationRegistrySetup[] + // For automated baggage scope creation the tracing handler is required + observationRegistry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer)); + // end::observationRegistrySetup[] + } + @Test void canSetAndGetBaggage() { // GIVEN @@ -238,6 +259,39 @@ void baggageTagKey() { then(spanData.getAttributes().get(AttributeKey.stringKey(TAG_KEY))).isEqualTo(TAG_VALUE); } + @Test + void baggageWithObservationApiWithRemoteFields() { + // tag::observation[] + // An observation with low and high cardinality keys + // with key names equal to baggage key entries set on the baggage manager + Observation observation = Observation.start("foo", observationRegistry) + .lowCardinalityKeyValue(KEY_1, TAG_VALUE) + .highCardinalityKeyValue(OBSERVATION_BAGGAGE_KEY, OBSERVATION_BAGGAGE_VALUE); + // end::observation[] + then(tracer.getBaggage(KEY_1).get()).isNull(); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isNull(); + + // tag::observationScope[] + // There is no baggage here + try (Scope scope = observation.openScope()) { + // Baggage here will be automatically put in scope + then(tracer.getBaggage(KEY_1).get()).isEqualTo(TAG_VALUE); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isEqualTo(OBSERVATION_BAGGAGE_VALUE); + } + // There is no baggage here + // end::observationScope[] + then(tracer.currentSpan()).isNull(); + then(tracer.getBaggage(KEY_1).get()).isNull(); + then(tracer.getBaggage(OBSERVATION_BAGGAGE_KEY).get()).isNull(); + observation.stop(); + + then(spanExporter.spans()).hasSize(1); + SpanData spanData = spanExporter.spans().poll(); + then(spanData.getAttributes().get(AttributeKey.stringKey(KEY_1))).isEqualTo(TAG_VALUE); + then(spanData.getAttributes().get(AttributeKey.stringKey(OBSERVATION_BAGGAGE_KEY))) + .isEqualTo(OBSERVATION_BAGGAGE_VALUE); + } + @Test void baggageTagKeyWithLegacyApi() { ScopedSpan span = this.tracer.startScopedSpan("call1"); diff --git a/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleBaggageManager.java b/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleBaggageManager.java index eb0a689e..0956a696 100644 --- a/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleBaggageManager.java +++ b/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleBaggageManager.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 the original author or authors. + * Copyright 2024 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 @@ -19,10 +19,7 @@ import io.micrometer.tracing.BaggageManager; import io.micrometer.tracing.TraceContext; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -39,12 +36,25 @@ public class SimpleBaggageManager implements BaggageManager { private final SimpleTracer simpleTracer; + private final List remoteFields; + /** * Creates a new instance of {@link SimpleBaggageManager}. * @param simpleTracer simple tracer */ public SimpleBaggageManager(SimpleTracer simpleTracer) { this.simpleTracer = simpleTracer; + this.remoteFields = Collections.emptyList(); + } + + /** + * Creates a new instance of {@link SimpleBaggageManager}. + * @param simpleTracer simple tracer + * @param remoteFields fields of baggage keys that should be propagated over the wire + */ + public SimpleBaggageManager(SimpleTracer simpleTracer, List remoteFields) { + this.simpleTracer = simpleTracer; + this.remoteFields = remoteFields; } @Override @@ -136,4 +146,14 @@ public BaggageInScope createBaggageInScope(TraceContext traceContext, String nam return createSimpleBaggage(name).makeCurrent(traceContext, value); } + @Override + public Map getAllBaggage(TraceContext traceContext) { + return getAllBaggageForCtx(traceContext); + } + + @Override + public List getBaggageFields() { + return this.remoteFields; + } + } diff --git a/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleTracer.java b/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleTracer.java index 9d1544eb..d9b1a58a 100644 --- a/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleTracer.java +++ b/micrometer-tracing-tests/micrometer-tracing-test/src/main/java/io/micrometer/tracing/test/simple/SimpleTracer.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 the original author or authors. + * Copyright 2024 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 @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.Deque; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingDeque; @@ -183,6 +184,11 @@ public BaggageInScope createBaggageInScope(TraceContext traceContext, String nam return this.simpleBaggageManager.createBaggageInScope(traceContext, name, value); } + @Override + public List getBaggageFields() { + return this.simpleBaggageManager.getBaggageFields(); + } + /** * Created spans. * @return all created spans diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java index a6c7b399..cb1988e7 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/BaggageManager.java @@ -1,5 +1,5 @@ /** - * Copyright 2022 the original author or authors. + * Copyright 2024 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. @@ -18,6 +18,7 @@ import io.micrometer.common.lang.Nullable; import java.util.Collections; +import java.util.List; import java.util.Map; /** @@ -145,4 +146,13 @@ default BaggageInScope createBaggageInScope(TraceContext traceContext, String na return createBaggage(name).makeCurrent(traceContext, value); } + /** + * Returns all names of baggage fields. + * @return baggage fields + * @since 1.3.0 + */ + default List getBaggageFields() { + return Collections.emptyList(); + } + } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/RevertingScope.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/RevertingScope.java index 44f8a79a..63decd2a 100644 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/RevertingScope.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/RevertingScope.java @@ -1,5 +1,5 @@ /** - * Copyright 2023 the original author or authors. + * Copyright 2024 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. @@ -15,11 +15,17 @@ */ package io.micrometer.tracing.handler; +import io.micrometer.common.KeyValue; +import io.micrometer.common.lang.Nullable; +import io.micrometer.observation.Observation.ContextView; +import io.micrometer.tracing.BaggageInScope; import io.micrometer.tracing.CurrentTraceContext; import io.micrometer.tracing.CurrentTraceContext.Scope; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; import io.micrometer.tracing.handler.TracingObservationHandler.TracingContext; -import java.util.Objects; +import java.util.*; class RevertingScope implements CurrentTraceContext.Scope { @@ -29,7 +35,7 @@ class RevertingScope implements CurrentTraceContext.Scope { private final CurrentTraceContext.Scope previousScope; - RevertingScope(TracingContext tracingContext, Scope currentScope, Scope previousScope) { + RevertingScope(TracingContext tracingContext, Scope currentScope, @Nullable Scope previousScope) { this.tracingContext = tracingContext; this.currentScope = currentScope; this.previousScope = previousScope; @@ -64,4 +70,53 @@ public int hashCode() { return Objects.hash(tracingContext, currentScope, previousScope); } + static RevertingScope maybeWithBaggage(Tracer tracer, TracingContext tracingContext, + @Nullable TraceContext newContext, RevertingScope revertingScopeForSpan, + Scope previousScopeOnThisObservation) { + RevertingScope revertingScope = revertingScopeForSpan; + ContextView context = tracingContext.getContext(); + if (context == null) { + return revertingScope; + } + Collection baggageKeyValues = matchingBaggageKeyValues(tracer, context); + if (baggageKeyValues.isEmpty()) { + return revertingScope; + } + ArrayDeque scopes = startBaggageScopes(tracer, newContext, baggageKeyValues); + return new RevertingScope(tracingContext, () -> { + for (BaggageInScope scope : scopes) { + scope.close(); + } + revertingScope.close(); + }, previousScopeOnThisObservation); + } + + private static ArrayDeque startBaggageScopes(Tracer tracer, TraceContext newContext, + Collection baggageKeyValues) { + ArrayDeque scopes = new ArrayDeque<>(); + for (KeyValue keyValue : baggageKeyValues) { + if (newContext != null) { + scopes.addFirst(tracer.createBaggageInScope(newContext, keyValue.getKey(), keyValue.getValue())); + } + else { + scopes.addFirst(tracer.createBaggageInScope(keyValue.getKey(), keyValue.getValue())); + } + } + return scopes; + } + + private static Collection matchingBaggageKeyValues(Tracer tracer, ContextView context) { + Set lowerCaseRemoteFields = new HashSet<>(); + for (String remoteField : tracer.getBaggageFields()) { + lowerCaseRemoteFields.add(remoteField.toLowerCase()); + } + Collection baggageKeyValues = new ArrayList<>(); + for (KeyValue keyValue : context.getAllKeyValues()) { + if (lowerCaseRemoteFields.contains(keyValue.getKey().toLowerCase())) { + baggageKeyValues.add(keyValue); + } + } + return baggageKeyValues; + } + } diff --git a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingObservationHandler.java b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingObservationHandler.java index ba55c64e..00e3a9e2 100755 --- a/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingObservationHandler.java +++ b/micrometer-tracing/src/main/java/io/micrometer/tracing/handler/TracingObservationHandler.java @@ -96,8 +96,10 @@ default void setMaybeScopeOnTracingContext(TracingContext tracingContext, @Nulla TraceContext newContext = newSpan != null ? newSpan.context() : null; CurrentTraceContext.Scope scope = getTracer().currentTraceContext().maybeScope(newContext); CurrentTraceContext.Scope previousScopeOnThisObservation = tracingContext.getScope(); - tracingContext.setSpanAndScope(spanFromThisObservation, - new RevertingScope(tracingContext, scope, previousScopeOnThisObservation)); + RevertingScope revertingScope = new RevertingScope(tracingContext, scope, previousScopeOnThisObservation); + revertingScope = RevertingScope.maybeWithBaggage(getTracer(), tracingContext, newContext, revertingScope, + previousScopeOnThisObservation); + tracingContext.setSpanAndScope(spanFromThisObservation, revertingScope); } @Override @@ -182,7 +184,9 @@ else if (currentSpan != null && !currentSpan.equals(spanFromParentObservation)) * @return tracing context */ default TracingContext getTracingContext(T context) { - return context.computeIfAbsent(TracingContext.class, clazz -> new TracingContext()); + TracingContext tracingContext = context.computeIfAbsent(TracingContext.class, clazz -> new TracingContext()); + tracingContext.setContext(context); + return tracingContext; } @Override @@ -232,6 +236,8 @@ class TracingContext implements AutoCloseable { private Map scopes = new ConcurrentHashMap<>(); + private Observation.ContextView context; + /** * Returns the span. * @return span @@ -315,6 +321,15 @@ private String traceContextFromSpan() { return "null"; } + void setContext(Observation.ContextView context) { + this.context = context; + } + + @Nullable + Observation.ContextView getContext() { + return this.context; + } + } }