Enabling distributed tracing in microservices with Jaeger

duration 20 minutes

Prerequisites:

Explore how to enable and customize tracing of JAX-RS and non-JAX-RS endpoint methods by using MicroProfile OpenTracing and Jaeger.

What you’ll learn

You will learn how to enable automatic tracing for JAX-RS endpoint methods and create custom tracers for non-JAX-RS endpoint methods by using MicroProfile OpenTracing.

OpenTracing is a standard API for instrumenting microservices for distributed tracing. Distributed tracing helps troubleshoot microservices by examining and logging requests as they propagate through a distributed system, allowing developers to tackle the otherwise difficult task of debugging these requests. Without a distributed tracing system in place, analyzing the workflows of operations becomes difficult, particularly in regard to pinpointing when and by whom a request is received or when a response is sent back.

Tracer and Span are two critical types in the OpenTracing specification. The Span type is the primary building block of a distributed trace, representing an individual unit of work done in a distributed system. The Trace type in OpenTracing can be thought of as a directed acyclic graph (DAG) of Spans, where the edges between Spans are called References. The Tracer interface creates Spans and Traces and understands how to serialize and deserialize their metadata across process boundaries.

MicroProfile OpenTracing enables distributed tracing in microservices. The MicroProfile OpenTracing specification doesn’t address the problem of defining, implementing, or configuring the underlying distributed tracing system. Rather, the specification makes it easier to instrument services with distributed tracing given an existing distributed tracing system.

Jaeger is an open source distributed tracing system that is compatible with the OpenTracing specification. Jaeger also provides an implementation of Tracer in the client package that is compatible with MicroProfile OpenTracing.

You’ll configure the provided inventory and system services to use Jaeger for distributed tracing with MicroProfile OpenTracing. You’ll run these services in two separate JVMs made of two server instances to demonstrate tracing in a distributed environment. If all the components were run on a single server, then any logging software would be sufficient.

Additional prerequisites

Before you begin, deploy the Jaeger all-in-one executable file to start the Jaeger tracing system. The Jaeger all-in-one executable file is configured for quick local testing. You can find information about the Jaeger server and instructions for starting the all-in-one executable file in the Jaeger documentation.

Before you proceed, make sure that your Jaeger server is up and running. Jaeger can be found at the http://localhost:16686 URL.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-opentracing-jaeger.git
cd guide-microprofile-opentracing-jaeger

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

Open a command-line session and navigate to the finish/inventory directory. Run the following Maven goal to build the inventory service and deploy it to Open Liberty:

mvn liberty:run

Open another command-line session and navigate to the finish/system directory. Run the following Maven goal to build the system service and deploy it to Open Liberty:

mvn liberty:run

After you see the following message in both command-line sessions, both of your services are ready:

The defaultServer server is ready to run a smarter planet.

Make sure that your Jaeger server is running and point your browser to the http://localhost:9081/inventory/systems/localhost URL. When you visit this endpoint, you make two GET HTTP requests, one to the system service and one to the inventory service. Both of these requests are configured to be traced, so a new trace is recorded in Jaeger.

To view the traces, go to the http://localhost:16686 URL. You can view the traces for the inventory or system services under the Search tab. Select the services in the Select a service menu and click the Find Traces button at the end of the section.

If you only see the jaeger-query option listed in the dropdown, you might need to wait a little longer and refresh the page to see the application services.

View the traces for inventory. You’ll see the following trace:

Trace result


The trace has four spans, three from inventory and one from system. Click the trace to view its details. Under Service & Operation, you see the spans in this trace. You can inspect each span by clicking it to reveal more detailed information, such as the time at which a request was received and the time at which a response was sent back.

Verify that there are three spans from inventory and one span from system:

Finished application’s trace


After you’re finished reviewing the application, stop the Open Liberty servers by pressing CTRL+C in the command-line sessions where you ran the system and inventory services. Alternatively, you can run the following goals from the finish directory in another command-line session:

 mvn -pl system liberty:stop
 mvn -pl inventory liberty:stop

