All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.guides.tracing.adoc Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
///////////////////////////////////////////////////////////////////////////////

    Copyright (c) 2019, 2024 Oracle and/or its affiliates.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

///////////////////////////////////////////////////////////////////////////////

= Helidon SE Tracing Guide
:description: Helidon tracing
:keywords: helidon, tracing, microprofile, guide
:toc:
:rootdir: {docdir}/../..
:imagesdir: {rootdir}/images

include::{rootdir}/includes/se.adoc[]

This guide describes how to create a sample Helidon SE project
that can be used to run some basic examples using tracing with a Helidon SE application.

== What You Need

For this 30 minute tutorial, you will need the following:

include::{rootdir}/includes/prerequisites.adoc[tag=prerequisites]

== Introduction

Distributed tracing is a critical feature of microservice-based applications, since it traces workflow both
within a service and across multiple services.  This provides insight to sequence and timing data for specific blocks of work,
which helps you identify performance and operational issues. Helidon includes support for distributed tracing through its own API, backed by either
through the https://opentelemetry.io/docs/instrumentation/js/api/tracing/[OpenTelemetry API], or by
https://opentracing.io[OpenTracing API].

=== Tracing Concepts

This section explains a few concepts that you need to understand before you get started with tracing.
In the context of this document, a service is synonymous with an application.
A _span_ is the basic unit of work done within a single service, on a single host.
Every span has a name, starting timestamp, and duration.  For example, the work done by a REST endpoint is a span.
A span is associated to a single service, but its descendants can belong to different services and hosts.
A _trace_ contains a collection of spans from one or more services, running on one or more hosts. For example,
if you trace a service endpoint that calls another service, then the trace would contain spans from both services.
Within a trace, spans are organized as a directed acyclic graph (DAG) and
can belong to multiple services, running on multiple hosts.
Spans are automatically created by Helidon as needed during execution of the REST request.

== Getting Started with Tracing

The examples in this guide demonstrate how to integrate tracing with Helidon, how to view traces, how to trace
across multiple services, and how to integrate tracing with Kubernetes. All examples use Jaeger and traces
will be viewed using the Jaeger UI.

=== Create a Sample Helidon SE Project

Use the Helidon SE Maven archetype to create a simple project that can be used for the examples in this guide.

