Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<module>webserver</module>
<module>coherence</module>
<module>declarative</module>
<module>telemetry</module>
</modules>

<build>
Expand Down
59 changes: 59 additions & 0 deletions examples/telemetry/config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Helidon OpenTelemetry SE Example

This project implements a simple Hello World REST service using Helidon SE and using OpenTelemetry tracing prepared using Helidon configuration.

## Download and start a telemetry back-end
One easy way to see telemetry in action is to run a back-end server that can collect OpenTelemetry OTLP telemetry data and display it. This example Helidon service by default uses OpenTelemetry to transmit its data using OTLP.

One option is to download the [Jaeger back-end](https://www.jaegertracing.io/download/), install it, and run it, but most modern backends support OTLP. Later steps in this example use Jaeger as an example back-end.

## View the telemetry configuration
Look at the `src/main/resources/application.yaml` file. It contains configuration for OpenTelemetry. Note these settings under `telemetry`:
* `service` - Assigns the service name by which all telemetry from this application is identified. You will use this later when using the telemetry back-end UI to browse traces.
* `tracing.attributes` - Declares settings applied to all traces transmitted to the back-end.
* `processors` - Specifies a single span processor, `simple`, which emits each span as soon as it is ended. This makes sure that OpenTelemetry sends span data to the back-end as soon as possible.

**NOTE**: In production systems, you should use the default `batch` processor type (with its additional settings if you wish) for better network performance.

## Build and run

With JDK21
```bash
mvn package
java -jar target/helidon-examples-telemetry-config.jar
```

## Exercise the application

Basic:
```
curl -X GET http://localhost:8080/simple-greet
Hello World!
```


JSON:
```
curl -X GET http://localhost:8080/greet
{"message":"Hello World!"}

curl -X GET http://localhost:8080/greet/Joe
{"message":"Hello Joe!"}

curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting

curl -X GET http://localhost:8080/greet/Jose
{"message":"Hola Jose!"}
```

## Use the telemetry back-end to view tracing information
Use a browser to access the back-end UI and view the spans. For example, with Jaeger:
1. Access `http://localhost:16686`.
2. Expand the upper-left "Service" drop list and select "otel-config-example" and then click the "Find Traces" button. ![Service Selection](images/service-selection.png "Service Selection")

Recall that the configuration assigns "otel-config-example" as the `telemetry.service`, so that is the service name the back-end displays.
3. The UI displays separate traces for each of the requests you made to the Helidon service.
4. Click on one of the traces.
5. The back-end shows two or more spans, depending on which trace you clicked. ![Example GET trace](images/get-trace.png "GET trace")
6. Click on any of the spans. ![Example GET span](images/get-span.png "GET span") Note that the "Process" tags include values for `x` and `y` from the `attributes` settings in the `application.yaml` config file.

Binary file added examples/telemetry/config/images/get-span.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/telemetry/config/images/get-trace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions examples/telemetry/config/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) 2025 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.

-->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-se</artifactId>
<version>4.3.0-SNAPSHOT</version>
<relativePath/>
</parent>
<groupId>io.helidon.examples.telemetry</groupId>
<artifactId>helidon-examples-telemetry-config</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Helidon Examples Telemetry Configuration</name>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add <name>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

<properties>
<mainClass>io.helidon.examples.telemetry.otelconfig.Main</mainClass>
</properties>

<dependencies>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.telemetry</groupId>
<artifactId>helidon-telemetry-opentelemetry-config</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webserver.observe</groupId>
<artifactId>helidon-webserver-observe-tracing</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonp</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.tracing.providers</groupId>
<artifactId>helidon-tracing-providers-opentelemetry</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver.observe</groupId>
<artifactId>helidon-webserver-observe-telemetry-tracing</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.helidon.logging</groupId>
<artifactId>helidon-logging-jul</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.helidon.telemetry.testing</groupId>
<artifactId>helidon-telemetry-testing-tracing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver.testing.junit5</groupId>
<artifactId>helidon-webserver-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2025 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.
*/

package io.helidon.examples.telemetry.otelconfig;

import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;

import io.helidon.config.Config;
import io.helidon.http.Status;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;

import jakarta.json.Json;
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;

/**
* A simple service to greet you. Examples:
* <p>
* Get default greeting message:
* {@code curl -X GET http://localhost:8080/greet}
* <p>
* Get greeting message for Joe:
* {@code curl -X GET http://localhost:8080/greet/Joe}
* <p>
* Change greeting
* {@code curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting}
* <p>
* The message is returned as a JSON object
*/
class GreetService implements HttpService {


private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());

/**
* The config value for the key {@code greeting}.
*/
private final AtomicReference<String> greeting = new AtomicReference<>();

GreetService() {
this(Config.global().get("app"));
}

GreetService(Config appConfig) {
greeting.set(appConfig.get("greeting").asString().orElse("Ciao"));
}

/**
* A service registers itself by updating the routing rules.
*
* @param rules the routing rules.
*/
@Override
public void routing(HttpRules rules) {
rules
.get("/", this::getDefaultMessageHandler)
.get("/{name}", this::getMessageHandler)
.put("/greeting", this::updateGreetingHandler);
}

/**
* Return a worldly greeting message.
*
* @param request the server request
* @param response the server response
*/
private void getDefaultMessageHandler(ServerRequest request,
ServerResponse response) {
sendResponse(response, "World");
}

/**
* Return a greeting message using the name that was provided.
*
* @param request the server request
* @param response the server response
*/
private void getMessageHandler(ServerRequest request,
ServerResponse response) {
String name = request.path().pathParameters().get("name");
sendResponse(response, name);
}

private void sendResponse(ServerResponse response, String name) {
String msg = String.format("%s %s!", greeting.get(), name);

JsonObject returnObject = JSON.createObjectBuilder()
.add("message", msg)
.build();
response.send(returnObject);
}

private void updateGreetingFromJson(JsonObject jo, ServerResponse response) {

if (!jo.containsKey("greeting")) {
JsonObject jsonErrorObject = JSON.createObjectBuilder()
.add("error", "No greeting provided")
.build();
response.status(Status.BAD_REQUEST_400)
.send(jsonErrorObject);
return;
}

greeting.set(jo.getString("greeting"));
response.status(Status.NO_CONTENT_204).send();
}

/**
* Set the greeting to use in future messages.
*
* @param request the server request
* @param response the server response
*/
private void updateGreetingHandler(ServerRequest request,
ServerResponse response) {
updateGreetingFromJson(request.content().as(JsonObject.class), response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2025 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.
*/

package io.helidon.examples.telemetry.otelconfig;

import io.helidon.config.Config;
import io.helidon.logging.common.LogConfig;
import io.helidon.service.registry.Services;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;

/**
* The application main class.
*/
public class Main {

/**
* Cannot be instantiated.
*/
private Main() {
}

/**
* Application main entry point.
* @param args command line arguments.
*/
public static void main(String[] args) {

// load logging configuration
LogConfig.configureRuntime();

// initialize config from default configuration
Config config = Services.get(Config.class);

WebServer server = WebServer.builder()
.config(config.get("server"))
.routing(Main::routing)
.build()
.start();

System.out.println("WEB server is up! http://localhost:" + server.port() + "/simple-greet");

}

/**
* Updates HTTP Routing.
*/
static void routing(HttpRouting.Builder routing) {
routing
.register("/greet", new GreetService())
.get("/simple-greet", (req, res) -> res.send("Hello World!"));
}
}
Loading