Building the application

You need to start the services to see basic traces appear in Jaeger.

When you run Open Liberty in dev mode, the server listens for file changes and automatically recompiles and deploys your updates whenever you save a new change.

Open a command-line session and navigate to the start/inventory directory. Run the following Maven goal to start the inventory service in dev mode:

mvn liberty:dev

Open a command-line session and navigate to the start/system directory. Run the following Maven goal to start the system service in dev mode:

mvn liberty:dev

After you see the following message, your application server in dev mode is ready:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

When the servers start, you can find the system and inventory services at the following URLs:

Enabling existing Tracer implementation

To collect traces across your systems, you need to implement the OpenTracing Tracer interface. Jaeger provides a Tracer implementation for the Jaeger server in the jaeger-client package.

This package is already added as a dependency for you in your pom.xml file. It’s downloaded and installed automatically into each service when you run a Maven build.

Configuring the Jaeger client

In a development environment, it is important that every trace is sampled. When every trace is sampled, all spans are available in the Jaeger UI.

The JAEGER_SAMPLER_TYPE and JAEGER_SAMPLER_PARAM environment variables are set as Open Liberty configuration properties to sample all traces.

The const value for JAEGER_SAMPLER_TYPE environment variable configures the Jaeger client sampler to make the same sampling decision for each trace, based on the sampler parameter. If the sampler parameter is 1, it samples all traces. If the sampler parameter is 0, it doesn’t sample any traces.

The 1 value for JAEGER_SAMPLER_PARAM variable configures the Jaeger sampler to sample all traces.

In a production environment, this configuration might cause a lot of overhead on the application and a lower sampling rate can be used. The different values for client sampling configuration can be found in the sampling documentation.

Similarly, in a production environment, Jaeger might not be running in the same host as the application. In this case, set the hostname of the Jaeger server to the JAEGER_AGENT_HOST environment variable and set the port that communicates with the Jaeger host to the JAEGER_AGENT_PORT environment variable.

