Step-by-Step Guide to Integrating Spring Boot with OpenTelemetry and GCP

This guide provides a step-by-step approach to integrating OpenTelemetry with Spring Boot and deploying it to Google Cloud Platform (GCP). The setup includes configuring the necessary dependencies, setting up OpenTelemetry Collector, creating a Docker environment, and deploying the application to GCP Cloud Run.

Important Notice: This article is for reference only. Please refer to the latest guide: Step-by-Step Guide to Integrating Spring Boot and OpenTelemetry with Micrometer on GCP for Distributed Tracing for up-to-date information and recommended practices.

It’s important to note that Micrometer is now the recommended framework by Spring for metrics and tracing in Spring Boot applications. The latest Spring Boot versions have integrated Micrometer as the default metrics and tracing facade, which can be easily configured to work with OpenTelemetry. For the most current approach to implementing observability in Spring Boot applications using Micrometer and OpenTelemetry, please refer to the guide linked above.

cover

Prerequisites

Before you start, ensure you have the following installed:

  • Java 21
  • Docker
  • Gradle
  • GCP account with Cloud Run and Artifact Registry enabled

Setting Up Spring Boot with OpenTelemetry

Configure build.gradle

To start integrating OpenTelemetry with your Spring Boot application, you need to add the necessary dependencies to your build.gradle file. This section will guide you through the configuration process.

Define Dependencies

Add the required dependencies to your build.gradle file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.testcontainers:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// Manually added dependencies
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter'
implementation 'io.micrometer:context-propagation:latest.integration' // For context propagation in asynchronous tasks
}

Here’s a brief explanation of the manually added dependencies:

  • spring-boot-starter-aop: Supports Aspect-Oriented Programming (AOP), which is necessary for using Micrometer’s @NewSpan annotation. This annotation helps in tracing method calls. By including AOP, you enable the ability to create spans around specific methods, allowing for detailed monitoring and tracing of your application’s behavior.
  • micrometer-tracing-bridge-otel: Bridges Micrometer tracing to OpenTelemetry. This dependency allows for tracing using the OpenTelemetry instrumentation. This bridge facilitates the propagation of trace context and spans created by Micrometer into OpenTelemetry’s tracing system. It ensures compatibility between the two systems and enables seamless monitoring and observability.
  • opentelemetry-spring-boot-starter: Provides auto-configuration for OpenTelemetry in Spring Boot applications. This starter simplifies the process of instrumenting your Spring Boot application with OpenTelemetry. The starter includes built-in instrumentation for many Spring Boot features, which helps you get started quickly with minimal configuration. Refer to the OpenTelemetry documentation for more details.
  • context-propagation:latest.integration: Handles context propagation in asynchronous tasks. This is essential for maintaining trace context across asynchronous calls and tasks. This library ensures that trace and context information is correctly propagated across threads and asynchronous operations, which is crucial for accurate tracing and monitoring in distributed systems. Refer to the Micrometer Context Propagation documentation for more information.

Dependency Management

Import the OpenTelemetry instrumentation BOM for dependency management:

1
2
3
4
5
dependencyManagement {
imports {
mavenBom("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.6.0")
}
}

Using the BOM (Bill of Materials) ensures that all OpenTelemetry dependencies are compatible with each other. The BOM helps in managing the versions of the dependencies and their transitive dependencies to ensure they are compatible. This approach simplifies dependency management and reduces conflicts.

By following these steps, you have successfully configured your build.gradle file to integrate OpenTelemetry with your Spring Boot application. Next, we’ll look into setting up the main application class and configuring asynchronous context propagation.

Configuring Application Settings

To ensure proper integration of OpenTelemetry and effective tracing and monitoring, you need to configure your Spring Boot application settings. Add the following settings to your application.yml or application.properties file.

Configuration Settings

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
application:
name: otel-demo
docker:
compose:
enabled: false

management:
endpoint:
health:
probes:
enabled: true
tracing:
sampling:
probability: 1.0
observations:
annotations:
enabled: true # https://docs.spring.io/spring-boot/reference/actuator/observability.html#actuator.observability.annotations
  • management.tracing.sampling.probability: Sets the sampling probability for tracing. A value of 1.0 means all requests will be traced. This setting is useful for ensuring comprehensive trace data during development and testing.
  • management.observations.annotations.enabled: Enables the use of annotations for observability. This setting allows you to use annotations like @NewSpan to define spans for tracing specific methods. For more details, refer to the Spring Boot Actuator Observability documentation.

