This Quick Start shows you how to use OpenTelemetry in your Java app to:

  • Configure a tracer
  • Generate trace data
  • Propagate context in the application’s context
  • Propagate context over HTTP
  • Export the trace data to the console and to Jaeger
  • Use Jaeger to view the trace data

This Quick Start uses a simple Java app and then a client and server component to show how to propagate context over HTTP.

Requirements

Java version 7 or newer

Setup

To use OpenTelemetry, you need to install the API and SDK artifacts. This example uses Maven, but you can use Gradle as well.

  1. Create a sample project using Maven:
    1
    2
    3
    4
    5
    
      mvn archetype:generate -DgroupId=com.mycompany.otelexample
     -DartifactId=otelexample
     -DarchetypeArtifactId=maven-archetype-quickstart
     -DarchetypeVersion=1.4
     -DinteractiveMode=false
    
  2. Add the API and SDK dependencies in otelexample/pom.xml.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    <dependency>
      <groupId>io.opentelemetry</groupId
      <artifactId>opentelemetry-api</artifactId>
      <version>0.2.0</version>
    </dependency>
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-sdk</artifactId>
      <version>0.2.0</version>
    </dependency>
    
  3. Add the LogExporter dependency.

    1
    2
    3
    4
    5
    
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-exporters-logging</artifactId>
      <version>0.2.0</version>
    </dependency>
    

Collect Trace Data

You need to configure a TraceProvider to collect tracing information. A tracer is an object that tracks the currently active span and allows you to create (or activate) new spans. As spans are created and completed, the tracer dispatches them to an exporter that can send the spans to a backend system for analysis.

In this first example, you use the LogExporter to print tracing information to the console.

  1. Copy the following otelexample/src/main/java/com/mycompany/otelexample/App.java to your favorite editor:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    package com.mycompany.otelexample;
    
    import io.opentelemetry.context.Scope;
    import io.opentelemetry.exporters.logging.LoggingExporter;
    import io.opentelemetry.sdk.OpenTelemetrySdk;
    import io.opentelemetry.sdk.trace.SpanProcessor;
    import io.opentelemetry.sdk.trace.export.SimpleSpansProcessor;
    import io.opentelemetry.trace.Span;
    import io.opentelemetry.trace.Tracer;
    import java.util.logging.Logger;
    
    public class App
    {
      private static final Logger logger =
      Logger.getLogger(App.class.getName());
    
      public static void main( String[] args )
      {
        // Configure the LoggingExporter as our exporter.
        SpanProcessor spanProcessor =
        SimpleSpansProcessor.newBuilder(new LoggingExporter()).build();
        OpenTelemetrySdk.getTracerFactory().addSpanProcessor(spanProcessor);
    
        // Add a single Span.
        Tracer tracer =
        OpenTelemetrySdk.getTracerFactory().get("otelexample");
        Span span = tracer.spanBuilder("foo").startSpan();
    
        // Set it as the current Span.
        try (Scope scope = tracer.withSpan(span))
        {
        logger.info(tracer.getCurrentSpan().toString());
        } finally {
        span.end();
        }
    
        spanProcessor.shutdown();
      }
    }
    
  2. Add a simple attribute to our Span.

    1
    2
    3
    
    Span span = tracer.spanBuilder("foo").startSpan();
    
    span.setAttribute("operation.id", 7);
    
  3. Add a Span event as well.

    1
    
    span.addEvent("operation.request_started");
    
  4. Add another Span, which will become an implicit child of the currently active instance:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    try (Scope scope = tracer.withSpan(span)) {
    
      // Add another Span, as an implicit child.
      Span childSpan = tracer.spanBuilder("bar").startSpan();
      childSpan.setAttribute("operation.id", 9);
      childSpan.addEvent("operation.request_started");
    
      try (Scope childScope = tracer.withSpan(childSpan)) {
    
        logger.info("Active Span: " + tracer.getCurrentSpan());
    
      } finally {
    
        childSpan.end();
    
      }
    }
    

Export and Explore Span Data

  1. Compile and run the sample project:

    1
    2
    3
    
    mvn compile
    
    mvn exec:java -Dexec.mainClass=com.mycompany.otelexample.App
    

    After running the code, the tracing information will appear in the console (excerpt):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    > INFO: Active Span:
    > io.opentelemetry.sdk.trace.RecordEventsReadableSpan@5c7ee7e6
    >
    > INFO: span:
    > SpanData{traceId=TraceId{traceId=dbd267e148ab815bd5ac5c40ea99ecc2},
    > spanId=SpanId{spanId=65eddeb4a1a06b9d},
    > traceFlags=TraceFlags{sampled=true},
    > tracestate=Tracestate{entries=[]},
    > parentSpanId=SpanId{spanId=1b69b9d231b14b74},
    > resource=Resource{labels={}},
    > instrumentationLibraryInfo=InstrumentationLibraryInfo{name=otelexample,
    > version=null}, name=bar, kind=INTERNAL,
    > startEpochNanos=1579315845244559041,
    > attributes={operation.id=AttributeValueLong{longValue=9}},
    > timedEvents=[TimedEvent{epochNanos=1579315845244566740,
    > name=operation.request_started, attributes={}}], links=[],
    > status=Status{canonicalCode=OK, description=null},
    > endEpochNanos=1579315845247886571, hasRemoteParent=false}
    
  2. Now send the traces into Jaeger. The following command launches the “handy in dev” but “don’t use it in PROD” all-in-one Jaeger Docker container

    1
    2
    
    docker run -d -p 6831:6831/udp -p 16686:16686 -p 14250:14250
    jaegertracing/all-in-one:latest
    
  3. Add the Jaeger dependency (and related dependencies) to otelexample/pom.xml.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    <dependency>
      <groupId>io.opentelemetry</groupId>
      <artifactId>opentelemetry-exporters-jaeger</artifactId>
      <version>0.2.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    
  4. Replace LoggingExporter with JaegerExporter.

    1
    2
    3
    4
    5
    6
    7
    
     SpanProcessor spanProcessor =
     SimpleSpansProcessor.newBuilder(JaegerGrpcSpanExporter.newBuilder()
     .setServiceName("getting_started")
     .setChannel(ManagedChannelBuilder.forAddress(
     "localhost", 14250).usePlaintext().build())
     .build()).build();
     OpenTelemetrySdk.getTracerFactory().addSpanProcessor(spanProcessor);
    
  5. Compile and run again.

  6. In a browser, navigate to the Jaeger interface using http://localhost:16686. You can see the trace that was just created.

