Skip to content

Commit 54865d0

Browse files
Igor Macedo QuintanilhaIgor Macedo Quintanilha
authored andcommitted
fix: use startObservation for parent observation to preserve custom conventions
When a parent observation was present, the observation was created using Observation.createNotStarted() with just the convention name, bypassing the customObservationConvention and ObservationDocumentation setup. Refactor startObservation to accept an optional parent observation and extract the observe-and-send logic into a private method, ensuring both code paths use the same convention/documentation pipeline.
1 parent 9dd6ad9 commit 54865d0

File tree

2 files changed

+62
-28
lines changed

2 files changed

+62
-28
lines changed

spring-cloud-aws-sqs/src/main/java/io/awspring/cloud/sqs/operations/AbstractMessagingTemplate.java

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -305,32 +305,22 @@ public <T> CompletableFuture<SendResult<T>> sendAsync(@Nullable String endpointN
305305
// Capture parent observation on the calling thread to propagate trace context across async boundary
306306
var parentObservation = this.observationRegistry.getCurrentObservation();
307307

308-
return preProcessMessageForSendAsync(endpointToUse, message).thenCompose(preprocessedMessage -> {
309-
// Create observation context with preprocessed message (now includes FIFO headers if applicable)
310-
var context = this.observationSpecifics.createContext(preprocessedMessage, endpointToUse);
311-
312-
// Start observation and link to parent to maintain trace continuity
313-
Observation observation;
314-
if (parentObservation != null) {
315-
observation = Observation.createNotStarted(this.observationSpecifics.getDefaultConvention().getName(),
316-
() -> context, this.observationRegistry).parentObservation(parentObservation).start();
317-
}
318-
else {
319-
observation = startObservation(context);
320-
}
321-
322-
// Add trace headers to the message
323-
var carrier = Objects.requireNonNull(context.getCarrier(), "No carrier found in context.");
324-
var messageWithObservationHeaders = MessageHeaderUtils.addHeadersIfAbsent(preprocessedMessage, carrier);
308+
return preProcessMessageForSendAsync(endpointToUse, message).thenCompose(
309+
preprocessedMessage -> observeAndSend(preprocessedMessage, message, endpointToUse, parentObservation));
310+
}
325311

326-
return doSendAndCompleteObservation(messageWithObservationHeaders, endpointToUse, context, observation)
327-
.exceptionallyCompose(
328-
t -> CompletableFuture.failedFuture(new MessagingOperationFailedException(
329-
"Message send operation failed for message %s to endpoint %s"
330-
.formatted(MessageHeaderUtils.getId(message), endpointToUse),
331-
endpointToUse, message, t)))
332-
.whenComplete((v, t) -> logSendMessageResult(endpointToUse, message, t));
333-
});
312+
private <T> CompletableFuture<SendResult<T>> observeAndSend(Message<T> preprocessedMessage,
313+
Message<T> originalMessage, String endpointToUse, @Nullable Observation parentObservation) {
314+
var context = this.observationSpecifics.createContext(preprocessedMessage, endpointToUse);
315+
Observation observation = startObservation(context, parentObservation);
316+
var carrier = Objects.requireNonNull(context.getCarrier(), "No carrier found in context.");
317+
var messageWithObservationHeaders = MessageHeaderUtils.addHeadersIfAbsent(preprocessedMessage, carrier);
318+
return doSendAndCompleteObservation(messageWithObservationHeaders, endpointToUse, context, observation)
319+
.exceptionallyCompose(t -> CompletableFuture.failedFuture(new MessagingOperationFailedException(
320+
"Message send operation failed for message %s to endpoint %s"
321+
.formatted(MessageHeaderUtils.getId(originalMessage), endpointToUse),
322+
endpointToUse, originalMessage, t)))
323+
.whenComplete((v, t) -> logSendMessageResult(endpointToUse, originalMessage, t));
334324
}
335325

