Skip to content

Commit

Permalink
Add manually instrumented spring sample (#368)
Browse files Browse the repository at this point in the history
* Add new example to project

* Udpate spring service name for spring sleuth example

* Add implementation for spring sample

* Update task name to run app

The name 'bootRun' conflicts with the name of tasks in a certain gradle
versions.

* Add comments for clarity

* Add EOF line

* Add cleanup instructions

* Address feedback

* update cleanup instructions to include deletion for Artifact Repository

* Update script to use shorthands for env verification
  • Loading branch information
psx95 authored Sep 13, 2024
1 parent 2a30e85 commit f29adf6
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
spring.application.name=gcp-otel-spring-demo
spring.application.name=gcp-otel-spring-sleuth-demo
spring.main.banner-mode=off
spring.sleuth.sampler.probability=1.0
75 changes: 75 additions & 0 deletions examples/spring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Instrumenting Spring Application using OpenTelemetry with Resource Detection

This example shows how to export metrics and traces to GCP generated from a manually instrumented Spring Boot application.
Opentelemetry Autoconfiguration module is used to configure exporters and resource detection.

#### Prerequisites

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

```shell
gcloud auth application-default login
```
Executing this command will save your application credentials to default path which will depend on the type of machine -
- Linux, macOS: `$HOME/.config/gcloud/application_default_credentials.json`
- Windows: `%APPDATA%\gcloud\application_default_credentials.json`

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

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

#### Run the application locally

You can run the example application via gradle. From the project root:

##### Build and run the executable JAR

```shell
cd examples/spring && gradle runApp
```

The application is now running. To generate traces, head to `http://localhost:8080` in your browser.
*You can also try hitting other supported endpoints - `http://localhost:8080/greeting` to generate additional traces.*

You should now see the exported traces in your Google Cloud project.

#### Run the application in Cloud Run

To run this example in Google Cloud Run, you need to run the convenience script provided. After following the prerequisites,

First, export the Google Cloud Region for cloud run to `GOOGLE_CLOUD_RUN_REGION` environment variable:

```shell
# This can be any supported Google cloud region
export GOOGLE_CLOUD_RUN_REGION=us-central1
```

Then, from the root of the repository,
```shell
cd examples/spring && ./run_in_cloud-run.sh
```

This will deploy the containerized application to Cloud Run and create a proxy endpoint on your localhost which you can then call to reach your deployed Cloud Run service.
You should be able to hit the following endpoints from your browser:

```text
http://localhost:8080/
http://localhost:8080/greeting
```

The metrics and traces from this example can be viewed in Google Cloud Console.

##### Cleaning up

Keeping services running in Cloud Run can incur costs. Services deployed on Cloud Run can either
be deleted from the GCP console or you can run the following `gcloud` command:

```shell
# Delete the Cloud Run Service
gcloud run services delete spring-java-cloud-run

# Delete Artifact Registry
gcloud artifacts repositories delete cloud-run-applications --location=$GOOGLE_CLOUD_RUN_REGION
```
86 changes: 86 additions & 0 deletions examples/spring/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 'org.springframework.boot' version '2.4.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'application'
id 'com.google.cloud.tools.jib'
}

repositories {
mavenCentral()
}

description = 'Java Spring application instrumented with OpenTelemetry and enabled resource detection'

jar {
manifest {
attributes(
'Main-Class': 'com.google.cloud.opentelemetry.example.spring.Main'
)
}
}

// OpenTelemetry Autoconfigure module can be configured using system properties
def autoconf_config = [
'-Dotel.resource.attributes=gcp.project_id=otel-test-sharmapranav',
'-Dotel.traces.exporter=google_cloud_trace',
'-Dotel.metrics.exporter=google_cloud_monitoring,logging',
'-Dotel.logs.exporter=none',
'-Dotel.java.global-autoconfigure.enabled=true',
'-Dotel.service.name=spring-example-service',
]

jib {
from.image = "gcr.io/distroless/java-debian10:11"
containerizingMode = "packaged"
container.ports = ["8080"]
container.jvmFlags = autoconf_config as List
}

// convenience task to build and run app with correct system properties
tasks.register('runApp', JavaExec) {
group = "Execution"
description = "Builds and runs the spring application's execuable JAR"

dependsOn tasks.bootJar
classpath = files('build/libs/examples-spring-0.1.0-SNAPSHOT.jar')
jvmArgs = autoconf_config
}

dependencies {
implementation(libraries.opentelemetry_api)
implementation(libraries.opentelemetry_sdk)
implementation(libraries.google_cloud_trace)
implementation(libraries.spring_boot_starter_web)
// using autoconfigure to detect resources
implementation(libraries.opentelemetry_sdk_autoconf)
implementation(libraries.opentelemetry_autoconfigure_spi)
// resource detection module
runtimeOnly(libraries.opentelemetry_gcp_resources)
// exporter dependencies
implementation project(':exporter-trace')
implementation project(':exporter-metrics')
// auto exporter makes Google Cloud exporters available to autoconfigure module
implementation project(':exporter-auto')
// this helps in debugging as it outputs all export to std out
implementation 'io.opentelemetry:opentelemetry-exporter-logging:1.33.0'
}

