Skip to content

Jaeger

Spans Trace

  • Spans - A span represents a logical unit of work in Jaeger that has an operation name, the start time of the operation, and the duration.
  • Trace - A trace is a data/execution path through the system.

Jaeger Architecture

  • Agent - listens for spans sent over UDP, which it batches and sends to the collector.
  • Collector - receives traces from Jaeger agents and runs them through a processing pipeline.
  • Query - retrieves traces from storage and hosts a UI to display them.

Jaeger Implementation

Implementing tracing is very straightforward and is included in the starter kit.

Jaeger Context

Let's take a look at how a microservice uses Jaeger to trace a subset of incoming requests. The code snippets below have been simplified for demonstration purposes.


First, we installed a package called jaeger-client, which we can use to create spans and report them to the collector. We can find this dependency listed in the package.json file:

"dependencies": {
  // other dependencies omitted for brevity
  "jaeger-client": "^3.18.0",
},

Next, we instantiate a new tracer that is configured to report spans to the Jaeger collector service, which is deployed as a component of Istio. Istio's ingress gateways use the Zipkin format for traces, so we'll configure our tracer to use this format as well.

const createTracer = () => {
    tracer = initTracer(
        {
            serviceName: "marketsummary",
            reporter: {
                logSpans: true,
                collectorEndpoint: "http://jaeger-collector.istio-system.svc.cluster.local:14268/api/traces",
            },
        },
        {}
    );

    const codec = new ZipkinB3TextMapCodec({ urlEncoding: true });

    tracer.registerInjector(opentracing.FORMAT_HTTP_HEADERS, codec);
    tracer.registerExtractor(opentracing.FORMAT_HTTP_HEADERS, codec);
};

Our Istio ingress gateways are configured to trace a small subset of incoming requests. When the ingress gateway decides to trace a request, it generates a parent span, and attaches metadata about this span to the request headers. Our microservice needs to detect the existence of these headers in order extract the trace metadata, so a child span can be created and attached to the parent.

We can hook into the request lifecycle to do this prior to processing the request in our controller:

server.ext("onPreHandler", (request, h) => {
    const { path, method } = request.route;
    const parent = tracer.extract(
        opentracing.FORMAT_HTTP_HEADERS,
        request.headers
    );

    if (parent.toSpanId()) { // if the request contains trace metadata
        // create new span representing the work done by the microservice
        const span = tracer.startSpan("findMarketSummary", {
            childOf: parent,
            tags: {
                "http.locale": getLocaleFromRequest(request),
                "http.user-agent": request.headers["user-agent"],
                "http.host": request.headers.host,
                "http.path": path,
                "http.method": method,
            },
        });

        // inject the child span metadata into the http request headers
        tracer.inject(
            span,
            opentracing.FORMAT_HTTP_HEADERS,
            request.headers
        );

        // store the span in a map that we can access later
        spans[span.context().toSpanId()] = span;
    }

    return h.continue;
});

After our controller has processed the request, we can hook into the request lifecycle again to finish the span we created when we received the request:

server.ext("onPreResponse", (request, h) => {
    const spanContext = tracer.extract(
        opentracing.FORMAT_HTTP_HEADERS,
        request.headers
    );

    if (spanContext.toSpanId()) {
        // fetch span from map
        const span = spans[spanContext.toSpanId()];

        if (span) {
            if (boom.isBoom(request.response, 500)) { // if the response is an error
                // add information about the error to the span
                span.log({
                    error: request.response.stack,
                    response: request.response.output,
                });
                span.setTag("error", true);
            }

            // report the span to the jaeger collector
            span.finish();
            // remove the span from the map
            delete spans[spanContext.toSpanId()];
        }
    }

    return h.continue;
});