From d57a931b21f8c7d92ed1570c16a2794cb468261c Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 3 Sep 2020 11:25:32 -0700 Subject: [PATCH] gRPC client and server examples (#1224) * Add dotnet new template ASP.NET Core gRPC service * Add Examples.GrpcService project to solution * Submit to StyleCop's demands * Add Open Telemetry * Remove unused usings * Add Grpc.Net.Client example to example Console project * Remove comments from original template * Fix file header * Add exception handling when gRPC service has not been started * Apply suggestions from code review Co-authored-by: Reiley Yang Co-authored-by: Reiley Yang Co-authored-by: Cijo Thomas --- OpenTelemetry.sln | 7 ++ examples/Console/Examples.Console.csproj | 11 +++ examples/Console/Program.cs | 8 +- examples/Console/TestGrpcNetClient.cs | 57 ++++++++++++ .../GrpcService/Examples.GrpcService.csproj | 23 +++++ examples/GrpcService/Program.cs | 38 ++++++++ examples/GrpcService/Protos/greet.proto | 21 +++++ .../GrpcService/Services/GreeterService.cs | 40 ++++++++ examples/GrpcService/Startup.cs | 92 +++++++++++++++++++ .../GrpcService/appsettings.Development.json | 10 ++ examples/GrpcService/appsettings.json | 25 +++++ 11 files changed, 331 insertions(+), 1 deletion(-) create mode 100644 examples/Console/TestGrpcNetClient.cs create mode 100644 examples/GrpcService/Examples.GrpcService.csproj create mode 100644 examples/GrpcService/Program.cs create mode 100644 examples/GrpcService/Protos/greet.proto create mode 100644 examples/GrpcService/Services/GreeterService.cs create mode 100644 examples/GrpcService/Startup.cs create mode 100644 examples/GrpcService/appsettings.Development.json create mode 100644 examples/GrpcService/appsettings.json diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 407d003a7..728dce4b0 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -189,6 +189,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "extending-the-sdk", "docs\t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "correlation", "docs\logs\correlation\correlation.csproj", "{B26BE278-C9DA-4067-A0EE-6A4227B3DC87}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.GrpcService", "examples\GrpcService\Examples.GrpcService.csproj", "{DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -363,6 +365,10 @@ Global {B26BE278-C9DA-4067-A0EE-6A4227B3DC87}.Debug|Any CPU.Build.0 = Debug|Any CPU {B26BE278-C9DA-4067-A0EE-6A4227B3DC87}.Release|Any CPU.ActiveCfg = Release|Any CPU {B26BE278-C9DA-4067-A0EE-6A4227B3DC87}.Release|Any CPU.Build.0 = Release|Any CPU + {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -390,6 +396,7 @@ Global {CB401DF1-FF5C-4055-886E-1183E832B2D6} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD} {FCDCF532-A163-40DA-80B7-7530AA1182C4} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {B26BE278-C9DA-4067-A0EE-6A4227B3DC87} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} + {DB942F5A-D571-4DEA-B1A7-B6BE0E24E6ED} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/examples/Console/Examples.Console.csproj b/examples/Console/Examples.Console.csproj index 7c19de781..6f7174497 100644 --- a/examples/Console/Examples.Console.csproj +++ b/examples/Console/Examples.Console.csproj @@ -6,12 +6,23 @@ false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/examples/Console/Program.cs b/examples/Console/Program.cs index 64da0e2d0..11e43f780 100644 --- a/examples/Console/Program.cs +++ b/examples/Console/Program.cs @@ -40,11 +40,12 @@ namespace Examples.Console /// Arguments from command line. public static void Main(string[] args) { - Parser.Default.ParseArguments(args) + Parser.Default.ParseArguments(args) .MapResult( (JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port), (ZipkinOptions options) => TestZipkinExporter.Run(options.Uri), (PrometheusOptions options) => TestPrometheusExporter.RunAsync(options.Port, options.PushIntervalInSecs, options.DurationInMins), + (GrpcNetClientOptions options) => TestGrpcNetClient.Run(), (HttpClientOptions options) => TestHttpClient.Run(), (RedisOptions options) => TestRedis.Run(options.Uri), (ZPagesOptions options) => TestZPagesExporter.Run(), @@ -90,6 +91,11 @@ namespace Examples.Console public int DurationInMins { get; set; } } + [Verb("grpc", HelpText = "Specify the options required to test Grpc.Net.Client")] + internal class GrpcNetClientOptions + { + } + [Verb("httpclient", HelpText = "Specify the options required to test HttpClient")] internal class HttpClientOptions { diff --git a/examples/Console/TestGrpcNetClient.cs b/examples/Console/TestGrpcNetClient.cs new file mode 100644 index 000000000..0e7b86a7e --- /dev/null +++ b/examples/Console/TestGrpcNetClient.cs @@ -0,0 +1,57 @@ +// +// Copyright The OpenTelemetry 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. +// + +using System.Diagnostics; +using Examples.GrpcService; +using Grpc.Core; +using Grpc.Net.Client; +using OpenTelemetry; +using OpenTelemetry.Trace; + +namespace Examples.Console +{ + internal class TestGrpcNetClient + { + internal static object Run() + { + using var openTelemetry = Sdk.CreateTracerProviderBuilder() + .AddGrpcClientInstrumentation() + .AddSource("grpc-net-client-test") + .AddConsoleExporter() + .Build(); + + var source = new ActivitySource("grpc-net-client-test"); + using (var parent = source.StartActivity("Main", ActivityKind.Server)) + { + using var channel = GrpcChannel.ForAddress("https://localhost:5001"); + var client = new Greeter.GreeterClient(channel); + + try + { + var reply = client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" }).GetAwaiter().GetResult(); + System.Console.WriteLine($"Message received: {reply.Message}"); + } + catch (RpcException) + { + System.Console.Error.WriteLine($"To run this Grpc.Net.Client example, first start the Examples.GrpcService project."); + throw; + } + } + + return null; + } + } +} diff --git a/examples/GrpcService/Examples.GrpcService.csproj b/examples/GrpcService/Examples.GrpcService.csproj new file mode 100644 index 000000000..426131711 --- /dev/null +++ b/examples/GrpcService/Examples.GrpcService.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + + + + diff --git a/examples/GrpcService/Program.cs b/examples/GrpcService/Program.cs new file mode 100644 index 000000000..4527f3e5b --- /dev/null +++ b/examples/GrpcService/Program.cs @@ -0,0 +1,38 @@ +// +// Copyright The OpenTelemetry 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. +// + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Examples.GrpcService +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + // Additional configuration is required to successfully run gRPC on macOS. + // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/examples/GrpcService/Protos/greet.proto b/examples/GrpcService/Protos/greet.proto new file mode 100644 index 000000000..eb48ad24f --- /dev/null +++ b/examples/GrpcService/Protos/greet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option csharp_namespace = "Examples.GrpcService"; + +package greet; + +// 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/GrpcService/Services/GreeterService.cs b/examples/GrpcService/Services/GreeterService.cs new file mode 100644 index 000000000..9ddb42520 --- /dev/null +++ b/examples/GrpcService/Services/GreeterService.cs @@ -0,0 +1,40 @@ +// +// Copyright The OpenTelemetry 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. +// + +using System.Threading.Tasks; +using Grpc.Core; +using Microsoft.Extensions.Logging; + +namespace Examples.GrpcService +{ + public class GreeterService : Greeter.GreeterBase + { + private readonly ILogger logger; + + public GreeterService(ILogger logger) + { + this.logger = logger; + } + + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + return Task.FromResult(new HelloReply + { + Message = "Hello " + request.Name, + }); + } + } +} diff --git a/examples/GrpcService/Startup.cs b/examples/GrpcService/Startup.cs new file mode 100644 index 000000000..391afd857 --- /dev/null +++ b/examples/GrpcService/Startup.cs @@ -0,0 +1,92 @@ +// +// Copyright The OpenTelemetry 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. +// + +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenTelemetry.Trace; + +namespace Examples.GrpcService +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddGrpc(); + + // Switch between Jaeger/Zipkin by setting UseExporter in appsettings.json. + var exporter = this.Configuration.GetValue("UseExporter").ToLowerInvariant(); + switch (exporter) + { + case "jaeger": + services.AddOpenTelemetryTracing((builder) => builder + .AddAspNetCoreInstrumentation() + .AddJaegerExporter(jaegerOptions => + { + jaegerOptions.ServiceName = this.Configuration.GetValue("Jaeger:ServiceName"); + jaegerOptions.AgentHost = this.Configuration.GetValue("Jaeger:Host"); + jaegerOptions.AgentPort = this.Configuration.GetValue("Jaeger:Port"); + })); + break; + case "zipkin": + services.AddOpenTelemetryTracing((builder) => builder + .AddAspNetCoreInstrumentation() + .AddZipkinExporter(zipkinOptions => + { + zipkinOptions.ServiceName = this.Configuration.GetValue("Zipkin:ServiceName"); + zipkinOptions.Endpoint = new Uri(this.Configuration.GetValue("Zipkin:Endpoint")); + })); + break; + default: + services.AddOpenTelemetryTracing((builder) => builder + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()); + break; + } + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + + endpoints.MapGet("/", async context => + { + await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + }); + }); + } + } +} diff --git a/examples/GrpcService/appsettings.Development.json b/examples/GrpcService/appsettings.Development.json new file mode 100644 index 000000000..fe20c40cc --- /dev/null +++ b/examples/GrpcService/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Grpc": "Information", + "Microsoft": "Information" + } + } +} diff --git a/examples/GrpcService/appsettings.json b/examples/GrpcService/appsettings.json new file mode 100644 index 000000000..eb311b0d6 --- /dev/null +++ b/examples/GrpcService/appsettings.json @@ -0,0 +1,25 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, + "UseExporter": "console", + "Jaeger": { + "ServiceName": "jaeger-test", + "Host": "localhost", + "Port": 6831 + }, + "Zipkin": { + "ServiceName": "zipkin-test", + "Endpoint": "http://localhost:9411/api/v2/spans" + } +}