Skip to content

Commit 3408d0a

Browse files
author
Artur Ciocanu
committed
Add Micrometer Observation support to Spring Dapr Messaging
Signed-off-by: Artur Ciocanu <[email protected]>
1 parent b683fce commit 3408d0a

File tree

9 files changed

+399
-15
lines changed

9 files changed

+399
-15
lines changed

dapr-spring/dapr-spring-boot-autoconfigure/pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
<optional>true</optional>
2929
</dependency>
3030
<dependency>
31-
<groupId>org.springframework.boot</groupId>
32-
<artifactId>spring-boot-starter</artifactId>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter</artifactId>
3333
</dependency>
3434
<dependency>
3535
<groupId>org.springframework.boot</groupId>
@@ -41,6 +41,10 @@
4141
<artifactId>spring-data-keyvalue</artifactId>
4242
<optional>true</optional>
4343
</dependency>
44+
<dependency>
45+
<groupId>io.micrometer</groupId>
46+
<artifactId>micrometer-tracing-bridge-otel</artifactId>
47+
</dependency>
4448
<dependency>
4549
<groupId>org.springframework.boot</groupId>
4650
<artifactId>spring-boot-starter-web</artifactId>

dapr-spring/dapr-spring-boot-autoconfigure/src/main/java/io/dapr/spring/boot/autoconfigure/pubsub/DaprPubSubProperties.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class DaprPubSubProperties {
2525
* Name of the PubSub Dapr component.
2626
*/
2727
private String name;
28+
private boolean observationEnabled;
2829

2930
public String getName() {
3031
return name;
@@ -34,4 +35,11 @@ public void setName(String name) {
3435
this.name = name;
3536
}
3637

38+
public boolean isObservationEnabled() {
39+
return observationEnabled;
40+
}
41+
42+
public void setObservationEnabled(boolean observationEnabled) {
43+
this.observationEnabled = observationEnabled;
44+
}
3745
}

dapr-spring/dapr-spring-messaging/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
<artifactId>dapr-spring-messaging</artifactId>
1313
<name>dapr-spring-messaging</name>
1414
<description>Dapr Spring Messaging</description>
15-
<packaging>jar</packaging>
15+
<packaging>jar</packaging>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>io.micrometer</groupId>
20+
<artifactId>micrometer-tracing</artifactId>
21+
</dependency>
22+
</dependencies>
1623

1724
</project>

dapr-spring/dapr-spring-messaging/src/main/java/io/dapr/spring/messaging/DaprMessagingTemplate.java

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,96 @@
1515

1616
import io.dapr.client.DaprClient;
1717
import io.dapr.client.domain.Metadata;
18+
import io.dapr.spring.messaging.observation.DaprMessagingObservationConvention;
19+
import io.dapr.spring.messaging.observation.DaprMessagingObservationDocumentation;
20+
import io.dapr.spring.messaging.observation.DaprMessagingSenderContext;
21+
import io.dapr.spring.messaging.observation.DefaultDaprMessagingObservationConvention;
22+
import io.micrometer.observation.Observation;
23+
import io.micrometer.observation.ObservationRegistry;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.beans.factory.BeanNameAware;
27+
import org.springframework.beans.factory.SmartInitializingSingleton;
28+
import org.springframework.context.ApplicationContext;
29+
import org.springframework.context.ApplicationContextAware;
1830
import reactor.core.publisher.Mono;
1931

32+
import javax.annotation.Nullable;
33+
2034
import java.util.Map;
2135

22-
public class DaprMessagingTemplate<T> implements DaprMessagingOperations<T> {
36+
/**
37+
* Create a new DaprMessagingTemplate.
38+
* @param <T> templated message type
39+
*/
40+
public class DaprMessagingTemplate<T> implements DaprMessagingOperations<T>, ApplicationContextAware, BeanNameAware,
41+
SmartInitializingSingleton {
2342

43+
private static final Logger LOGGER = LoggerFactory.getLogger(DaprMessagingTemplate.class);
2444
private static final String MESSAGE_TTL_IN_SECONDS = "10";
45+
private static final DaprMessagingObservationConvention DEFAULT_OBSERVATION_CONVENTION =
46+
DefaultDaprMessagingObservationConvention.INSTANCE;
2547

2648
private final DaprClient daprClient;
2749
private final String pubsubName;
50+
private final Map<String, String> metadata;
51+
private final boolean observationEnabled;
52+
53+
@Nullable
54+
private ApplicationContext applicationContext;
55+
56+
@Nullable
57+
private String beanName;
58+
59+
@Nullable
60+
private ObservationRegistry observationRegistry;
2861

29-
public DaprMessagingTemplate(DaprClient daprClient, String pubsubName) {
62+
@Nullable
63+
private DaprMessagingObservationConvention observationConvention;
64+
65+
/**
66+
* Constructs a new DaprMessagingTemplate.
67+
* @param daprClient Dapr client
68+
* @param pubsubName pubsub name
69+
* @param observationEnabled whether to enable observations
70+
*/
71+
public DaprMessagingTemplate(DaprClient daprClient, String pubsubName, boolean observationEnabled) {
3072
this.daprClient = daprClient;
3173
this.pubsubName = pubsubName;
74+
this.metadata = Map.of(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS);
75+
this.observationEnabled = observationEnabled;
76+
}
77+
78+
@Override
79+
public void setApplicationContext(ApplicationContext applicationContext) {
80+
this.applicationContext = applicationContext;
81+
}
82+
83+
@Override
84+
public void setBeanName(String beanName) {
85+
this.beanName = beanName;
86+
}
87+
88+
/**
89+
* If observations are enabled, attempt to obtain the Observation registry and
90+
* convention.
91+
*/
92+
@Override
93+
public void afterSingletonsInstantiated() {
94+
if (!observationEnabled) {
95+
LOGGER.debug("Observations are not enabled - not recording");
96+
return;
97+
}
98+
99+
if (applicationContext == null) {
100+
LOGGER.warn("Observations enabled but application context null - not recording");
101+
return;
102+
}
103+
104+
observationRegistry = applicationContext.getBeanProvider(ObservationRegistry.class)
105+
.getIfUnique(() -> observationRegistry);
106+
observationConvention = applicationContext.getBeanProvider(DaprMessagingObservationConvention.class)
107+
.getIfUnique(() -> observationConvention);
32108
}
33109

34110
@Override
@@ -38,29 +114,72 @@ public void send(String topic, T message) {
38114

39115
@Override
40116
public SendMessageBuilder<T> newMessage(T message) {
41-
return new SendMessageBuilderImpl<>(this, message);
117+
return new DeefaultSendMessageBuilder<>(this, message);
42118
}
43119

44120
private void doSend(String topic, T message) {
45121
doSendAsync(topic, message).block();
46122
}
47123

48124
private Mono<Void> doSendAsync(String topic, T message) {
49-
return daprClient.publishEvent(pubsubName,
50-
topic,
51-
message,
52-
Map.of(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS));
125+
LOGGER.trace("Sending message to '{}' topic", topic);
126+
127+
if (canUseObservation()) {
128+
return publishEventWithObservation(pubsubName, topic, message);
129+
}
130+
131+
return publishEvent(pubsubName, topic, message);
132+
}
133+
134+
private boolean canUseObservation() {
135+
return observationEnabled
136+
&& observationRegistry != null
137+
&& observationConvention != null
138+
&& beanName != null;
139+
}
140+
141+
private Mono<Void> publishEvent(String pubsubName, String topic, T message) {
142+
return daprClient.publishEvent(pubsubName, topic, message, metadata);
143+
}
144+
145+
private Mono<Void> publishEventWithObservation(String pubsubName, String topic, T message) {
146+
DaprMessagingSenderContext senderContext = DaprMessagingSenderContext.newContext(topic, this.beanName);
147+
Observation observation = createObservation(senderContext);
148+
149+
return observation.observe(() ->
150+
publishEvent(pubsubName, topic, message)
151+
.doOnError(err -> {
152+
LOGGER.error("Failed to send msg to '{}' topic", topic, err);
153+
154+
observation.error(err);
155+
observation.stop();
156+
})
157+
.doOnSuccess(ignore -> {
158+
LOGGER.trace("Sent msg to '{}' topic", topic);
159+
160+
observation.stop();
161+
})
162+
);
163+
}
164+
165+
private Observation createObservation(DaprMessagingSenderContext senderContext) {
166+
return DaprMessagingObservationDocumentation.TEMPLATE_OBSERVATION.observation(
167+
observationConvention,
168+
DEFAULT_OBSERVATION_CONVENTION,
169+
() -> senderContext,
170+
observationRegistry
171+
);
53172
}
54173

55-
private static class SendMessageBuilderImpl<T> implements SendMessageBuilder<T> {
174+
private static class DeefaultSendMessageBuilder<T> implements SendMessageBuilder<T> {
56175

57176
private final DaprMessagingTemplate<T> template;
58177

59178
private final T message;
60179

61180
private String topic;
62181

63-
SendMessageBuilderImpl(DaprMessagingTemplate<T> template, T message) {
182+
DeefaultSendMessageBuilder(DaprMessagingTemplate<T> template, T message) {
64183
this.template = template;
65184
this.message = message;
66185
}
@@ -74,12 +193,12 @@ public SendMessageBuilder<T> withTopic(String topic) {
74193

75194
@Override
76195
public void send() {
77-
this.template.doSend(this.topic, this.message);
196+
template.doSend(topic, message);
78197
}
79198

80199
@Override
81200
public Mono<Void> sendAsync() {
82-
return this.template.doSendAsync(this.topic, this.message);
201+
return template.doSendAsync(topic, message);
83202
}
84203

85204
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.spring.messaging.observation;
15+
16+
import io.micrometer.observation.Observation.Context;
17+
import io.micrometer.observation.ObservationConvention;
18+
19+
/**
20+
* {@link ObservationConvention} for Dapr Messaging.
21+
*
22+
*/
23+
public interface DaprMessagingObservationConvention extends ObservationConvention<DaprMessagingSenderContext> {
24+
25+
@Override
26+
default boolean supportsContext(Context context) {
27+
return context instanceof DaprMessagingSenderContext;
28+
}
29+
30+
@Override
31+
default String getName() {
32+
return "spring.dapr.messaging.template";
33+
}
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024 The Dapr Authors
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
* Unless required by applicable law or agreed to in writing, software
8+
* distributed under the License is distributed on an "AS IS" BASIS,
9+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
* See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package io.dapr.spring.messaging.observation;
15+
16+
import io.micrometer.common.docs.KeyName;
17+
import io.micrometer.observation.Observation;
18+
import io.micrometer.observation.Observation.Context;
19+
import io.micrometer.observation.ObservationConvention;
20+
import io.micrometer.observation.docs.ObservationDocumentation;
21+
22+
/**
23+
* An {@link Observation} for {@link io.dapr.spring.messaging.DaprMessagingTemplate}.
24+
*
25+
*/
26+
public enum DaprMessagingObservationDocumentation implements ObservationDocumentation {
27+
28+
/**
29+
* Observation created when a Dapr template sends a message.
30+
*/
31+
TEMPLATE_OBSERVATION {
32+
33+
@Override
34+
public Class<? extends ObservationConvention<? extends Context>> getDefaultConvention() {
35+
return DefaultDaprMessagingObservationConvention.class;
36+
}
37+
38+
@Override
39+
public String getPrefix() {
40+
return "spring.dapr.messaging.template";
41+
}
42+
43+
@Override
44+
public KeyName[] getLowCardinalityKeyNames() {
45+
return TemplateLowCardinalityTags.values();
46+
}
47+
};
48+
49+
/**
50+
* Low cardinality tags.
51+
*/
52+
public enum TemplateLowCardinalityTags implements KeyName {
53+
/**
54+
* Bean name of the template that sent the message.
55+
*/
56+
BEAN_NAME {
57+
58+
@Override
59+
public String asString() {
60+
return "spring.dapr.messaging.template.name";
61+
}
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)