Set Up the Main Application Class

Create the main application class OtelApplication.java:

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
public class OtelApplication {

public static void main(String[] args) {
SpringApplication.run(OtelApplication.class, args);
}
}
  • @EnableAsync: This annotation enables Spring’s asynchronous method execution capability. When applied, it allows methods annotated with @Async to run in a separate thread, thus enabling concurrent execution. This is particularly useful for tasks that can be performed in parallel, improving the application’s responsiveness and performance.

By including @EnableAsync, you enable asynchronous processing within your application, which is critical for handling background tasks without blocking the main execution thread. This is especially useful in the context of tracing and monitoring, where you may want to offload certain tasks to be processed asynchronously to avoid impacting the main application flow.

Configure Asynchronous Context Propagation

Create the AsyncTraceContextConfiguration.java to handle context propagation for asynchronous tasks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.otel;

import java.util.concurrent.Executor;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import io.micrometer.context.ContextExecutorService;
import io.micrometer.context.ContextRegistry;
import io.micrometer.context.ContextSnapshotFactory;
import lombok.RequiredArgsConstructor;

@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
public class AsyncTraceContextConfiguration implements AsyncConfigurer {

private final ThreadPoolTaskExecutor taskExecutor;

@Override
public Executor getAsyncExecutor() {
ContextSnapshotFactory contextSnapshotFactory = ContextSnapshotFactory.builder()
.contextRegistry(ContextRegistry.getInstance())
.build();
return ContextExecutorService.wrap(
taskExecutor.getThreadPoolExecutor(),
contextSnapshotFactory::captureAll
);
}
}

This class configures context propagation for asynchronous tasks in a Spring Boot application. It ensures that trace information and other context data are carried over when tasks are executed asynchronously. Here’s a brief breakdown:

  • Task Executor Injection: Injects a ThreadPoolTaskExecutor to manage thread pools for asynchronous tasks.
  • ContextSnapshotFactory: Creates snapshots of the current context, including trace data.
  • ContextExecutorService.wrap: Wraps the ThreadPoolTaskExecutor to capture and propagate context information across asynchronous boundaries.

Purpose and Benefits

  • Asynchronous Execution: Enables handling of asynchronous method execution by providing a custom Executor.
  • Context Propagation: Ensures that context information, such as trace data, is propagated across asynchronous tasks, maintaining the integrity of trace data for distributed tracing and monitoring.

By following this configuration, you enable asynchronous context propagation in your Spring Boot application, ensuring that trace and context information is correctly managed and propagated. This is essential for accurate and comprehensive distributed tracing and monitoring.

Create a Sample Controller and Service

This section provides sample code for a controller and a service to demonstrate how to use tracing annotations and asynchronous execution in a Spring Boot application with OpenTelemetry.

Purpose and Design of the Sample Code

The sample code includes OrderController and OrderService classes. These classes are designed to illustrate how to:

  1. Use the @NewSpan annotation to create new tracing spans.
  2. Handle asynchronous execution using the @Async annotation.
  3. Simulate processing work to show how tracing captures the execution flow.

OrderController.java

This class defines a REST controller for handling order-related requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.otel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.micrometer.tracing.annotation.NewSpan;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

private static final Logger log = LoggerFactory.getLogger(OrderController.class);
private final OrderService orderService;

public OrderController(OrderService orderService) {
this.orderService = orderService;
}

@PostMapping
@NewSpan("create-order")
public String createOrder(@RequestParam String customerId) {
log.info("Received order creation request for customer: {}", customerId);
String orderId = orderService.createOrder(customerId);
orderService.processOrderAsync(orderId);
return "Order created and processing started for ID: " + orderId;
}

@GetMapping("/{orderId}")
@NewSpan("get-order-status")
public String getOrderStatus(@PathVariable String orderId) {
log.info("Checking status for order ID: {}", orderId);
return orderService.getOrderStatus(orderId);
}
}
  • @NewSpan: Annotates methods to create new tracing spans, capturing the execution of these methods as separate trace segments.

OrderService.java

This class defines the service responsible for order operations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.example.otel;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import io.micrometer.tracing.annotation.NewSpan;

