Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add manually instrumented spring sample #368

Merged
merged 10 commits into from
Sep 13, 2024
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
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
psx95 marked this conversation as resolved.
Show resolved Hide resolved

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',
psx95 marked this conversation as resolved.
Show resolved Hide resolved
'-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')
psx95 marked this conversation as resolved.
Show resolved Hide resolved
// 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
psx95 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading