diff --git a/examples/example-gcp-observability/README.md b/examples/example-gcp-observability/README.md new file mode 100644 index 00000000000..95270fa8140 --- /dev/null +++ b/examples/example-gcp-observability/README.md @@ -0,0 +1,39 @@ +gRPC GCP Observability Example +================ + +The GCP Observability example consists of a Hello World client and a Hello World server instrumented for logs, metrics and tracing. + +__Please refer to Microservices Observability user guide for setup.__ + +### Build the example + +Build the Observability client & server. From the `grpc-java/examples/example-gcp-observability` +directory: +``` +$ ../gradlew installDist +``` + +This creates the scripts `build/install/example-gcp-observability/bin/gcp-observability-client` and +`build/install/example-gcp-observability/bin/gcp-observability-server`. + +### Run the example with configuration + +To use Observability, you should first setup and configure authorization as mentioned in the user guide. + +You need to set the `GRPC_GCP_OBSERVABILITY_CONFIG_FILE` environment variable to point to the gRPC GCP Observability configuration file (preferred) or if that +is not set then `GRPC_GCP_OBSERVABILITY_CONFIG` environment variable to gRPC GCP Observability configuration value. This is needed by both +`build/install/example-gcp-observability/bin/gcp-observability-client` and +`build/install/example-gcp-observability/bin/gcp-observability-server`. + +1. To start the observability-enabled example server on its default port of 50051, run: +``` +$ export GRPC_GCP_OBSERVABILITY_CONFIG_FILE=src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_server_config.json +$ ./build/install/example-gcp-observability/bin/gcp-observability-server +``` + +2. In a different terminal window, run the observability-enabled example client: +``` +$ export GRPC_GCP_OBSERVABILITY_CONFIG_FILE=src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_client_config.json +$ ./build/install/example-gcp-observability/bin/gcp-observability-client +``` + diff --git a/examples/example-gcp-observability/build.gradle b/examples/example-gcp-observability/build.gradle new file mode 100644 index 00000000000..957d4da8fd9 --- /dev/null +++ b/examples/example-gcp-observability/build.gradle @@ -0,0 +1,68 @@ +plugins { + // Provide convenience executables for trying out the examples. + id 'application' + // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions + id 'com.google.protobuf' version '0.8.17' + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' + id 'java' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" + } + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you +// are looking at a tagged version of the example and not "master"! + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def grpcVersion = '1.54.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protocVersion = '3.21.7' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-gcp-observability:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" +} + +protobuf { + protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } + plugins { + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + } + generateProtoTasks { + all()*.plugins { grpc {} } + } +} + +startScripts.enabled = false + +task ObservabilityHelloWorldServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.gcpobservability.GcpObservabilityServer' + applicationName = 'gcp-observability-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task ObservabilityHelloWorldClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.gcpobservability.GcpObservabilityClient' + applicationName = 'gcp-observability-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(ObservabilityHelloWorldServer) + from(ObservabilityHelloWorldClient) + fileMode = 0755 +} diff --git a/examples/example-gcp-observability/settings.gradle b/examples/example-gcp-observability/settings.gradle new file mode 100644 index 00000000000..1e4ba3812eb --- /dev/null +++ b/examples/example-gcp-observability/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-gcp-observability' diff --git a/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityClient.java b/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityClient.java new file mode 100644 index 00000000000..34112adb108 --- /dev/null +++ b/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityClient.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 The gRPC Authors + * + * 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.grpc.examples.gcpobservability; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.gcp.observability.GcpObservability; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A simple observability client that requests a greeting from the {@link HelloWorldServer} and + * generates logs, metrics and traces based on the configuration. + */ +public class GcpObservabilityClient { + private static final Logger logger = Logger.getLogger(GcpObservabilityClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public GcpObservabilityClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + String user = "world"; + String target = "localhost:50051"; + if (args.length > 0) { + if ("--help".equals(args[0])) { + System.err.println("Usage: [name [target]]"); + System.err.println(""); + System.err.println(" name The name you wish to be greeted by. Defaults to " + user); + System.err.println(" target The server to connect to. Defaults to " + target); + System.exit(1); + } + user = args[0]; + } + if (args.length > 1) { + target = args[1]; + } + + // Initialize observability + try (GcpObservability observability = GcpObservability.grpcInit()) { + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); + try { + GcpObservabilityClient client = new GcpObservabilityClient(channel); + client.greet(user); + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } // observability.close() called implicitly + } +} diff --git a/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityServer.java b/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityServer.java new file mode 100644 index 00000000000..c599e7047c5 --- /dev/null +++ b/examples/example-gcp-observability/src/main/java/io/grpc/examples/gcpobservability/GcpObservabilityServer.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023 The gRPC Authors + * + * 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.grpc.examples.gcpobservability; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.gcp.observability.GcpObservability; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Observability server that manages startup/shutdown of a {@code Greeter} server and generates + * logs, metrics and traces based on the configuration. + */ +public class GcpObservabilityServer { + private static final Logger logger = Logger.getLogger(GcpObservabilityServer.class.getName()); + + private Server server; + + private void start() throws IOException { + int port = 50051; + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new GreeterImpl()) + .build() + .start(); + logger.info("Server started, listening on " + port); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + // Initialize observability + GcpObservability observability = GcpObservability.grpcInit(); + final GcpObservabilityServer server = new GcpObservabilityServer(); + server.start(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + server.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + // Shut down observability + observability.close(); + System.err.println("*** server shut down"); + } + }); + + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/example-gcp-observability/src/main/proto/helloworld/helloworld.proto b/examples/example-gcp-observability/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..64a8c09ee16 --- /dev/null +++ b/examples/example-gcp-observability/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,39 @@ +/* + * Copyright 2023 The gRPC Authors + * + * 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. + */ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_client_config.json b/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_client_config.json new file mode 100644 index 00000000000..9c69d55e7f7 --- /dev/null +++ b/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_client_config.json @@ -0,0 +1,17 @@ +{ + "cloud_monitoring": {}, + "cloud_trace": { + "sampling_rate": 1.0 + }, + "cloud_logging": { + "client_rpc_events": [{ + "methods": ["helloworld.Greeter/*"] + }], + "server_rpc_events": [{ + "methods": ["helloworld.Greeter/*"] + }] + }, + "labels": { + "environment" : "example-client" + } +} diff --git a/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_server_config.json b/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_server_config.json new file mode 100644 index 00000000000..78698c05faf --- /dev/null +++ b/examples/example-gcp-observability/src/main/resources/io/grpc/examples/gcpobservability/gcp_observability_server_config.json @@ -0,0 +1,17 @@ +{ + "cloud_monitoring": {}, + "cloud_trace": { + "sampling_rate": 1.0 + }, + "cloud_logging": { + "client_rpc_events": [{ + "methods": ["helloworld.Greeter/*"] + }], + "server_rpc_events": [{ + "methods": ["helloworld.Greeter/*"] + }] + }, + "labels": { + "environment" : "example-server" + } +}