[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion={helidon-version} \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se
----

[source,bash]
.The project will be built and run from the `helidon-quickstart-se` directory:
----
cd helidon-quickstart-se
----

=== Set up Jaeger

First, you need to run the Jaeger tracer.  Helidon will communicate with this tracer at runtime.

[source,bash]
.Run Jaeger within a docker container, then check the Jaeger server working:
----
docker run -d --name jaeger \ # <1>
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 14250:14250 \
  -p 14268:14268 \
  -p 14269:14269 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.50

----
<1> Run the Jaeger docker image.

=== Enable Tracing in the Helidon Application

Update the `pom.xml` file and add the following Jaeger dependency to the ``
section (*not* ``). This will enable Helidon to use Jaeger at the
default host and port, `localhost:14250`.

[source,xml]
.Add the following dependencies to `pom.xml`:
----

     
         io.helidon.tracing
         helidon-tracing   
     
     
         io.helidon.webserver.observe
         helidon-webserver-observe-tracing 
         runtime
     
     
         io.helidon.tracing.providers
         helidon-tracing-providers-jaeger  
         runtime
     

----
<1> Helidon Tracing dependencies.
<2> Observability features for tracing.
<3> Jaeger tracing provider.

All spans sent by Helidon to Jaeger need to be associated with a service, assigned by the `tracing.service` setting in the example below.

[source,bash]
.Add the following lines to `src/main/resources/application.yaml`:
----
tracing:
  service: helidon-se-1
  protocol: http
  port: 14250
  path: /api/traces
  tags:
    env: development
  enabled: true
  sampler-type: "const"
  sampler-param: 1
  log-spans: true
  propagation: b3
----

=== View Automatic Tracing of REST Endpoints
Tracing is part of Helidon's observability support. By default, Helidon discovers any observability feature on the classpath and activates it automatically. In particular for tracing, Helidon adds a trace each time a client accesses a service endpoint.
You can see these traces using the Jaeger UI once you build, run, and access your application without changing your application's Java code.

==== Build and Access QuickStart

[source,bash]
.Build and run the application
----
mvn clean package
java -jar target/helidon-quickstart-se.jar
----

[source,bash]
.Access the application
----
curl http://localhost:8080/greet
----

=== Viewing Traces Using the Jaeger UI

Jaeger provides a web-based UI at http://localhost:16686 where you can see a visual
representation of the traces and spans within them.

. From the `Service` drop list select `helidon-se-1`. This name corresponds to the `tracing.service` setting you assigned in the `application.yaml` config file.
. Click on the UI Find Traces button. Notice that you can change the look-back time to restrict the trace list.
You will see a trace for each `curl` command you ran to access the application.

.List of traces
image::guides/tracing_se_trace_list.png[Trace List,role="fit"]

Click on a trace to see the trace detail page (shown below) which shows the spans within the trace. You can clearly
see the root span (`HTTP Request`) and the single child span (`content-write`) along with the time over which each span was active.

.Trace detail page
image::guides/tracing_se_first_trace.png[Trace Detail,role="fit"]

You can examine span details by clicking on the span row.  Refer to the image below which shows the span details including timing information.
You can see times for each space relative to the root span.

.Span detail page
image::guides/tracing_se_span_detail.png[Span Details,role="fit"]

=== Adding a Custom Span
Your application can use the Helidon tracing API to create custom spans.
The following code replaces the generated `getDefaultMessageHandler` method to add a custom span around the code which prepares the default greeting response. The new custom span's parent span is set to the one which Helidon automatically creates for the REST endpoint.

[source,java]
.Update the `GreetService` class, replacing the `getDefaultMessageHandler` method:
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_1, indent=0]
----
<1> Create a new `Span` using the global tracer.
<2> Set the parent of the new span to the span from the `Request` if available.
<3> Start the span.
<4> Make the new span the current span, returning a `Scope` which is auto-closed.
<5> End the span normally after the response is sent.
<6> End the span with an exception if one was thrown.


[source,bash]
.Build the application and run it:
----
mvn package
java -jar target/helidon-quickstart-se.jar
----

[source,bash]
.Run the `curl` command in a new terminal window and check the response:
----
curl http://localhost:8080/greet
----

[source,json]
.JSON response:
----
{
  "message": "Hello World!"
}
----

Return to the main Jaeger UI screen and click Find Traces again.
The new display contains an additional trace, displayed first, for the most recent `curl` you ran.

.Expanded trace list
image::guides/tracing_se_second_trace_list.png[Expanded trace list,role="fit"]

Notice that the top trace has three spans, not two as with the earlier trace. Click on the trace to see the trace details.

.Trace details with custom span
image::guides/tracing_se_expanded_trace.png[Trace details with custom span,role="fit"]

Note the row for `mychildSpan`--the custom span created by the added code.

=== Using Tracing Across Services

Helidon automatically traces across services if the services use the same tracer, for example, the same instance of Jaeger.
This means a single trace can include spans from multiple services and hosts.  Helidon uses a `SpanContext` to
propagate tracing information across process boundaries.  When you make client API calls, Helidon will
internally call OpenTelemetry APIs or OpenTracing APIs to propagate the `SpanContext`. There is nothing you need to do in your application to make this work.

To demonstrate distributed tracing, create a second project where the server listens to on port 8081.
Create a new directory to hold this new project, then do the following steps, similar to
what you did at the start of this guide:

=== Create the Second Service