@Service
public class OrderService {

private static final Logger log = LoggerFactory.getLogger(OrderService

.class);

@NewSpan("create-order-service")
public String createOrder(String customerId) {
log.info("Creating order for customer: {}", customerId);
simulateWork(500);
return "ORD-" + System.currentTimeMillis();
}

@NewSpan("get-order-status-service")
public String getOrderStatus(String orderId) {
log.info("Checking status for order: {}", orderId);
simulateWork(200);
return "Status for order " + orderId + ": Processing";
}

@Async
@NewSpan("process-order-async")
public void processOrderAsync(String orderId) {
log.info("Start processing order asynchronously: {}", orderId);
simulateWork(2000);
log.info("Finished processing order: {}", orderId);
}

private void simulateWork(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Work simulation interrupted", e);
}
}
}
  • @NewSpan: Creates new spans for the createOrder, getOrderStatus, and processOrderAsync methods.
  • @Async: Marks the processOrderAsync method to be executed asynchronously, allowing the method to run in a separate thread.

Design Purpose

  • Traceability: The @NewSpan annotations ensure that each method’s execution is captured in a separate tracing span. This helps in tracking the flow of requests through different parts of the application.
  • Asynchronous Processing: The @Async annotation demonstrates how to offload work to a separate thread, which is essential for non-blocking operations. This is particularly useful for long-running tasks that should not block the main thread.
  • Logging: The use of logging at key points in the methods helps in debugging and understanding the flow of execution during the tracing process.

By following this design, you can effectively trace and monitor asynchronous operations in your Spring Boot application, ensuring better observability and performance analysis.

Setting Up Docker for Local Development

Create a compose.yaml File

Set up OpenTelemetry Collector and Jaeger using Docker Compose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
services:
otel-collector:
container_name: otel-collector
image: otel/opentelemetry-collector-contrib:latest
restart: always
command:
- --config=/etc/otelcol-contrib/otel-collector.yml
volumes:
- ./dev-resources/collector/otel-collector.yml:/etc/otelcol-contrib/otel-collector.yml
ports:
- "1888:1888"
- "8888:8888"
- "8889:8889"
- "13133:13133"
- "4317:4317"
- "4318:4318"
- "55679:55679"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:13133"]
interval: 10s
timeout: 5s
retries: 5
depends_on:
- jaeger
networks:
- otel-network

jaeger:
container_name: jaeger
image: jaegertracing/all-in-one:latest
restart: always
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "14268:14268"
- "14250:14250"
- "9411:9411"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:16686"]
interval: 10s
timeout: 5s
retries: 5
networks:
- otel-network

networks:
otel-network:
driver: bridge

Create otel-collector.yml for OpenTelemetry Collector Configuration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318

processors:
batch:

exporters:
otlp:
endpoint: "jaeger:4317"
tls:
insecure: true

extensions:
health_check:
endpoint: "0.0.0.0:13133"
pprof:
endpoint: "0.0.0.0:1888"
zpages:
endpoint: "0.0.0.0:55679"

service:
extensions: [health_check, pprof, zpages]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]

Starting Services in Local Development

Use docker compose up -d to start the services defined in compose.yaml. Once the services are up, you can open http://localhost:16686 to view the Jaeger UI.

Next, start your Spring Boot application and perform the following test operations.

Create an Order

1
curl -X POST "http://localhost:8080/api/orders?customerId=CUST1234"

Output:

1
Order created and processing started for ID: ORD-1722481291139

Retrieve an Order

1
curl "http://localhost:8080/api/orders/ORD-1722481291139"

Output:

1
Status for order ORD-1722481291139: Processing

After completing these operations, return to the Jaeger UI to view the trace information. You should see two new traces: one for otel-demo: http post /api/orders and another for otel-demo: http get /api/orders/{orderId}.

Jaeger UI Overview

Jaeger UI - Overview

This image displays the Jaeger UI search interface. You can see two traces under the otel-demo service: one for the POST request to create an order and one for the GET request to retrieve the order. These traces were generated by the CURL commands.

Jaeger UI Trace Details

Jaeger UI - Trace Details