336326
private <T> CompletableFuture<SendResult<T>> doSendAndCompleteObservation(Message<T> message, String endpointToUse,
@@ -353,13 +343,18 @@ private void completeObservation(@Nullable SendResult<?> sendResult, AbstractTem
353343
}
354344

355345
@SuppressWarnings("unchecked")
356-
private <Context extends Observation.Context> Observation startObservation(Context observationContext) {
346+
private <Context extends Observation.Context> Observation startObservation(Context observationContext,
347+
@Nullable Observation parentObservation) {
357348
ObservationConvention<Context> defaultConvention = (ObservationConvention<Context>) observationSpecifics
358349
.getDefaultConvention();
359350
ObservationConvention<Context> customConvention = (ObservationConvention<Context>) this.customObservationConvention;
360351
ObservationDocumentation documentation = observationSpecifics.getDocumentation();
361-
return documentation.start(customConvention, defaultConvention, () -> observationContext,
362-
this.observationRegistry);
352+
Observation observation = documentation.observation(customConvention, defaultConvention,
353+
() -> observationContext, this.observationRegistry);
354+
if (parentObservation != null) {
355+
observation.parentObservation(parentObservation);
356+
}
357+
return observation.start();
363358
}
364359

365360
protected abstract <T> Message<T> preProcessMessageForSend(String endpointToUse, Message<T> message);

spring-cloud-aws-sqs/src/test/java/io/awspring/cloud/sqs/operations/SqsTemplateObservationTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.awspring.cloud.sqs.listener.SqsHeaders;
2323
import io.awspring.cloud.sqs.support.observation.SqsTemplateObservation;
2424
import io.micrometer.common.KeyValues;
25+
import io.micrometer.observation.Observation;
2526
import io.micrometer.observation.tck.TestObservationRegistry;
2627
import io.micrometer.observation.tck.TestObservationRegistryAssert;
2728
import java.util.UUID;
@@ -163,6 +164,44 @@ void shouldCaptureErrorsInObservation() {
163164
.hasSingleObservationThat().hasError().assertThatError().isInstanceOf(RuntimeException.class);
164165
}
165166

167+
@Test
168+
void shouldApplyCustomConventionWhenParentObservationIsPresent() {
169+
// given
170+
SqsTemplateObservation.Convention customConvention = mock(SqsTemplateObservation.Convention.class);
171+
given(customConvention.supportsContext(any())).willReturn(true);
172+
given(customConvention.getName()).willReturn("spring.aws.sqs.template");
173+
174+
String lowCardinalityCustomKeyName = "custom.lowCardinality.key";
175+
String lowCardinalityCustomValue = "custom-lowCardinality-value";
176+
String highCardinalityCustomKeyName = "custom.highCardinality.key";
177+
String highCardinalityCustomValue = "custom-highCardinality-value";
178+
given(customConvention.getLowCardinalityKeyValues(any()))
179+
.willReturn(KeyValues.of(lowCardinalityCustomKeyName, lowCardinalityCustomValue));
180+
given(customConvention.getHighCardinalityKeyValues(any()))
181+
.willReturn(KeyValues.of(highCardinalityCustomKeyName, highCardinalityCustomValue));
182+
183+
TestObservationRegistry customRegistry = TestObservationRegistry.create();
184+
185+
SqsTemplate templateWithCustomConvention = SqsTemplate.builder().sqsAsyncClient(mockSqsAsyncClient)
186+
.configure(
187+
options -> options.observationRegistry(customRegistry).observationConvention(customConvention))
188+
.build();
189+
190+
// when - send within a parent observation scope
191+
Observation parentObservation = Observation.start("parent-observation", customRegistry);
192+
try (var ignored = parentObservation.openScope()) {
193+
templateWithCustomConvention.send(queueName, "test-payload");
194+
}
195+
finally {
196+
parentObservation.stop();
197+
}
198+
199+
// then - custom convention should be applied even with parent observation
200+
TestObservationRegistryAssert.then(customRegistry).hasNumberOfObservationsEqualTo(2)
201+
.hasAnObservationWithAKeyValue(lowCardinalityCustomKeyName, lowCardinalityCustomValue)
202+
.hasAnObservationWithAKeyValue(highCardinalityCustomKeyName, highCardinalityCustomValue);
203+
}
204+
166205
@Test
167206
void shouldSupportCustomKeyValuesInActiveSending() {
168207
// given

0 commit comments

Comments
 (0)