Skip to content

GH-1444: Listener Observability Initial Commit #1500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Sep 19, 2022
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6f35711
GH-1444: Listener Observability Initial Commit
garyrussell Sep 6, 2022
3c767f1
Rename Sender/Receiver contexts and other PR review comments.
garyrussell Sep 7, 2022
0886783
Rename contexts to Rabbit...; supply default KeyValues via the conven…
garyrussell Sep 7, 2022
355f5db
Javadoc polishing.
garyrussell Sep 7, 2022
b39a5f9
Don't add default KV to high-card KVs.
garyrussell Sep 7, 2022
393ed51
Fix previous commit.
garyrussell Sep 7, 2022
c282d46
Fix contextual name (receiver side).
garyrussell Sep 7, 2022
c5b2975
Fix checkstyle.
garyrussell Sep 7, 2022
9de2da2
Polish previous commit.
garyrussell Sep 7, 2022
55f0437
Fix contextual name (sender side)
garyrussell Sep 7, 2022
3079268
Remove contextual names from observations.
garyrussell Sep 7, 2022
0cf3e55
Fix checkstyle.
garyrussell Sep 7, 2022
d929b0f
Remove customization of KeyValues from conventions.
garyrussell Sep 7, 2022
b1bd023
Add `getDefaultConvention()` to observations.
garyrussell Sep 13, 2022
be4be28
Fix since 3.0.
garyrussell Sep 13, 2022
d422412
Support wider convention customization.
garyrussell Sep 14, 2022
d762752
Convention type safety.
garyrussell Sep 14, 2022
f1ac117
Fix Test - not sure why PR build succeeded.
garyrussell Sep 14, 2022
03ccb33
Add Meters to ObservationTests.
garyrussell Sep 14, 2022
2557794
Fix checkstyle.
garyrussell Sep 14, 2022
970ba6c
Make INSTANCE final.
garyrussell Sep 14, 2022
a528d09
Add integration test.
garyrussell Sep 14, 2022
1927f3b
Test all available integrations.
garyrussell Sep 15, 2022
cb6ba4c
Remove redundant test code.
garyrussell Sep 15, 2022
d0b758e
Move getContextualName to conventions.
garyrussell Sep 15, 2022
0043280
Add docs.
garyrussell Sep 15, 2022
599d5b7
Fix doc link.
garyrussell Sep 15, 2022
38589a0
Remove unnecessary method overrides; make tag names more meaningful.
garyrussell Sep 15, 2022
d5464ab
Move getName() from contexts to conventions.
garyrussell Sep 17, 2022
4ec81a1
Fix Race in Test
garyrussell Sep 19, 2022
2651f1c
Fix Race in Test.
garyrussell Sep 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2022 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.amqp.rabbit.support.micrometer;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.amqp.rabbit.junit.RabbitAvailableCondition;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;

import io.micrometer.common.KeyValues;
import io.micrometer.core.tck.MeterRegistryAssert;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.tracing.Span.Kind;
import io.micrometer.tracing.exporter.FinishedSpan;
import io.micrometer.tracing.test.SampleTestRunner;
import io.micrometer.tracing.test.simple.SpanAssert;
import io.micrometer.tracing.test.simple.SpansAssert;