This image shows the detailed trace information for the http post /api/orders request. This trace includes multiple spans, such as create-order and process-order-async, indicating that asynchronous processing was correctly traced. Each span shows the duration and order of execution, providing a clear picture of the request’s flow.

  • Trace Overview: In Jaeger UI, you can see an overview of the traces for the otel-demo service. Each trace represents a complete request-response cycle.
  • Span Details: By clicking on a trace, you can view detailed information about each step (span) within the trace, including execution time and sequence. These spans help you understand the application’s execution flow and verify that asynchronous operations are working as expected.
  • Asynchronous Processing: The create-order API uses asynchronous processing for order handling. This is evident from the process-order-async span in the trace details. By examining these spans, we can ensure that the asynchronous operations are being traced correctly, providing insight into the application’s behavior during these processes.

Preparing OpenTelemetry Collector for GCP

This section details the steps to prepare and configure the OpenTelemetry Collector for deployment on Google Cloud Platform (GCP).

Setting Up GCP Permissions

Before deploying your application to Google Cloud Platform (GCP), you need to set up the necessary permissions. The following steps will guide you through configuring GCP permissions.

CLI Cleanup

Revoke all existing authorizations:

1
2
gcloud auth revoke --all
gcloud auth application-default revoke

Update the gcloud CLI:

1
gcloud components update -q

Login

Log in using the following command:

1
gcloud auth login

Set your project:

1
2
export PROJECT_ID=your-project-id
gcloud config set project $PROJECT_ID

Verify that the authorization and project settings are correct:

1
2
gcloud auth list
gcloud config list project

Create Service Account for Cloud Run Runtime

Create the service account:

1
2
3
4
5
6
export SERVICE_ACCOUNT_ID_RUNTIME=your-service-account-id

gcloud iam service-accounts create $SERVICE_ACCOUNT_ID_RUNTIME \
--description="Description of the service account" \
--display-name="Cloud Run Service Account" \
--project=$PROJECT_ID

Bind Roles to the Service Account

Bind the necessary roles to the service account:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_ID_RUNTIME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/monitoring.metricWriter" \
--condition="None"

gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_ID_RUNTIME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/cloudtrace.agent" \
--condition="None"

gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$SERVICE_ACCOUNT_ID_RUNTIME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/logging.logWriter" \
--condition="None"

Create Artifact Registry Repository

Create a Docker repository:

1
2
3
4
5
export REGION=asia-east1
export REPOSITORY_NAME=your-repository-name

gcloud artifacts repositories create $REPOSITORY_NAME --repository-format=docker \
--location=$REGION --description="Description of the repository"

These steps will help you set up the necessary GCP permissions, ensuring that your application has the required access and permissions when deployed to Google Cloud Platform.

Create Dockerfile for OpenTelemetry Collector

Create a Dockerfile for the OpenTelemetry Collector:

1
2
3
4
5
FROM otel/opentelemetry-collector-contrib:latest

WORKDIR /etc/otelcol-contrib

COPY otel-config.yaml /etc/otelcol-contrib/config.yaml

Create otel-config.yaml for GCP Configuration

Create the configuration file otel-config.yaml with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318

exporters:
googlecloud:
log:
default_log_name: opentelemetry.io/collector-exported-log

extensions:
health_check:
pprof:
zpages:

processors:
memory_limiter:
check_interval: 1s
limit_percentage: 65
spike_limit_percentage: 20
batch:
filter/drop_actuator:
traces:
span:
- attributes["http.route"] == "/actuator/prometheus"
- attributes["http.route"] == "/actuator/health"
resourcedetection:
detectors: [gcp]
timeout: 10s

service:
extensions: [health_check, pprof, zpages]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [googlecloud]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [googlecloud]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [googlecloud]

Build and Push Docker Images

Build and push the Docker images to Google Artifact Registry:

1
2
3
4
5
6
7
8
9
export PROJECT_ID=your-gcp-project-id
export REGION=your-region
export REPOSITORY_NAME=your-repo-name
export REGISTRY_URI=$REGION-docker.pkg.dev/$PROJECT_ID/$REPOSITORY_NAME
export BUILD_IMAGE_NAME=otel-collector
export BUILD_IMAGE_TAG=0.0.1

docker build --platform linux/amd64 -t $REGISTRY_URI/$BUILD_IMAGE_NAME:$BUILD_IMAGE_TAG .
docker push $REGISTRY_URI/$BUILD_IMAGE_NAME:$BUILD_IMAGE_TAG
  • otel-config.yaml: This configuration file sets up the OpenTelemetry Collector to receive traces and metrics via OTLP over gRPC and HTTP. It includes exporters for sending data to Google Cloud Logging and various processors and extensions for managing the data.
  • Build and Push Docker Images: The commands build the Docker image using the specified Dockerfile and configuration file, then push the image to Google Artifact Registry. This ensures that your OpenTelemetry Collector configuration is containerized and ready for deployment on GCP.

