Difficulty: Beginner
Estimated Time: 10 minutes

In lesson 1, we saw the basic concepts for OpenTracing. In this lesson, you'll learn about the tracing context, which is how we tie spans together, so that we are able to form a graph-like structure (a DAG, to be more precise).

This scenario is based on the OpenTracing tutorial located at https://github.com/yurishkuro/opentracing-tutorial

The two complete programs, HelloManual and HelloActive, can be found in the solution package.

Don’t stop now! The next scenario will only take about 10 minutes to complete.

OpenTracing Tutorial - Lesson 2

Step 1 of 2

Tracing individual functions

In Lesson 1 we wrote a program that creates a trace that consists of a single span. That single span combined two operations performed by the program, formatting the output string and printing it. Let's use it as the base for this lesson.

package lesson02.exercise;

import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;

import io.jaegertracing.Configuration;
import io.jaegertracing.Configuration.ReporterConfiguration;
import io.jaegertracing.Configuration.SamplerConfiguration;
import io.jaegertracing.internal.JaegerTracer;

import com.google.common.collect.ImmutableMap;

public class Hello {

    private final Tracer tracer;

    private Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();
        span.setTag("hello-to", helloTo);

        String helloStr = String.format("Hello, %s!", helloTo);
        span.log(ImmutableMap.of("event", "string-format", "value", helloStr));

        System.out.println(helloStr);
        span.log(ImmutableMap.of("event", "println"));

        span.finish();
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("Expecting one argument");
        }
        String helloTo = args[0];
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(helloTo);
    }

    public static JaegerTracer initTracer(String service) {
        SamplerConfiguration samplerConfig = new SamplerConfiguration().fromEnv().withType("const").withParam(1);
        ReporterConfiguration reporterConfig = new ReporterConfiguration().fromEnv().withLogSpans(true);
        Configuration config = new Configuration(service).withSampler(samplerConfig).withReporter(reporterConfig);
        return config.getTracer();
    }
}

And let's switch to the Java version of the tutorial: cd opentracing-tutorial/java.

Let's move those operations into standalone functions first:

String helloStr = formatString(span, helloTo);
printHello(span, helloStr);

and the functions:

private String formatString(Span span, String helloTo) {
    String helloStr = String.format("Hello, %s!", helloTo);
    span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
    return helloStr;
}

private void printHello(Span span, String helloStr) {
    System.out.println(helloStr);
    span.log(ImmutableMap.of("event", "println"));
}

Of course, this does not change the outcome. What we really want to do is to wrap each function into its own span.

private  String formatString(Span rootSpan, String helloTo) {
    Span span = tracer.buildSpan("formatString").start();
    try {
        String helloStr = String.format("Hello, %s!", helloTo);
        span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
        return helloStr;
    } finally {
        span.finish();
    }
}

private void printHello(Span rootSpan, String helloStr) {
    Span span = tracer.buildSpan("printHello").start();
    try {
        System.out.println(helloStr);
        span.log(ImmutableMap.of("event", "println"));
    } finally {
        span.finish();
    }
}

Let's run it: ./run.sh lesson02.exercise.Hello Bryan

We got three spans, but there is a problem here. If we search for the spans in the UI each one will represent a standalone trace with a single span. That's not what we wanted!

What we really wanted was to establish causal relationship between the two new spans to the root span started in main(). We can do that by passing an additional option asChildOf to the span builder:

Span span = tracer.buildSpan("formatString").asChildOf(rootSpan).startManual();

If we think of the trace as a directed acyclic graph where nodes are the spans and edges are the causal relationships between them, then the ChildOf option is used to create one such edge between span and rootSpan. In the API the edges are represented by SpanReference type that consists of a SpanContext and a label. The SpanContext represents an immutable, thread-safe portion of the span that can be used to establish references or to propagate it over the wire. The label, or ReferenceType, describes the nature of the relationship. ChildOf relationship means that the rootSpan has a logical dependency on the child span before rootSpan can complete its operation. Another standard reference type in OpenTracing is FollowsFrom, which means the rootSpan is the ancestor in the DAG, but it does not depend on the completion of the child span, for example if the child represents a best-effort, fire-and-forget cache write.

If we modify the printHello function accordingly and run the app, we'll see that all reported spans now belong to the same trace: ./run.sh lesson02.exercise.Hello Bryan. In the UI, the trace will show a proper parent-child relationship between the spans.

For reference, here's how our final code looks like:

package lesson02.exercise;

import io.opentracing.Span;
import io.opentracing.Tracer;

import io.jaegertracing.Configuration;
import io.jaegertracing.Configuration.ReporterConfiguration;
import io.jaegertracing.Configuration.SamplerConfiguration;
import io.jaegertracing.internal.JaegerTracer;

import com.google.common.collect.ImmutableMap;

public class Hello {

    private final Tracer tracer;

    private Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();
        span.setTag("hello-to", helloTo);

        String helloStr = formatString(span, helloTo);
        printHello(span, helloStr);

        span.finish();
    }

    private String formatString(Span rootSpan, String helloTo) {
        Span span = tracer.buildSpan("formatString").start();
        try {
            String helloStr = String.format("Hello, %s!", helloTo);
            span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
            return helloStr;
        } finally {
            span.finish();
        }
    }

    private void printHello(Span rootSpan, String helloStr) {
        Span span = tracer.buildSpan("printHello").start();
        try {
            System.out.println(helloStr);
            span.log(ImmutableMap.of("event", "println"));
        } finally {
            span.finish();
        }
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            throw new IllegalArgumentException("Expecting one argument");
        }
        String helloTo = args[0];
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(helloTo);
    }

    public static JaegerTracer initTracer(String service) {
        SamplerConfiguration samplerConfig = new SamplerConfiguration().fromEnv().withType("const").withParam(1);
        ReporterConfiguration reporterConfig = new ReporterConfiguration().fromEnv().withLogSpans(true);
        Configuration config = new Configuration(service).withSampler(samplerConfig).withReporter(reporterConfig);
        return config.getTracer();
    }
}
Hello.java