diff --git a/examples/features/opentelemetry/README.md b/examples/features/opentelemetry/README.md new file mode 100644 index 000000000000..d4e18af705de --- /dev/null +++ b/examples/features/opentelemetry/README.md @@ -0,0 +1,32 @@ +# OpenTelemetry + +This example shows how to configure OpenTelemetry on a client and server, and shows +what type of telemetry data it can produce for certain RPC's. + +## Try it + +``` +go run server/main.go +``` + +``` +go run client/main.go +``` + +``` +curl localhost:9464/metrics +curl localhost:9465/metrics +``` + +## Explanation + +The client continuously makes RPC's to a server. The client and server both +expose a prometheus exporter to listen and provide metrics. This defaults to +:9464 for the server and :9465 for the client. + +OpenTelemetry is configured on both the client and the server, and exports to +the Prometheus exporter. The exporter exposes metrics on the Prometheus ports +described above. + +Curling to the exposed Prometheus ports outputs the metrics recorded on the +client and server. diff --git a/examples/features/opentelemetry/client/main.go b/examples/features/opentelemetry/client/main.go new file mode 100644 index 000000000000..4bc3d5afb12b --- /dev/null +++ b/examples/features/opentelemetry/client/main.go @@ -0,0 +1,72 @@ +/* + * + * Copyright 2024 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 main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/stats/opentelemetry" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/sdk/metric" +) + +var ( + addr = flag.String("addr", ":50051", "the server address to connect to") + prometheusEndpoint = flag.String("prometheus_endpoint", ":9465", "the Prometheus exporter endpoint") +) + +func main() { + exporter, err := prometheus.New() + if err != nil { + log.Fatalf("Failed to start prometheus exporter: %v", err) + } + provider := metric.NewMeterProvider(metric.WithReader(exporter)) + go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler()) + + ctx := context.Background() + do := opentelemetry.DialOption(opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}}) + + cc, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()), do) + if err != nil { + log.Fatalf("Failed to start NewClient: %v", err) + } + defer cc.Close() + c := echo.NewEchoClient(cc) + + // Make a RPC every second. This should trigger telemetry to be emitted from + // the client and the server. + for { + r, err := c.UnaryEcho(ctx, &echo.EchoRequest{Message: "this is examples/opentelemetry"}) + if err != nil { + log.Fatalf("UnaryEcho failed: %v", err) + } + fmt.Println(r) + time.Sleep(time.Second) + } +} diff --git a/examples/features/opentelemetry/server/main.go b/examples/features/opentelemetry/server/main.go new file mode 100644 index 000000000000..ae1c91052200 --- /dev/null +++ b/examples/features/opentelemetry/server/main.go @@ -0,0 +1,74 @@ +/* + * + * Copyright 2024 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 main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "net/http" + + "google.golang.org/grpc" + pb "google.golang.org/grpc/examples/features/proto/echo" + "google.golang.org/grpc/stats/opentelemetry" + + "github.com/prometheus/client_golang/prometheus/promhttp" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/sdk/metric" +) + +var ( + addr = flag.String("addr", ":50051", "the server address to connect to") + prometheusEndpoint = flag.String("prometheus_endpoint", ":9464", "the Prometheus exporter endpoint") +) + +type echoServer struct { + pb.UnimplementedEchoServer + addr string +} + +func (s *echoServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { + return &pb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil +} + +func main() { + exporter, err := prometheus.New() + if err != nil { + log.Fatalf("Failed to start prometheus exporter: %v", err) + } + provider := metric.NewMeterProvider(metric.WithReader(exporter)) + go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler()) + + so := opentelemetry.ServerOption(opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}}) + + lis, err := net.Listen("tcp", *addr) + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + s := grpc.NewServer(so) + pb.RegisterEchoServer(s, &echoServer{addr: *addr}) + + log.Printf("Serving on %s\n", *addr) + + if err := s.Serve(lis); err != nil { + log.Fatalf("Failed to serve: %v", err) + } +}