You can view the configuration environment variables at the Jaeger Java client documentation.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  3http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4    <modelVersion>4.0.0</modelVersion>
  5
  6    <groupId>io.openliberty.guides</groupId>
  7    <artifactId>guide-microprofile-opentracing-jaeger-inventory</artifactId>
  8    <version>1.0-SNAPSHOT</version>
  9    <packaging>war</packaging>
 10
 11    <properties>
 12        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14        <maven.compiler.source>1.8</maven.compiler.source>
 15        <maven.compiler.target>1.8</maven.compiler.target>
 16
 17        <!-- OpenLiberty runtime -->
 18        <liberty.var.system.http.port>9080</liberty.var.system.http.port>
 19        <liberty.var.default.http.port>9081</liberty.var.default.http.port>
 20        <liberty.var.default.https.port>9444</liberty.var.default.https.port>
 21
 22        <!-- Jaeger configuration -->
 23        <!-- tag::jaegerClientConfig[] -->
 24        <!-- tag::samplerTypeConfig[] -->
 25        <liberty.env.JAEGER_SAMPLER_TYPE>const</liberty.env.JAEGER_SAMPLER_TYPE>
 26        <!-- end::samplerTypeConfig[] -->
 27        <!-- tag::samplerParamConfig[] -->
 28        <liberty.env.JAEGER_SAMPLER_PARAM>1</liberty.env.JAEGER_SAMPLER_PARAM>
 29        <!-- end::samplerParamConfig[] -->
 30        <!-- end::jaegerClientConfig[] -->
 31    </properties>
 32
 33    <dependencies>
 34        <!-- Provided dependencies -->
 35        <dependency>
 36            <groupId>jakarta.platform</groupId>
 37            <artifactId>jakarta.jakartaee-api</artifactId>
 38            <version>9.1.0</version>
 39            <scope>provided</scope>
 40        </dependency>
 41        <dependency>
 42            <groupId>org.eclipse.microprofile</groupId>
 43            <artifactId>microprofile</artifactId>
 44            <version>5.0</version>
 45            <type>pom</type>
 46            <scope>provided</scope>
 47        </dependency>
 48        <!-- For Jaeger -->
 49        <!-- tag::jaeger[] -->
 50        <dependency>
 51            <groupId>io.jaegertracing</groupId>
 52            <artifactId>jaeger-client</artifactId>
 53            <version>1.7.0</version>
 54        </dependency>
 55        <dependency>
 56            <groupId>org.slf4j</groupId>
 57            <artifactId>slf4j-api</artifactId>
 58            <version>1.7.33</version>
 59        </dependency>
 60        <dependency>
 61            <groupId>org.slf4j</groupId>
 62            <artifactId>slf4j-jdk14</artifactId>
 63            <version>1.7.33</version>
 64        </dependency>
 65        <!-- end::jaeger[] -->
 66        <!-- For tests -->
 67        <dependency>
 68            <groupId>org.junit.jupiter</groupId>
 69            <artifactId>junit-jupiter</artifactId>
 70            <version>5.8.2</version>
 71            <scope>test</scope>
 72        </dependency>
 73        <dependency>
 74            <groupId>org.jboss.resteasy</groupId>
 75            <artifactId>resteasy-client</artifactId>
 76            <version>6.0.0.Final</version>
 77            <scope>test</scope>
 78        </dependency>
 79        <dependency>
 80            <groupId>org.jboss.resteasy</groupId>
 81            <artifactId>resteasy-json-binding-provider</artifactId>
 82            <version>6.0.0.Final</version>
 83            <scope>test</scope>
 84        </dependency>
 85        <dependency>
 86            <groupId>org.glassfish</groupId>
 87            <artifactId>jakarta.json</artifactId>
 88            <version>2.0.1</version>
 89            <scope>test</scope>
 90        </dependency>
 91    </dependencies>
 92
 93    <build>
 94        <finalName>${project.artifactId}</finalName>
 95        <plugins>
 96            <plugin>
 97                <groupId>org.apache.maven.plugins</groupId>
 98                <artifactId>maven-war-plugin</artifactId>
 99                <version>3.3.2</version>
100            </plugin>
101
102            <!-- Liberty plugin -->
103            <plugin>
104                <groupId>io.openliberty.tools</groupId>
105                <artifactId>liberty-maven-plugin</artifactId>
106                <version>3.7.1</version>
107            </plugin>
108
109            <!-- Plugin to run unit tests -->
110            <plugin>
111                <groupId>org.apache.maven.plugins</groupId>
112                <artifactId>maven-surefire-plugin</artifactId>
113                <version>2.22.2</version>
114            </plugin>
115
116            <!-- Plugin to run functional tests -->
117            <plugin>
118                <groupId>org.apache.maven.plugins</groupId>
119                <artifactId>maven-failsafe-plugin</artifactId>
120                <version>2.22.2</version>
121                <configuration>
122                    <systemPropertyVariables>
123                        <sys.http.port>${liberty.var.system.http.port}</sys.http.port>
124                        <inv.http.port>${liberty.var.default.http.port}</inv.http.port>
125                    </systemPropertyVariables>
126                </configuration>
127            </plugin>
128        </plugins>
129    </build>
130</project>

Enabling and disabling distributed tracing

The MicroProfile OpenTracing feature enables tracing of all JAX-RS endpoint methods by default. To further control and customize these traces, use the @Traced annotation to enable and disable tracing of particular methods. You can also inject a custom Tracer object to create and customize spans.

This feature is already enabled in the inventory and system configuration files.

Enabling distributed tracing without code instrumentation

Because tracing of all JAX-RS endpoint methods is enabled by default, you only need to enable the MicroProfile OpenTracing feature in the server.xml file to see some basic traces in Jaeger.