[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn -U archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion={helidon-version} \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se-2 \
    -Dpackage=io.helidon.examples.quickstart.se
----

[source,bash]
.The project is in the `helidon-quickstart-se-2` directory:
----
cd helidon-quickstart-se-2
----

[source,xml]
.Add the following dependencies to `pom.xml`:
----

     
         io.helidon.tracing
         helidon-tracing   
     
     
         io.helidon.webserver.observe
         helidon-webserver-observe-tracing 
         runtime
     
     
         io.helidon.tracing.providers
         helidon-tracing-providers-jaeger  
         runtime
     

----
<1> Helidon Tracing API.
<2> Observability features for tracing.
<3> Jaeger tracing provider.

[source,bash]
.Replace `src/main/resources/application.yaml` with the following:
----
app:
  greeting: "Hello From SE-2"

tracing:
  service: helidon-se-2
  protocol: http
  port: 14250
  path: /api/traces
  tags:
    env: development
  enabled: true
  sampler-type: "const"
  sampler-param: 1
  log-spans: true
  propagation: b3

server:
  port: 8081
  host: 0.0.0.0
----

NOTE: The settings above are for development and experimental purposes only. For production environment, please see the
xref:{rootdir}/se/tracing.adoc[Tracing documentation].

[source,java]
.Update the `GreetService` class. Replace the `getDefaultMessageHandler` method:
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_2, indent=0]
----

Build the application, skipping unit tests; the unit tests check for the default greeting response which is now different in the updated config. Then run the application.
[source,bash]
.Build and run:
----
mvn package -DskipTests=true
java -jar target/helidon-quickstart-se-2.jar
----

[source,bash]
.Run the curl command in a new terminal window (*notice the port is 8081*) :
----
curl http://localhost:8081/greet
----

[source,json]
.JSON response:
----
{
  "message": "Hello From SE-2 World!"
}
----

=== Modify the First Service

Once you have validated that the second service is running correctly, you need to modify the original application to
call it.

[source,xml]
.Add the following dependencies to `pom.xml`:
----

    
        io.helidon.webclient
        helidon-webclient
    
    
        io.helidon.webclient
        helidon-webclient-api
    
    
        io.helidon.webclient
        helidon-webclient-tracing
    
    
        io.helidon.webclient
        helidon-webclient-http1
        runtime
    

----
Make the following changes to the `GreetService` class.

1. Add a `WebClient` field.
+
[source,java]
.Add a private instance field (before the constructors)
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_3, indent=0]
----

2. Add code to initialize the `WebClient` field.
+
[source,java]
.Add the following code to the `GreetService(Config)` constructor
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_4, indent=0]
----
3. Add a routing rule for the new endpoint `/outbound`.
+
[source,java]
.Add the following line in the `routing` method as the first `.get` invocation in the method
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_5, indent=0]
----
4. Add a method to handle requests to `/outbound`.
+
[source,java]
.Add the following method
----
include::{sourcedir}/se/guides/TracingSnippets.java[tag=snippet_6, indent=0]
----

Stop the application if it is still running, rebuild and run it, then invoke the endpoint and check the response.
[source,bash]
.Build, run, and access the application
----
mvn clean package
java -jar target/helidon-quickstart-se.jar
curl -i http://localhost:8080/greet/outbound # <1>
----
<1> The request goes to the service on `8080`, which then invokes the service at `8081` to get the greeting.

[source,hocon]
.JSON response:
----
{
  "message": "Hello From SE-2 World!" // <1>
}
----
<1> Notice the greeting came from the second service.

Refresh the Jaeger UI trace listing page and notice that there is a trace across two services. Click on that trace to see its details.

.Tracing across multiple services detail view
image::guides/tracing_se_second_expanded_trace.png[Traces,role="fit"]

Note several things about the display:

1. The top-level span `helidon-se-1 HTTP Request` includes all the work across _both_ services.
2. `helidon-se-1 outboundMessageHandler` is the custom span you added to the first service `/outbound` endpoint code.
3. `helidon-se-1 GET-http://localhost:8080/greet` captures the work the `WebClient` is doing in sending a request to the second service. Helidon adds these spans automatically to each outbound `WebClient` request.
4. `helidon-se-2 HTTP Request` represents the arrival of the request sent by the first service's `WebClient` at the second service's `/greet` endpoint.
5. `helidon-se-2 getDefaultMessageHandler` is the custom span you added to the second service `/greet` endpoint code.


You can now stop your second service, it is no longer used in this guide.

== Integration with Kubernetes

The following example demonstrates how to use Jaeger from a Helidon application running in Kubernetes.

[source,yaml]
.Replace the tracing configuration in `resources/application.yaml` with the following:
----
tracing: # <1>
  service: helidon-se-1
  host: jaeger
