29
29
import org .apache .pulsar .client .api .TypedMessageBuilder ;
30
30
import org .apache .pulsar .client .api .interceptor .ProducerInterceptor ;
31
31
32
+ import org .springframework .beans .factory .BeanNameAware ;
33
+ import org .springframework .beans .factory .ObjectProvider ;
34
+ import org .springframework .beans .factory .SmartInitializingSingleton ;
35
+ import org .springframework .context .ApplicationContext ;
36
+ import org .springframework .context .ApplicationContextAware ;
32
37
import org .springframework .core .log .LogAccessor ;
38
+ import org .springframework .pulsar .observation .DefaultPulsarTemplateObservationConvention ;
39
+ import org .springframework .pulsar .observation .PulsarMessageSenderContext ;
40
+ import org .springframework .pulsar .observation .PulsarTemplateObservation ;
41
+ import org .springframework .pulsar .observation .PulsarTemplateObservationConvention ;
42
+
43
+ import io .micrometer .observation .Observation ;
44
+ import io .micrometer .observation .ObservationRegistry ;
33
45
34
46
/**
35
47
* A thread-safe template for executing high-level Pulsar operations.
39
51
* @author Chris Bono
40
52
* @author Alexander Preuß
41
53
*/
42
- public class PulsarTemplate <T > implements PulsarOperations <T > {
54
+ public class PulsarTemplate <T >
55
+ implements PulsarOperations <T >, ApplicationContextAware , BeanNameAware , SmartInitializingSingleton {
43
56
44
57
private final LogAccessor logger = new LogAccessor (LogFactory .getLog (this .getClass ()));
45
58
46
59
private final PulsarProducerFactory <T > producerFactory ;
47
60
48
61
private final List <ProducerInterceptor > interceptors ;
49
62
63
+ private ApplicationContext applicationContext ;
64
+
65
+ private String beanName ;
66
+
50
67
private Schema <T > schema ;
51
68
69
+ private boolean observationEnabled ;
70
+
71
+ private PulsarTemplateObservationConvention observationConvention ;
72
+
73
+ private ObservationRegistry observationRegistry ;
74
+
52
75
/**
53
76
* Construct a template instance.
54
77
* @param producerFactory the factory used to create the backing Pulsar producers.
@@ -92,14 +115,52 @@ public SendMessageBuilder<T> newMessage(T message) {
92
115
return new SendMessageBuilderImpl <>(this , message );
93
116
}
94
117
118
+ @ Override
119
+ public void setApplicationContext (ApplicationContext applicationContext ) {
120
+ this .applicationContext = applicationContext ;
121
+ }
122
+
123
+ @ Override
124
+ public void setBeanName (String beanName ) {
125
+ this .beanName = beanName ;
126
+ }
127
+
95
128
/**
96
- * Setter for schema.
129
+ * Set the schema to use on this template .
97
130
* @param schema provides the {@link Schema} used on this template
98
131
*/
99
132
public void setSchema (Schema <T > schema ) {
100
133
this .schema = schema ;
101
134
}
102
135
136
+ /**
137
+ * Set to true to enable observation via Micrometer.
138
+ * @param observationEnabled true to enable.
139
+ */
140
+ public void setObservationEnabled (boolean observationEnabled ) {
141
+ this .observationEnabled = observationEnabled ;
142
+ }
143
+
144
+ /**
145
+ * Set a custom observation convention.
146
+ * @param observationConvention the convention.
147
+ */
148
+ public void setObservationConvention (PulsarTemplateObservationConvention observationConvention ) {
149
+ this .observationConvention = observationConvention ;
150
+ }
151
+
152
+ @ Override
153
+ public void afterSingletonsInstantiated () {
154
+ // TODO is this how we want to do this? What about SBAC?
155
+ // TODO when would AC be null? Should we assert or at least log the fact if it
156
+ // happens?
157
+ if (this .observationEnabled && this .observationRegistry == null && this .applicationContext != null ) {
158
+ ObjectProvider <ObservationRegistry > registry = this .applicationContext
159
+ .getBeanProvider (ObservationRegistry .class );
160
+ this .observationRegistry = registry .getIfUnique ();
161
+ }
162
+ }
163
+
103
164
private MessageId doSend (String topic , T message , TypedMessageBuilderCustomizer <T > typedMessageBuilderCustomizer ,
104
165
MessageRouter messageRouter , ProducerBuilderCustomizer <T > producerCustomizer ) throws PulsarClientException {
105
166
try {
@@ -115,22 +176,49 @@ private CompletableFuture<MessageId> doSendAsync(String topic, T message,
115
176
ProducerBuilderCustomizer <T > producerCustomizer ) throws PulsarClientException {
116
177
final String topicName = ProducerUtils .resolveTopicName (topic , this .producerFactory );
117
178
this .logger .trace (() -> String .format ("Sending msg to '%s' topic" , topicName ));
118
- final Producer <T > producer = prepareProducerForSend (topic , message , messageRouter , producerCustomizer );
119
- TypedMessageBuilder <T > messageBuilder = producer .newMessage ().value (message );
120
- if (typedMessageBuilderCustomizer != null ) {
121
- typedMessageBuilderCustomizer .customize (messageBuilder );
122
- }
123
- return messageBuilder .sendAsync ().whenComplete ((msgId , ex ) -> {
124
- if (ex == null ) {
125
- this .logger .trace (() -> String .format ("Sent msg to '%s' topic" , topicName ));
126
- // TODO success metrics
127
- }
128
- else {
129
- this .logger .error (ex , () -> String .format ("Failed to send msg to '%s' topic" , topicName ));
130
- // TODO fail metrics
179
+
180
+ PulsarMessageSenderContext senderContext = PulsarMessageSenderContext .newContext (topicName , this .beanName );
181
+ Observation observation = newObservation (senderContext );
182
+ try {
183
+ observation .start ();
184
+ final Producer <T > producer = prepareProducerForSend (topic , message , messageRouter , producerCustomizer );
185
+ TypedMessageBuilder <T > messageBuilder = producer .newMessage ().value (message );
186
+ if (typedMessageBuilderCustomizer != null ) {
187
+ typedMessageBuilderCustomizer .customize (messageBuilder );
131
188
}
132
- ProducerUtils .closeProducerAsync (producer , this .logger );
133
- });
189
+ senderContext .properties ().forEach (messageBuilder ::property ); // propagate
190
+ // props to
191
+ // message
192
+ return messageBuilder .sendAsync ().whenComplete ((msgId , ex ) -> {
193
+ if (ex == null ) {
194
+ this .logger .trace (() -> String .format ("Sent msg to '%s' topic" , topicName ));
195
+ observation .stop ();
196
+ }
197
+ else {
198
+ this .logger .error (ex , () -> String .format ("Failed to send msg to '%s' topic" , topicName ));
199
+ observation .error (ex );
200
+ observation .stop ();
201
+ }
202
+ ProducerUtils .closeProducerAsync (producer , this .logger );
203
+ });
204
+ }
205
+ catch (RuntimeException ex ) {
206
+ observation .error (ex );
207
+ observation .stop ();
208
+ throw ex ;
209
+ }
210
+ }
211
+
212
+ private Observation newObservation (PulsarMessageSenderContext senderContext ) {
213
+ Observation observation ;
214
+ if (!this .observationEnabled || this .observationRegistry == null ) {
215
+ observation = Observation .NOOP ;
216
+ }
217
+ else {
218
+ observation = PulsarTemplateObservation .TEMPLATE_OBSERVATION .observation (this .observationConvention ,
219
+ DefaultPulsarTemplateObservationConvention .INSTANCE , senderContext , this .observationRegistry );
220
+ }
221
+ return observation ;
134
222
}
135
223
136
224
private Producer <T > prepareProducerForSend (String topic , T message , MessageRouter messageRouter ,
0 commit comments