The OpenTracing API is exposed as a third-party API in Open Liberty. To add the visibility of OpenTracing APIs to the application, add third-party to the types of API packages that this class loader supports. Instead of explicitly configuring a list of API packages that includes third-party, set the +third-party value to the apiTypeVisibility attribute in the classLoader configuration. This configuration adds third-party to the default list of API package types that are supported.

server.xml

 1<server description="Sample Liberty server">
 2
 3    <featureManager>
 4        <feature>restfulWS-3.0</feature>
 5        <feature>jsonb-2.0</feature>
 6        <feature>jsonp-2.0</feature>
 7        <feature>cdi-3.0</feature>
 8        <feature>mpConfig-3.0</feature>
 9        <!-- tag::mpOpenTracing[] -->
10        <feature>mpOpenTracing-3.0</feature>
11        <!-- end::mpOpenTracing[] -->
12    </featureManager>
13
14    <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
15                  id="defaultHttpEndpoint" host="*" />
16
17    <webApplication location="guide-microprofile-opentracing-jaeger-inventory.war" contextRoot="/">
18        <!-- enable visibility to third party apis -->
19        <!-- tag::thirdParty[] -->
20        <classloader apiTypeVisibility="+third-party"/>
21        <!-- end::thirdParty[] -->
22    </webApplication>
23
24</server>

Make sure that your services are running. Then, point your browser to any of the services' endpoints and check your Jaeger server for traces.

Enabling explicit distributed tracing

Use the @Traced annotation to define explicit span creation for specific classes and methods. If you place the annotation on a class, then the annotation is automatically applied to all methods within that class. If you place the annotation on a method, then the annotation overrides the class annotation if one exists.

The @Traced annotation can be configured with the following two parameters:

  • The value=[true|false] parameter indicates whether a particular class or method is traced. For example, while all JAX-RS endpoint methods are traced by default, you can disable their tracing by using the @Traced(false) annotation. This parameter is set to true by default.

  • The operationName=<Span name> parameter indicates the name of the span that is assigned to the method that is traced. If you omit this parameter, the span is named with the <package name>.<class name>.<method name> format. If you use this parameter at a class level, then all methods within that class have the same span name unless they are explicitly overridden by another @Traced annotation.

Replace the InventoryManager class.
inventory/src/main/java/io/openliberty/guides/inventory/InventoryManager.java

InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.ArrayList;
15import java.util.Properties;
16import io.openliberty.guides.inventory.client.SystemClient;
17import io.openliberty.guides.inventory.model.InventoryList;
18import io.openliberty.guides.inventory.model.SystemData;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.inject.Inject;
21import java.util.List;
22import java.util.Collections;
23
24import org.eclipse.microprofile.config.inject.ConfigProperty;
25import org.eclipse.microprofile.opentracing.Traced;
26import io.opentracing.Scope;
27import io.opentracing.Tracer;
28import io.opentracing.Span;
29
30@ApplicationScoped
31// tag::InventoryManager[]
32public class InventoryManager {
33
34    @Inject
35    @ConfigProperty(name = "system.http.port")
36    int SYSTEM_PORT;
37
38    private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
39    private SystemClient systemClient = new SystemClient();
40    // tag::customTracer[]
41    @Inject Tracer tracer;
42    // end::customTracer[]
43
44    public Properties get(String hostname) {
45        systemClient.init(hostname, SYSTEM_PORT);
46        Properties properties = systemClient.getProperties();
47        return properties;
48    }
49
50    public void add(String hostname, Properties systemProps) {
51        Properties props = new Properties();
52        props.setProperty("os.name", systemProps.getProperty("os.name"));
53        props.setProperty("user.name", systemProps.getProperty("user.name"));
54
55        SystemData system = new SystemData(hostname, props);
56        // tag::Add[]
57        if (!systems.contains(system)) {
58            // tag::addSpan[]
59            Span span = tracer.buildSpan("add() Span").start();
60            // end::addSpan[]
61            // tag::Try[]
62            try (Scope childScope = tracer.activateSpan(span)) {
63                // tag::addToInvList[]
64                systems.add(system);
65                // end::addToInvList[]
66            } finally {
67                span.finish();
68            }
69            // end::Try[]
70        }
71        // end::Add[]
72    }
73
74    // tag::Traced[]
75    @Traced(operationName = "InventoryManager.list")
76    // end::Traced[]
77    // tag::list[]
78    public InventoryList list() {
79        return new InventoryList(systems);
80    }
81    // end::list[]
82
83    int clear() {
84        int propertiesClearedCount = systems.size();
85        systems.clear();
86        return propertiesClearedCount;
87    }
88}
89// end::InventoryManager[]