----
<1> Helidon service `helidon-se-1` will connect to the Jaeger server at host name `jaeger`.

[source,bash]
.Stop the application and build the docker image for your application:
----
docker build -t helidon-tracing-se .
----

=== Deploy Jaeger into Kubernetes

[source,yaml]
.Create the Kubernetes YAML specification, named `jaeger.yaml`, with the following contents:
----
apiVersion: v1
kind: Service
metadata:
  name: jaeger
spec:
  ports:
    - port: 16686
      protocol: TCP
  selector:
    app: jaeger
---
kind: Pod
apiVersion: v1
metadata:
  name: jaeger
  labels:
    app: jaeger
spec:
  containers:
    - name: jaeger
      image: jaegertracing/all-in-one
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 16686
----

[source,bash]
.Create the Jaeger pod and ClusterIP service:
----
kubectl apply -f ./jaeger.yaml
----

[source,bash]
.Create a Jaeger external server to view the UI and expose it on port 9142:
----
kubectl expose pod  jaeger --name=jaeger-external --port=16687 --target-port=16686 --type=LoadBalancer
----

Navigate to http://localhost:16687/jaeger to validate that you can access Jaeger running in Kubernetes. It may
take a few seconds before it is ready.


=== Deploy Your Helidon Application into Kubernetes

[source,yaml]
.Create the Kubernetes YAML specification, named `tracing.yaml`, with the following contents:
----
kind: Service
apiVersion: v1
metadata:
  name: helidon-tracing # <1>
  labels:
    app: helidon-tracing
spec:
  type: NodePort
  selector:
    app: helidon-tracing
  ports:
    - port: 8080
      targetPort: 8080
      name: http
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: helidon-tracing
spec:
  replicas: 1 # <2>
  selector:
    matchLabels:
      app: helidon-tracing
  template:
    metadata:
      labels:
        app: helidon-tracing
        version: v1
    spec:
      containers:
        - name: helidon-tracing
          image: helidon-tracing-se
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
----
<1> A service of type `NodePort` that serves the default routes on port `8080`.
<2> A deployment with one replica of a pod.

[source,bash]
.Create and deploy the application into Kubernetes:
----
kubectl apply -f ./tracing.yaml
----

=== Access Your Application and the Jaeger Trace

[source,bash]
.Get the application service information:
----
kubectl get service/helidon-tracing
----

[source,bash]
----
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
helidon-tracing   NodePort   10.99.159.2           8080:31143/TCP   8s # <1>
----
<1> A service of type `NodePort` that serves the default routes on port `31143`.

[source,bash]
.Verify the tracing endpoint using port `31143`, your port will likely be different:
----
curl http://localhost:31143/greet
----

[source,json]
.JSON response:
----
{
  "message": "Hello World!"
}
----

Access the Jaeger UI at http://localhost:9412/jaeger and click on the refresh icon to see the trace that was just created.


=== Cleanup

You can now delete the Kubernetes resources just created during this example.

[source,bash]
.Delete the Kubernetes resources:
----
kubectl delete -f ./jaeger.yaml
kubectl delete -f ./tracing.yaml
kubectl delete service jaeger-external
docker rm -f jaeger
----

include::{rootdir}/includes/tracing/common-callbacks.adoc[tags=defs;detailed]

== Summary

This guide has demonstrated how to use the Helidon SE tracing feature with Jaeger. You have learned to do the following:

- Enable tracing within a service
- Use tracing with JAX-RS
- Use the Jaeger REST API and UI
- Use tracing across multiple services
- Integrate tracing with Kubernetes


Refer to the following references for additional information:

- link:{microprofile-tracing-spec-url}[MicroProfile OpenTracing specification]
- link:{microprofile-tracing-javadoc-url}[MicroProfile OpenTracing Javadoc]
* link:https://opentelemetry.io/docs/instrumentation/js/api/tracing/[OpenTelemetry API]
- link:{javadoc-base-url}/index.html?overview-summary.html[Helidon Javadoc]





© 2015 - 2025 Weber Informatics LLC | Privacy Policy