test {
useJUnitPlatform()
}
47 changes: 47 additions & 0 deletions examples/spring/run_in_cloud-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/bash
#
# 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.
#
CONTAINER_REGISTRY=cloud-run-applications
REGISTRY_LOCATION=us-central1
UNSET_WARNING="Environment variable not set, please set the environment variable"

# Verify necessary environment variables are set
echo "${GOOGLE_CLOUD_PROJECT:?${UNSET_WARNING}}"
echo "${GOOGLE_APPLICATION_CREDENTIALS:?${UNSET_WARNING}}"
echo "${GOOGLE_CLOUD_RUN_REGION:?${UNSET_WARNING}}"

echo "ENVIRONMENT VARIABLES VERIFIED"
set -x

echo "CREATING CLOUD ARTIFACT REPOSITORY"
gcloud artifacts repositories create ${CONTAINER_REGISTRY} --repository-format=docker --location=${REGISTRY_LOCATION} --description="Sample applications to run on Google Cloud Run"
echo "CREATED ${CONTAINER_REGISTRY} in ${REGISTRY_LOCATION}"

echo "BUILDING SAMPLE APP IMAGE"
gradle clean jib --image "${REGISTRY_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/${CONTAINER_REGISTRY}/spring-java-sample"

echo "DEPLOYING APPLICATION ON CLOUD RUN"
gcloud run deploy spring-java-cloud-run \
--image="${REGISTRY_LOCATION}-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/${CONTAINER_REGISTRY}/spring-java-sample" \
--region="${GOOGLE_CLOUD_RUN_REGION}" \
--no-allow-unauthenticated \
--no-cpu-throttling \
--max-instances=5 \
--min-instances=3

echo "ENABLING SAMPLE APP ON PORT 8080 VIA PROXY"
echo "VISIT http://localhost:8080 TO ACCESS THE APPLICATION OR PRESS CTRL+C TO EXIT"
gcloud beta run services proxy spring-java-cloud-run --port=8080
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.example.spring;

import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {
private final LongCounter counterGreeting;
private final LongCounter counterHome;
private final Tracer tracer;

@Autowired
public AppController(Meter meter, Tracer tracer) {
counterGreeting =
meter
.counterBuilder("greeting_counter")
.setDescription("Hit /greeting endpoint")
.setUnit("1")
.build();
counterHome =
meter
.counterBuilder("home_counter")
.setDescription("Hit root endpoint")
.setUnit("1")
.build();
this.tracer = tracer;
}

@GetMapping("/greeting")
public String greeting() {
Span span = tracer.spanBuilder("greeting_call").startSpan();
try (Scope scope = span.makeCurrent()) {
span.addEvent("Event A");
span.setAttribute("test_api", true);
counterGreeting.add(1);
} finally {
span.end();
}
return "Hello World";
}

@GetMapping("/")
public String home() {
counterHome.add(1);
return "Welcome to Spring Demo";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.example.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.example.spring;

import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenTelemetryConfiguration {

private static final String INSTRUMENTATION_SCOPE_NAME = "spring-demo";
private static final String INSTRUMENTATION_VERSION = "semver:1.0.0";

@Bean
public OpenTelemetrySdk getOpenTelemetrySdk() {
return AutoConfiguredOpenTelemetrySdk.initialize().getOpenTelemetrySdk();
}

@Bean
public Tracer getTracer(OpenTelemetrySdk openTelemetrySdk) {
return openTelemetrySdk
.tracerBuilder(INSTRUMENTATION_SCOPE_NAME)
.setInstrumentationVersion(INSTRUMENTATION_VERSION)
.build();
}

@Bean
public Meter getMeter(OpenTelemetrySdk openTelemetrySdk) {
return openTelemetrySdk
.meterBuilder(INSTRUMENTATION_SCOPE_NAME)
.setInstrumentationVersion(INSTRUMENTATION_VERSION)
.build();
}
}
2 changes: 2 additions & 0 deletions examples/spring/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG
spring.application.name=gcp-otel-spring-demo
4 changes: 4 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ include ":detector-resources"
include ":detector-resources-support"
include ":e2e-test-server"
include ":examples-spring-sleuth"
include ":examples-spring"
include ":propagators-gcp"
include ":shared-resourcemapping"

Expand Down Expand Up @@ -88,3 +89,6 @@ project(':shared-resourcemapping').projectDir =

project(':examples-otlp-spring').projectDir =
"$rootDir/examples/otlp-spring" as File

project(':examples-spring').projectDir =
"$rootDir/examples/spring" as File

0 comments on commit f29adf6

Please sign in to comment.