Enable tracing of the list() non-JAX-RS endpoint method by updating @Traced as shown.

Go to the http://localhost:9081/inventory/systems URL and check your Jaeger server at the http://localhost:16686 URL. If you have the Jaeger UI open from a previous step, refresh the page. Select the inventory traces and click the Find Traces button.

You see a new trace record that is two spans long. One span is for the listContents() JAX-RS endpoint method in the InventoryResource class, and the other span is for the list() method in the InventoryManager class.

Verify that you see the following spans:

Explicit trace span


InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.Properties;
15
16import org.eclipse.microprofile.opentracing.Traced;
17
18import io.openliberty.guides.inventory.model.InventoryList;
19import jakarta.enterprise.context.RequestScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.DELETE;
22import jakarta.ws.rs.GET;
23import jakarta.ws.rs.Path;
24import jakarta.ws.rs.PathParam;
25import jakarta.ws.rs.Produces;
26import jakarta.ws.rs.core.MediaType;
27import jakarta.ws.rs.core.Response;
28
29@RequestScoped
30@Path("/systems")
31// tag::InventoryResource[]
32public class InventoryResource {
33
34    @Inject InventoryManager manager;
35
36    @GET
37    @Path("/{hostname}")
38    @Produces(MediaType.APPLICATION_JSON)
39    // tag::getPropertiesForHost[]
40    public Response getPropertiesForHost(@PathParam("hostname") String hostname) {
41        Properties props = manager.get(hostname);
42        if (props == null) {
43            return Response.status(Response.Status.NOT_FOUND)
44                           .entity("{ \"error\" : \"Unknown hostname or the system "
45                           + "service may not be running on " + hostname + "\" }")
46                           .build();
47        }
48        manager.add(hostname, props);
49        return Response.ok(props).build();
50    }
51    // end::getPropertiesForHost[]
52
53    @GET
54    // tag::Traced-false[]
55    @Traced(false)
56    // end::Traced-false[]
57    @Produces(MediaType.APPLICATION_JSON)
58    // tag::listContents[]
59    public InventoryList listContents() {
60        return manager.list();
61    }
62    // end::listContents[]
63
64    @DELETE
65    @Produces(MediaType.APPLICATION_JSON)
66    public Response clearContents() {
67        int cleared = manager.clear();
68
69        if (cleared == 0) {
70            return Response.status(Response.Status.NOT_MODIFIED)
71                    .build();
72        }
73        return Response.status(Response.Status.OK)
74                .build();
75    }
76}
77// end::InventoryResource[]

Disable automatic distributed tracing

You can use the @Traced annotation with a value of false to disable automatic distributed tracing of JAX-RS endpoint methods.