Deploying to Google Cloud Platform

To deploy your application and OpenTelemetry Collector to Google Cloud Platform, follow these steps to create and configure a Cloud Run service.

Create a Cloud Run Service Configuration File

Create a cloud-run.yaml file with the following content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: otel-demo
annotations:
run.googleapis.com/ingress: all
run.googleapis.com/ingress-status: all
labels:
cloud.googleapis.com/location: asia-east1
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/maxScale: '4'
autoscaling.knative.dev/minScale: '1'
run.googleapis.com/cpu-throttling: 'false'
run.googleapis.com/startup-cpu-boost: 'true'
labels:
run.googleapis.com/startupProbeType: Custom
spec:
containerConcurrency: 100
timeoutSeconds: 300
serviceAccountName: ${YOUR_SERVICE_ACCOUNT_NAME}@${YOUR_PROJECT_ID}.iam.gserviceaccount.com
containers:
- name: otel-demo
image: ${YOUR_REGION}-docker.pkg.dev/${YOUR_PROJECT_ID}/${YOUR_REPO}/otel-demo:${YOUR_VERSION}
ports:
- containerPort: 8080
env:
- name: spring.profiles.active
value: test,gcp
- name: logging.level.com.example.otel
value: debug
- name: JAVA_TOOL_OPTIONS
value: -Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF-8
resources:
limits:
cpu: 1000m
memory: 1Gi
startupProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 10
timeoutSeconds: 1
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 10
timeoutSeconds: 1
- name: otel-collector
image: ${YOUR_REGION}-docker.pkg.dev/${YOUR_PROJECT_ID}/${YOUR_REPO}/otel-collector:${YOUR_VERSION} # 替換為您的 Artifact Registry 路徑和版本
resources:
limits:
cpu: 500m
memory: 128Mi
traffic:
- percent: 100
latestRevision: true

Deploying the Cloud Run Service

After creating the cloud-run.yaml file, deploy the Cloud Run service using the following command:

1
gcloud run services replace cloud-run.yaml

Once your application and OpenTelemetry Collector are deployed to Google Cloud Platform, you can view the tracing information in the GCP Console. Here is an example of what you might see:

GCP Trace Explorer Overview

In the provided screenshot, the trace for the http post /api/orders request is shown. This trace includes multiple spans such as create-order and process-order-async, indicating that asynchronous processing was correctly traced. Each span shows the duration and order of execution, providing a clear picture of the request’s flow.

  • Trace Overview: The trace overview shows that the total duration of the request was 2.572 seconds and it involved 7 spans. This overview helps you quickly understand the performance of the request.
  • Span Details: The span details show the execution time of each operation within the trace. For example, the create-order span took approximately 786 milliseconds, and the process-order-async span took about 2 seconds. This detailed breakdown helps you identify which parts of the request took the most time.

In the GCP Trace Explorer, you can expand to view logs and traces together, providing a comprehensive view of your application’s behavior, as shown below:

GCP Trace Explorer expand

Conclusion

By following this step-by-step guide, you have successfully integrated OpenTelemetry with a Spring Boot application and deployed it to Google Cloud Platform. This setup includes:

  1. Configuring Spring Boot for OpenTelemetry: Ensuring all necessary dependencies and settings are included for effective tracing and monitoring.
  2. Creating Docker Environment: Setting up Docker Compose for local development and testing.
  3. Deploying to GCP: Configuring and deploying your application and OpenTelemetry Collector to Google Cloud Platform using Cloud Run.
  4. Monitoring and Analysis: Using Jaeger UI and GCP Trace Explorer to view and analyze traces, ensuring that asynchronous processing is correctly traced and providing insights into the application’s performance.

This integration provides comprehensive observability, helping you monitor and analyze your application’s behavior in both development and production environments. With detailed tracing and monitoring, you can quickly identify and resolve performance bottlenecks and ensure a smooth and efficient application operation.

References

By leveraging these resources, you can further enhance your understanding and capabilities in integrating observability into your Spring Boot applications, ensuring robust and reliable performance in cloud environments.