/**
* @author Artem Bilan
* @author Gary Russell
*
* @since 3.0
*/
@RabbitAvailable(queues = { "int.observation.testQ1", "int.observation.testQ2" })
public class ObservationIntegrationTests extends SampleTestRunner {

@Override
public TracingSetup[] getTracingSetup() {
return new TracingSetup[]{ TracingSetup.IN_MEMORY_BRAVE };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to explicitly set this - if you don't have Zipkin running or Wavefront configured they will be ignored. Why have you ignored the OTel tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks; that wasn't obvious from the docs.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://micrometer.io/docs/tracing#_running_integration_tests

 -   by asserting spans that were stored without emitting them to a reporting system

-    against running Tanzu Observability by Wavefront instance (this option turns on when you have passed the Wavefront related configuration in the constructor - otherwise the test will be disabled)

-    against running Zipkin instance (this option turns on when Zipkin is running - otherwise the test will be disabled)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct to me:

Screen Shot 2022-09-15 at 9 14 32 AM

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So testq2 send is a child of a testq1 receive ? If that's the case then it looks good!

In some cases the name has / at the beginning - should it look like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the test1 receiver forwards the message to test2 which is received by the second receiver.

Yes, with RabbitMQ, the sender doesn't know the destination queue; it sends to exchange/routingKey. In this case, I am sending to the default exchange "" with a routing key equal to the queue name (which is a default binding provided by rabbitmq).

The / is not present in the Kafka PR because the sender knows the destination topic name.

}

@Override
public SampleTestRunnerConsumer yourCode() {
// template -> listener -> template -> listener
return (bb, meterRegistry) -> {
ObservationRegistry observationRegistry = getObservationRegistry();
try (AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext()) {
applicationContext.registerBean(ObservationRegistry.class, () -> observationRegistry);
applicationContext.register(Config.class);
applicationContext.refresh();
RabbitListenerEndpointRegistry bean = applicationContext.getBean(RabbitListenerEndpointRegistry.class);
applicationContext.getBean(RabbitTemplate.class).convertAndSend("int.observation.testQ1", "test");
assertThat(applicationContext.getBean(Listener.class).latch1.await(10, TimeUnit.SECONDS)).isTrue();
}

List<FinishedSpan> finishedSpans = bb.getFinishedSpans();
SpansAssert.assertThat(finishedSpans)
.haveSameTraceId()
.hasSize(4);
SpanAssert.assertThat(finishedSpans.get(0))
.hasKindEqualTo(Kind.PRODUCER)
.hasTag("bean.name", "template");
SpanAssert.assertThat(finishedSpans.get(1))
.hasKindEqualTo(Kind.PRODUCER)
.hasTag("bean.name", "template");
SpanAssert.assertThat(finishedSpans.get(2))
.hasKindEqualTo(Kind.CONSUMER)
.hasTag("listener.id", "obs1");
SpanAssert.assertThat(finishedSpans.get(3))
.hasKindEqualTo(Kind.CONSUMER)
.hasTag("listener.id", "obs2");

MeterRegistryAssert.assertThat(getMeterRegistry())
.hasTimerWithNameAndTags("spring.rabbit.template", KeyValues.of("bean.name", "template"))
.hasTimerWithNameAndTags("spring.rabbit.template", KeyValues.of("bean.name", "template"))
.hasTimerWithNameAndTags("spring.rabbit.listener", KeyValues.of("listener.id", "obs1"))
.hasTimerWithNameAndTags("spring.rabbit.listener", KeyValues.of("listener.id", "obs1"))
.hasTimerWithNameAndTags("spring.rabbit.listener", KeyValues.of("listener.id", "obs2"));
};
}


@Configuration
@EnableRabbit
public static class Config {

@Bean
CachingConnectionFactory ccf() {
return new CachingConnectionFactory(RabbitAvailableCondition.getBrokerRunning().getConnectionFactory());
}

@Bean
RabbitTemplate template(CachingConnectionFactory ccf) {
RabbitTemplate template = new RabbitTemplate(ccf);
template.setObservationEnabled(true);
return template;
}

@Bean
SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(CachingConnectionFactory ccf) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(ccf);
factory.setContainerCustomizer(container -> container.setObservationEnabled(true));
return factory;
}

@Bean
Listener listener(RabbitTemplate template) {
return new Listener(template);
}

}

public static class Listener {

private final RabbitTemplate template;

final CountDownLatch latch1 = new CountDownLatch(1);

volatile Message message;

public Listener(RabbitTemplate template) {
this.template = template;
}

@RabbitListener(id = "obs1", queues = "int.observation.testQ1")
void listen1(Message in) {
this.template.convertAndSend("int.observation.testQ2", in);
}

@RabbitListener(id = "obs2", queues = "int.observation.testQ2")
void listen2(Message in) {
this.message = in;
this.latch1.countDown();
}

}


}