Replace the InventoryResource class.
inventory/src/main/java/io/openliberty/guides/inventory/InventoryResource.java

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.Properties;
15
16import org.eclipse.microprofile.opentracing.Traced;
17
18import io.openliberty.guides.inventory.model.InventoryList;
19import jakarta.enterprise.context.RequestScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.DELETE;
22import jakarta.ws.rs.GET;
23import jakarta.ws.rs.Path;
24import jakarta.ws.rs.PathParam;
25import jakarta.ws.rs.Produces;
26import jakarta.ws.rs.core.MediaType;
27import jakarta.ws.rs.core.Response;
28
29@RequestScoped
30@Path("/systems")
31// tag::InventoryResource[]
32public class InventoryResource {
33
34    @Inject InventoryManager manager;
35
36    @GET
37    @Path("/{hostname}")
38    @Produces(MediaType.APPLICATION_JSON)
39    // tag::getPropertiesForHost[]
40    public Response getPropertiesForHost(@PathParam("hostname") String hostname) {
41        Properties props = manager.get(hostname);
42        if (props == null) {
43            return Response.status(Response.Status.NOT_FOUND)
44                           .entity("{ \"error\" : \"Unknown hostname or the system "
45                           + "service may not be running on " + hostname + "\" }")
46                           .build();
47        }
48        manager.add(hostname, props);
49        return Response.ok(props).build();
50    }
51    // end::getPropertiesForHost[]
52
53    @GET
54    // tag::Traced-false[]
55    @Traced(false)
56    // end::Traced-false[]
57    @Produces(MediaType.APPLICATION_JSON)
58    // tag::listContents[]
59    public InventoryList listContents() {
60        return manager.list();
61    }
62    // end::listContents[]
63
64    @DELETE
65    @Produces(MediaType.APPLICATION_JSON)
66    public Response clearContents() {
67        int cleared = manager.clear();
68
69        if (cleared == 0) {
70            return Response.status(Response.Status.NOT_MODIFIED)
71                    .build();
72        }
73        return Response.status(Response.Status.OK)
74                .build();
75    }
76}
77// end::InventoryResource[]

Disable tracing of the listContents() JAX-RS endpoint method by setting @Traced(false).

Go to the http://localhost:9081/inventory/systems URL and check your Jaeger server at the http://localhost:16686 URL. If you have the Jaeger UI open from a previous step, refresh the page. Select the inventory traces and click the Find Traces button. You see a new trace record that is just one span long for the remaining list() method in the InventoryManager class.

Verify that you see the following span:

Disable trace span


InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.ArrayList;
15import java.util.Properties;
16import io.openliberty.guides.inventory.client.SystemClient;
17import io.openliberty.guides.inventory.model.InventoryList;
18import io.openliberty.guides.inventory.model.SystemData;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.inject.Inject;
21import java.util.List;
22import java.util.Collections;
23
24import org.eclipse.microprofile.config.inject.ConfigProperty;
25import org.eclipse.microprofile.opentracing.Traced;
26import io.opentracing.Scope;
27import io.opentracing.Tracer;
28import io.opentracing.Span;
29
30@ApplicationScoped
31// tag::InventoryManager[]
32public class InventoryManager {
33
34    @Inject
35    @ConfigProperty(name = "system.http.port")
36    int SYSTEM_PORT;
37
38    private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
39    private SystemClient systemClient = new SystemClient();
40    // tag::customTracer[]
41    @Inject Tracer tracer;
42    // end::customTracer[]
43
44    public Properties get(String hostname) {
45        systemClient.init(hostname, SYSTEM_PORT);
46        Properties properties = systemClient.getProperties();
47        return properties;
48    }
49
50    public void add(String hostname, Properties systemProps) {
51        Properties props = new Properties();
52        props.setProperty("os.name", systemProps.getProperty("os.name"));
53        props.setProperty("user.name", systemProps.getProperty("user.name"));
54
55        SystemData system = new SystemData(hostname, props);
56        // tag::Add[]
57        if (!systems.contains(system)) {
58            // tag::addSpan[]
59            Span span = tracer.buildSpan("add() Span").start();
60            // end::addSpan[]
61            // tag::Try[]
62            try (Scope childScope = tracer.activateSpan(span)) {
63                // tag::addToInvList[]
64                systems.add(system);
65                // end::addToInvList[]
66            } finally {
67                span.finish();
68            }
69            // end::Try[]
70        }
71        // end::Add[]
72    }
73
74    // tag::Traced[]
75    @Traced(operationName = "InventoryManager.list")
76    // end::Traced[]
77    // tag::list[]
78    public InventoryList list() {
79        return new InventoryList(systems);
80    }
81    // end::list[]
82
83    int clear() {
84        int propertiesClearedCount = systems.size();
85        systems.clear();
86        return propertiesClearedCount;
87    }
88}
89// end::InventoryManager[]

