1
1
package com .signalfx .tracing .examples .opentracing .kafka ;
2
2
3
+ import java .io .Closeable ;
3
4
import java .util .Properties ;
5
+ import java .util .concurrent .CountDownLatch ;
4
6
5
7
import org .apache .kafka .clients .producer .KafkaProducer ;
6
8
import org .apache .kafka .clients .producer .Producer ;
9
11
import org .apache .kafka .common .serialization .LongSerializer ;
10
12
import org .apache .kafka .common .serialization .StringSerializer ;
11
13
14
+ import brave .Tracing ;
15
+ import brave .opentracing .BraveTracer ;
16
+ import brave .sampler .CountingSampler ;
12
17
import io .opentracing .Scope ;
13
18
import io .opentracing .Tracer ;
14
19
import io .opentracing .contrib .kafka .TracingKafkaProducer ;
15
- import io .opentracing .util .GlobalTracer ;
16
20
import okhttp3 .Request ;
17
21
import zipkin2 .reporter .AsyncReporter ;
18
22
import zipkin2 .reporter .okhttp3 .OkHttpSender ;
@@ -21,19 +25,18 @@ public class ProducerApp {
21
25
22
26
private static final String NAME = "signalfx-opentracing-kafka-java-producer-example" ;
23
27
24
- public static void main (String [] args ) {
25
- // Create a single instance of a Jaeger tracer that will be used throughout the application.
26
- // If you are using a DI framework, you should rely on that as much as possible to provide
27
- // this instance. Here we are defining the tracer as an OpenTracing tracer, since it implements
28
- // that interface. You should generally use the OpenTracing interface where possible to make
29
- // it potentially easier to swap out tracers in the future.
30
- Tracer tracer = createTracer ();
28
+ public static void main (String [] args ) throws Exception {
29
+ // Here we instantiate our TracingHelper class and get the tracer from it. Normally this would
30
+ // be done by your DI framework and the resulting tracer injected to each class that needs it.
31
+ TracingHelper tracingHelper = new TracingHelper ();
32
+ Tracer tracer = tracingHelper .getTracer ();
31
33
32
34
Producer <Long , String > producer = createKafkaProducer (tracer );
33
35
34
36
String kafkaTopic = System .getProperty ("kafkaTopic" );
37
+ CountDownLatch latch = new CountDownLatch (1 );
35
38
36
- try (Scope scope = tracer .buildSpan ("root " ).startActive (true )) {
39
+ try (Scope scope = tracer .buildSpan ("producer.say_hi " ).startActive (true )) {
37
40
System .out .printf ("Sending message on Kafka topic %s...%n" , kafkaTopic );
38
41
producer .send (new ProducerRecord <>(kafkaTopic , 42L , "Hello, world!" ), (r , e ) -> {
39
42
if (e != null ) {
@@ -42,8 +45,13 @@ public static void main(String[] args) {
42
45
} else {
43
46
System .out .printf ("Sent Kafka message on %s.%n" , kafkaTopic );
44
47
}
48
+ latch .countDown ();
45
49
});
46
50
}
51
+
52
+ latch .await ();
53
+ tracingHelper .close ();
54
+ System .out .println ("Done." );
47
55
}
48
56
49
57
/**
@@ -60,44 +68,70 @@ private static Producer<Long, String> createKafkaProducer(Tracer tracer) {
60
68
}
61
69
62
70
/**
63
- * Create a Jaeger tracer instance that is configured to send span data to SignalFx. This is
64
- * intended to be called once. If you are using a DI framework, this logic would be used by
65
- * that to create a single instance of the tracer and inject it to every class that needs it.
71
+ * A helper class that encapsulates all of the Brave objects that need to be created and cleaned up
72
+ * upon shutdown. If you are using a DI framework, this logic would be used by that
73
+ * to create a single instance of the tracer and inject it to every class that needs it. Be sure
74
+ * to incorporate the logic in the close method to your DI framework's shutdown logic.
66
75
*/
67
- private static Tracer createTracer () {
68
- String ingestUrl = System .getProperty ("ingestUrl" , "https://ingest.signalfx.com" );
69
- String accessToken = System .getProperty ("accessToken" );
70
-
71
- // Build the sender that does the HTTP request containing spans to our ingest server.
72
- OkHttpSender .Builder senderBuilder = OkHttpSender .newBuilder ()
73
- .compressionEnabled (true )
74
- .endpoint (ingestUrl + "/v1/trace" );
75
-
76
- // Add an interceptor to inject the SignalFx X-SF-Token auth header.
77
- senderBuilder .clientBuilder ().addInterceptor (chain -> {
78
- Request request = chain .request ().newBuilder ()
79
- .addHeader ("X-SF-Token" , accessToken )
80
- .build ();
81
-
82
- return chain .proceed (request );
83
- });
84
-
85
- OkHttpSender sender = senderBuilder .build ();
86
-
87
- // Build the Jaeger Tracer instance, which implements the opentracing Tracer interface.
88
- io .opentracing .Tracer tracer = new io .jaegertracing .Tracer .Builder (NAME )
89
- // This configures the tracer to send all spans, but you will probably want to use
90
- // something less verbose.
91
- .withSampler (new ConstSampler (true ))
92
- // Configure the tracer to send spans in the Zipkin V2 JSON format instead of the
93
- // default Jaeger protocol, which we do not support.
94
- .withReporter (new Zipkin2Reporter (AsyncReporter .create (sender )))
95
- .build ();
96
-
97
- // It is considered best practice to at least register the GlobalTracer instance, even if you
98
- // don't generally use it.
99
- GlobalTracer .register (tracer );
100
-
101
- return tracer ;
76
+ private static class TracingHelper implements Closeable {
77
+
78
+ // We need to keep references to all of these components because they have to be closed upon
79
+ // shutdown in a certain order to avoid losing spans.
80
+ private AsyncReporter <zipkin2 .Span > reporter ;
81
+ private Tracer tracer ;
82
+ private OkHttpSender sender ;
83
+
84
+ TracingHelper () {
85
+ // The ingest url is where the span data will be sent, which can normally just be the default
86
+ // value of this property.
87
+ String ingestUrl = System .getProperty ("ingestUrl" , "https://ingest.signalfx.com" );
88
+ // This would be your organization's SignalFx access token, accessed in whatever manner most
89
+ // appropriate to your environment.
90
+ String accessToken = System .getProperty ("accessToken" );
91
+
92
+ // Build the sender that does the HTTP request containing spans to our ingest server.
93
+ OkHttpSender .Builder senderBuilder = OkHttpSender .newBuilder ()
94
+ .compressionEnabled (true )
95
+ .endpoint (ingestUrl + "/v1/trace" );
96
+
97
+ // Add an interceptor to inject the SignalFx X-SF-Token auth header.
98
+ senderBuilder .clientBuilder ().addInterceptor (chain -> {
99
+ Request request = chain .request ().newBuilder ()
100
+ .addHeader ("X-SF-Token" , accessToken )
101
+ .build ();
102
+ return chain .proceed (request );
103
+ });
104
+
105
+ this .sender = senderBuilder .build ();
106
+ this .reporter = AsyncReporter .create (sender );
107
+
108
+ // Create the Tracing instance from which we obtain the tracer instance
109
+ this .tracer = BraveTracer .create (
110
+ Tracing .newBuilder ()
111
+ // This sets the name of the local application and will be fairly prominent in the Zipkin UI.
112
+ .localServiceName (NAME )
113
+ .spanReporter (reporter )
114
+ // Use a sampler that always reports spans. You can swap this out for other samplers.
115
+ .sampler (CountingSampler .create (1.0f ))
116
+ .build ());
117
+ }
118
+
119
+ /**
120
+ * Return the tracer instance from the Tracing object. This is what spans are created through.
121
+ */
122
+ public Tracer getTracer () {
123
+ return tracer ;
124
+ }
125
+
126
+ /**
127
+ * This might be part of the shutdown logic if using a DI framework. It should be called one way
128
+ * or another though.
129
+ */
130
+ @ Override
131
+ public void close () {
132
+ reporter .flush ();
133
+ reporter .close ();
134
+ sender .close ();
135
+ }
102
136
}
103
137
}
0 commit comments