Skip to content

Commit

Permalink
Add otlp with spring boot sample (#327)
Browse files Browse the repository at this point in the history
* Add initial setup files for new example

* Add functioning spring boot application

* add additional mappings to enhance sample

* Add spring boot application properties

* Update spring application to generate traces

* Run application in GKE

* Fix bug in credential refresh

* Add client application to test credential refresh

* Add readme for sample

* Add missing EOF lines

* Readme style update
  • Loading branch information
psx95 authored Apr 17, 2024
1 parent 008cf95 commit 97a8f20
Show file tree
Hide file tree
Showing 9 changed files with 541 additions and 0 deletions.
96 changes: 96 additions & 0 deletions examples/otlp-spring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# OTLP Trace with Spring Boot and Google Auth

A sample Spring Boot service that exports OTLP traces, protected by Google authentication. The sample uses auto-refreshing credentials.

### Prerequisites

##### Get Google Cloud Credentials on your machine

```shell
gcloud auth application-default login
```

##### Export the Google Cloud Project ID to `GOOGLE_CLOUD_PROJECT` environment variable:

```shell
export GOOGLE_CLOUD_PROJECT="my-awesome-gcp-project-id"
```

##### Update build.gradle to set required arguments

Update [`build.gradle`](build.grade) to set the following:

```
'-Dotel.resource.attributes=gcp.project_id=<YOUR_PROJECT_ID>,
'-Dotel.exporter.otlp.headers=X-Goog-User-Project=<YOUR_QUOTA_PROJECT>',
# Optional - if you want to export using gRPC protocol
'-Dotel.exporter.otlp.protocol=grpc',
```

## Running Locally on your machine

Setup your endpoint with the `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable:

```shell
export OTEL_EXPORTER_OTLP_ENDPOINT="http://your-endpoint:port"
```

To run the spring boot application from project root:

```shell
./gradlew :examples-otlp-spring:run
```

This will start the web application on localhost:8080. The application provides 2 routes:
- http://localhost:8080/ : The index root; does not generate a trace.
- http://localhost:8080/work : This route generates a trace.

Visit these routes to interact with the application.

## Running on Google Kubernetes Engine

> [!NOTE]
> You need to have a GKE cluster already setup in your GCP project before continuing with these steps.
Create artifact registry repository to host your containerized image of the application:
```shell
gcloud artifacts repositories create otlp-samples --repository-format=docker --location=us-central1

gcloud auth configure-docker us-central1-docker.pkg.dev
```

Build and push your image to the Artifact Registry.
```shell
./gradlew :examples:otlp-spring:jib --image="us-central1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/otlp-samples/spring-otlp-trace-example:v1"
```

Deploy the image on your Kubernetes cluster and setup port forwarding to interact with your cluster:
```shell
sed s/%GOOGLE_CLOUD_PROJECT%/$GOOGLE_CLOUD_PROJECT/g \
examples/otlp-spring/k8s/deployment.yaml | kubectl apply -f -

# This connects port 8080 on your machine to port 60000 on the spring-otlp-service
kubectl port-forward service/spring-otlp-service 8080:60000
```

After successfully setting up port-forwarding, you can send requests to your cluster via `curl` or some similar tool:
```shell
curl http://localhost:8080/work?desc=test
```

### Sending continuous requests

The sample comes with a [client program](src/test/java/com/google/cloud/opentelemetry/examples/otlpspring/MainClient.java) which sends requests to deployed application on your cluster at a fixed rate.
Once you have port forwarding setup for your cluster, run this client program to send continuous requests to the Spring service to generate multiple traces.

```shell
./gradlew :examples-otlp-spring:runClient
```

### GKE Cleanup

After running the sample, delete the deployment and the service if you are done with it:
```shell
kubectl delete services spring-otlp-service
kubectl delete deployment spring-otlp-trace-example
```
70 changes: 70 additions & 0 deletions examples/otlp-spring/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/
plugins {
id 'java'
id 'application'
id 'com.google.cloud.tools.jib'
}

description = 'Example showcasing OTLP trace ingest on GCP and Google Auth in a Spring Boot App'

group 'com.google.cloud.opentelemetry.examples'

repositories {
mavenCentral()
}

dependencies {
implementation(libraries.opentelemetry_api)
implementation(libraries.opentelemetry_sdk)
implementation(libraries.opentelemetry_otlp_exporter)
implementation(libraries.opentelemetry_sdk_autoconf)
implementation(libraries.google_auth)

implementation(libraries.spring_boot_starter_web)
}

// Provide headers from env variable
// export OTEL_EXPORTER_OTLP_ENDPOINT="http://path/to/yourendpoint:port"
def autoconf_config = [
'-Dotel.resource.attributes=gcp.project_id=<YOUR_PROJECT>',
'-Dotel.exporter.otlp.headers=X-Goog-User-Project=<YOUR_QUOTA_PROJECT>',
'-Dotel.traces.exporter=otlp',
'-Dotel.metrics.exporter=none',
'-Dotel.exporter.otlp.protocol=http/protobuf',
'-Dotel.java.global-autoconfigure.enabled=true',
'-Dotel.service.name=spring-otlp-trace-example-service',
]

application {
mainClass = "com.google.cloud.opentelemetry.examples.otlpspring.Main"
applicationDefaultJvmArgs = autoconf_config
}

jib {
from.image = "gcr.io/distroless/java-debian10:11"
containerizingMode = "packaged"
container.jvmFlags = autoconf_config as List
container.ports = ["8080"]
container.environment = [
"OTEL_EXPORTER_OTLP_ENDPOINT":"http://path/to/yourendpoint:port",
]
}

tasks.register('runClient', JavaExec) {
classpath = sourceSets.test.runtimeClasspath
mainClass = 'com.google.cloud.opentelemetry.examples.otlpspring.MainClient'
}
46 changes: 46 additions & 0 deletions examples/otlp-spring/k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-otlp-trace-example
namespace: default
labels:
app: spring-otlp-trace-example
tier: backend
spec:
selector:
matchLabels:
app: spring-otlp-trace-example
tier: backend
template:
metadata:
labels:
app: spring-otlp-trace-example
tier: backend
spec:
containers:
- name: spring-otlp-trace-example
image: us-central1-docker.pkg.dev/%GOOGLE_CLOUD_PROJECT%/otlp-samples/spring-otlp-trace-example:v1
imagePullPolicy: Always
# required for resource detection in GKE environment
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONTAINER_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
---
apiVersion: v1
kind: Service
metadata:
name: spring-otlp-service
spec:
selector:
app: spring-otlp-trace-example
tier: backend
ports:
- protocol: TCP
port: 60000
targetPort: 8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2024 Google LLC
*
* 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 com.google.cloud.opentelemetry.examples.otlpspring;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import java.util.Optional;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApplicationController {
private static final String INSTRUMENTATION_SCOPE_NAME = ApplicationController.class.getName();
private static final Random random = new Random();

private static final String INDEX_GREETING =
"Welcome to OTLP Trace sample with Google Auth on Spring";
private static final String WORK_RESPONSE_FMT = "Work finished in %d ms";

private final Logger logger = LoggerFactory.getLogger(ApplicationController.class);
private final OpenTelemetrySdk openTelemetrySdk;

@Autowired
public ApplicationController(OpenTelemetrySdk openTelemetrySdk) {
this.openTelemetrySdk = openTelemetrySdk;
}

@GetMapping("/")
public String index() {
return INDEX_GREETING;
}

@GetMapping("/work")
public String simulateWork(@RequestParam(name = "desc") Optional<String> description) {
String desc = description.orElse("generic");
// Generate a span
Span span =
openTelemetrySdk.getTracer(INSTRUMENTATION_SCOPE_NAME).spanBuilder(desc).startSpan();
long workDurationMillis;
try (Scope scope = span.makeCurrent()) {
span.addEvent("Event A");
// Do some work for the use case
// Simulate work: this could be simulating a network request or an expensive disk operation
workDurationMillis = 100 + random.nextInt(5) * 100;
Thread.sleep(workDurationMillis);
span.addEvent("Event B");
} catch (InterruptedException e) {
logger.debug("Error while sleeping: {}", e.getMessage());
throw new RuntimeException(e);
} finally {
span.end();
}
return String.format(WORK_RESPONSE_FMT, workDurationMillis);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* 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 com.google.cloud.opentelemetry.examples.otlpspring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);

public static void main(String[] args) {
logger.info("Starting OTLP with Spring Boot and Google Auth");
SpringApplication.run(Main.class, args);
}
}
Loading

0 comments on commit 97a8f20

Please sign in to comment.