Injecting a custom Tracer object

The MicroProfile OpenTracing specification also makes the underlying OpenTracing Tracer instance available for use. You can access the configured Tracer by injecting it into a bean by using the @Inject annotation from the Contexts and Dependency Injections API.

Inject the Tracer object into the inventory/src/main/java/io/openliberty/guides/inventory/InventoryManager.java file. Then, use it to define a new child scope in the add() call.

Replace the InventoryManager class.
inventory/src/main/java/io/openliberty/guides/inventory/InventoryManager.java

InventoryManager.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.ArrayList;
15import java.util.Properties;
16import io.openliberty.guides.inventory.client.SystemClient;
17import io.openliberty.guides.inventory.model.InventoryList;
18import io.openliberty.guides.inventory.model.SystemData;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.inject.Inject;
21import java.util.List;
22import java.util.Collections;
23
24import org.eclipse.microprofile.config.inject.ConfigProperty;
25import org.eclipse.microprofile.opentracing.Traced;
26import io.opentracing.Scope;
27import io.opentracing.Tracer;
28import io.opentracing.Span;
29
30@ApplicationScoped
31// tag::InventoryManager[]
32public class InventoryManager {
33
34    @Inject
35    @ConfigProperty(name = "system.http.port")
36    int SYSTEM_PORT;
37
38    private List<SystemData> systems = Collections.synchronizedList(new ArrayList<>());
39    private SystemClient systemClient = new SystemClient();
40    // tag::customTracer[]
41    @Inject Tracer tracer;
42    // end::customTracer[]
43
44    public Properties get(String hostname) {
45        systemClient.init(hostname, SYSTEM_PORT);
46        Properties properties = systemClient.getProperties();
47        return properties;
48    }
49
50    public void add(String hostname, Properties systemProps) {
51        Properties props = new Properties();
52        props.setProperty("os.name", systemProps.getProperty("os.name"));
53        props.setProperty("user.name", systemProps.getProperty("user.name"));
54
55        SystemData system = new SystemData(hostname, props);
56        // tag::Add[]
57        if (!systems.contains(system)) {
58            // tag::addSpan[]
59            Span span = tracer.buildSpan("add() Span").start();
60            // end::addSpan[]
61            // tag::Try[]
62            try (Scope childScope = tracer.activateSpan(span)) {
63                // tag::addToInvList[]
64                systems.add(system);
65                // end::addToInvList[]
66            } finally {
67                span.finish();
68            }
69            // end::Try[]
70        }
71        // end::Add[]
72    }
73
74    // tag::Traced[]
75    @Traced(operationName = "InventoryManager.list")
76    // end::Traced[]
77    // tag::list[]
78    public InventoryList list() {
79        return new InventoryList(systems);
80    }
81    // end::list[]
82
83    int clear() {
84        int propertiesClearedCount = systems.size();
85        systems.clear();
86        return propertiesClearedCount;
87    }
88}
89// end::InventoryManager[]

This try block is called a try-with-resources statement, meaning that the childScope object is closed at the end of the statement. It’s good practice to define custom spans inside such statements. Otherwise, any exceptions that are thrown before the span closes will leak the active span.

Go to the http://localhost:9081/inventory/systems/localhost URL and check your Jaeger server at the http://localhost:16686 URL. If you have the Jaeger UI open from a previous step, refresh the page. Select the inventory traces and click the Find Traces button.

Verify that there are three spans from inventory and one span from system:

Trace with custom span