Propagate the Context

Propagating context is a fundamental part of having complete traces across microservices. Usually users will be able to rely on existing instrumentation for libraries and frameworks (such as Spring, OkHttp, and Postgresql), which will create traces out-of-the-box [1]. However, sometimes you might want to propagate context yourself. In this step, you’ll perform a simple request using Jetty and OkHttp, and instrument it manually, obtaining a trace that will include the client and server sides.

  1. Copy the following to create the otelexample/src/main/java/com/mycompany/otelexample/SimpleServer.java file, which contains the server.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    
    package com.mycompany.otelexample;
    
    import io.opentelemetry.context.Scope;
    import io.opentelemetry.context.propagation.HttpTextFormat;
    import io.opentelemetry.sdk.OpenTelemetrySdk;
    import io.opentelemetry.trace.Span;
    import io.opentelemetry.trace.SpanContext;
    import io.opentelemetry.trace.Tracer;
    import java.io.IOException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletHandler;
    
    public final class SimpleServer {
      public void start() throws Exception {
        ServletHandler handler = new ServletHandler();
        handler.addServletWithMapping(SimpleServlet.class, "/*");
        Server server = new Server(8080);
        server.setHandler(handler);
        server.start();
      }
    
      public static final class SimpleServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse res)
        throws IOException {
          Tracer tracer =
          OpenTelemetrySdk.getTracerFactory().get("simpleserver");
    
          // Extract the propagated SpanContext.
          HttpTextFormat<SpanContext> textFormat = tracer.getHttpTextFormat();
          SpanContext spanContext = textFormat.extract(
          req, new HttpTextFormat.Getter<HttpServletRequest>() {
            @Override
            public String get(HttpServletRequest req, String key) {
              return req.getHeader(key);
            }
          });
    
          // Create a new Span as a child of the extracted SpanContext.
          Span span = tracer
          .spanBuilder("simplerequest")
          .setParent(spanContext)
          .startSpan();
    
          try (Scope scope = tracer.withSpan(span)) {
            res.setContentType("application/text");
            res.setStatus(HttpServletResponse.SC_OK);
            res.getWriter().println("bye");
          } finally {
            span.end();
          }
        }
      }
    }
    
  2. Update the main otelexample/src/main/java/com/mycompany/otelexample/App.java created earlier to start SimpleServer and inject the SpanContext when performing the request:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    
    ...
    
    public static void main( String[] args ) throws Exception
    {
      // Start our simple server.
      new SimpleServer().start();
      ...
    }
    
    static void doRequest(Tracer tracer) throws Exception {
      Request.Builder reqBuilder = new Request.Builder();
    
      // Inject the current Span into the Request.
      HttpTextFormat<SpanContext> textFormat = tracer.getHttpTextFormat();
      Span currentSpan = tracer.getCurrentSpan();
      textFormat.inject(currentSpan.getContext(), reqBuilder, new
      HttpTextFormat.Setter<Request.Builder>() {
        @Override
        public void put(Request.Builder reqBuilder, String key, String value)
        {
          reqBuilder.addHeader(key, value);
        }
      });
    
      // Perform the actual request with the propagated Span.
      Request req = reqBuilder.url("http://127.0.0.1:8080").build();
      OkHttpClient client = new OkHttpClient();
      try (Response res = client.newCall(req).execute()) {
        logger.info(res.body().string());
      }
    }
    
  3. Finally, call doRequest() in the innermost block of the main() method.

    1
    2
    3
    4
    5
    
    try (Scope childScope = tracer.withSpan(childSpan)) {
      doRequest(tracer);
    } finally {
      childSpan.end();
    }
    
  4. Compile and run.

    1
    2
    
    mvn compile
    mvn exec:java -Dexec.mainClass=com.mycompany.otelexample.App
    
  5. In a browser, navigate to the Jaeger interface at http://localhost:16686 to see an additional Span as part of our trace, representing the server side operation.

That’s it! You’re now ready to instrument your Java code with OpenTelemetry! If you’re interested in contributing but aren’t sure how to, be sure to check out the recent blog post on How to Start Contributing, there’s still a ton of work to do! Find us on Gitter or visit the OpenTelemetry Java repo if you’d like to get involved!

[1] At the moment of writing this, instrumentation for OSS libraries is an ongoing, unfinished effort.