This simple example shows what you can do with the injected Tracer object. More configuration options are available to you, including setting a timestamp for when a span was created and destroyed. However, these options require an implementation of their own, which doesn’t come as a part of the Jaeger user feature that is provided. In a real-world scenario, implement all the OpenTracing interfaces that you deem necessary, which might include the SpanBuilder interface. You can use this interface for span creation and customization, including setting timestamps.

SystemResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.Properties;
15
16// CDI
17import jakarta.enterprise.context.RequestScoped;
18import jakarta.ws.rs.GET;
19// JAX-RS
20import jakarta.ws.rs.Path;
21import jakarta.ws.rs.Produces;
22import jakarta.ws.rs.core.MediaType;
23
24@RequestScoped
25@Path("properties")
26public class SystemResource {
27
28  @GET
29  @Produces(MediaType.APPLICATION_JSON)
30  // tag::Properties[]
31  public Properties getProperties() {
32    return System.getProperties();
33  }
34  // end::Properties[]
35}

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2023 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.inventory;
13
14import java.util.Properties;
15
16import org.eclipse.microprofile.opentracing.Traced;
17
18import io.openliberty.guides.inventory.model.InventoryList;
19import jakarta.enterprise.context.RequestScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.DELETE;
22import jakarta.ws.rs.GET;
23import jakarta.ws.rs.Path;
24import jakarta.ws.rs.PathParam;
25import jakarta.ws.rs.Produces;
26import jakarta.ws.rs.core.MediaType;
27import jakarta.ws.rs.core.Response;
28
29@RequestScoped
30@Path("/systems")
31// tag::InventoryResource[]
32public class InventoryResource {
33
34    @Inject InventoryManager manager;
35
36    @GET
37    @Path("/{hostname}")
38    @Produces(MediaType.APPLICATION_JSON)
39    // tag::getPropertiesForHost[]
40    public Response getPropertiesForHost(@PathParam("hostname") String hostname) {
41        Properties props = manager.get(hostname);
42        if (props == null) {
43            return Response.status(Response.Status.NOT_FOUND)
44                           .entity("{ \"error\" : \"Unknown hostname or the system "
45                           + "service may not be running on " + hostname + "\" }")
46                           .build();
47        }
48        manager.add(hostname, props);
49        return Response.ok(props).build();
50    }
51    // end::getPropertiesForHost[]
52
53    @GET
54    // tag::Traced-false[]
55    @Traced(false)
56    // end::Traced-false[]
57    @Produces(MediaType.APPLICATION_JSON)
58    // tag::listContents[]
59    public InventoryList listContents() {
60        return manager.list();
61    }
62    // end::listContents[]
63
64    @DELETE
65    @Produces(MediaType.APPLICATION_JSON)
66    public Response clearContents() {
67        int cleared = manager.clear();
68
69        if (cleared == 0) {
70            return Response.status(Response.Status.NOT_MODIFIED)
71                    .build();
72        }
73        return Response.status(Response.Status.OK)
74                .build();
75    }
76}
77// end::InventoryResource[]

Testing the services

No automated tests are provided to verify the correctness of the traces. Manually verify these traces by viewing them on the Jaeger server.

A few tests are included for you to test the basic functionality of the services. If a test failure occurs, then you might have introduced a bug into the code.

Running the tests

Since you started Open Liberty in dev mode, run the tests for the system and inventory services by pressing the enter/return key in the command-line sessions where you started the services.

When you are done checking out the services, exit dev mode by pressing CTRL+C in the shell sessions where you ran the system and inventory services, or by typing q and then pressing the enter/return key.

Finally, stop the Jaeger service that you started in the Additional prerequisites section.

Great work! You’re done!

You just used MicroProfile OpenTracing in Open Liberty to customize how and which traces are delivered to Jaeger.

Try out one of the related MicroProfile guides. These guides demonstrate more technologies that you can learn to expand on what you built in this guide.

Guide Attribution

Enabling distributed tracing in microservices with Jaeger by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star