From c96e0187341c13266f7c168c2ecd012a3fee663f Mon Sep 17 00:00:00 2001 From: Angith <32255855+Angith@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:01:09 +0530 Subject: [PATCH] chore: An example of instrumenting a gRPC client and server with Instana using instagrpc (#975) * chore: an example of instrumenting a gRPC client and server with Instana using instagrpc --- example/grpc/README.md | 29 ++++ example/grpc/client/main.go | 119 ++++++++++++++++ example/grpc/go.mod | 20 +++ example/grpc/go.sum | 37 +++++ example/grpc/hellopb/hello.pb.go | 194 ++++++++++++++++++++++++++ example/grpc/hellopb/hello_grpc.pb.go | 177 +++++++++++++++++++++++ example/grpc/proto/hello.proto | 26 ++++ example/grpc/server/main.go | 77 ++++++++++ 8 files changed, 679 insertions(+) create mode 100644 example/grpc/README.md create mode 100644 example/grpc/client/main.go create mode 100644 example/grpc/go.mod create mode 100644 example/grpc/go.sum create mode 100644 example/grpc/hellopb/hello.pb.go create mode 100644 example/grpc/hellopb/hello_grpc.pb.go create mode 100644 example/grpc/proto/hello.proto create mode 100644 example/grpc/server/main.go diff --git a/example/grpc/README.md b/example/grpc/README.md new file mode 100644 index 000000000..77950ebc2 --- /dev/null +++ b/example/grpc/README.md @@ -0,0 +1,29 @@ +# An Example For gRPC Instrumentation +============================ + +An example of instrumenting a gRPC client and server with Instana using [`github.com/instana/go-sensor/tree/main/instrumentation/instagrpc`](https://pkg.go.dev/github.com/instana/go-sensor/instrumentation/instagrpc). + + +## Usage + +Install the packages + +```bash +go mod tidy +``` + +Start the gRPC Server: + +```bash +go run server/main.go +``` + +Run the gRPC client + +```bash +go run client/main.go +``` + +## Output + +The client makes a unary, stream and an unknown call to the gRPC server. You will be able to see those 3 call traces in the Instana dashboard. \ No newline at end of file diff --git a/example/grpc/client/main.go b/example/grpc/client/main.go new file mode 100644 index 000000000..0b56358ef --- /dev/null +++ b/example/grpc/client/main.go @@ -0,0 +1,119 @@ +// (c) Copyright IBM Corp. 2024 + +//go:build go1.22 +// +build go1.22 + +package main + +import ( + "context" + "io" + "log" + "time" + + instana "github.com/instana/go-sensor" + pb "github.com/instana/go-sensor/example/grpc/hellopb" + "github.com/instana/go-sensor/instrumentation/instagrpc" + "github.com/opentracing/opentracing-go/ext" + "google.golang.org/grpc" +) + +func main() { + + sensor := instana.NewSensor("grpc-client") + + // Connect to the server. + conn, err := grpc.Dial("localhost:50051", + grpc.WithInsecure(), + grpc.WithUnaryInterceptor(instagrpc.UnaryClientInterceptor(sensor)), + grpc.WithStreamInterceptor(instagrpc.StreamClientInterceptor(sensor))) + if err != nil { + log.Fatalf("Failed to connect: %v", err) + } + defer conn.Close() + + client := pb.NewGreeterClient(conn) + + // Unary call + doUnaryCall(sensor, client) + + // Server-side streaming call + doStreamingCall(sensor, client) + + // Make a call to an unknown service/method. + doUnknownServiceCall(sensor, conn) + + time.Sleep(10 * time.Minute) +} + +func doUnknownServiceCall(sensor instana.TracerLogger, client *grpc.ClientConn) { + + sp := sensor.Tracer(). + StartSpan("grpc-unknown-service-call"). + SetTag(string(ext.SpanKind), "entry") + + sp.Finish() + + ctx := instana.ContextWithSpan(context.Background(), sp) + + // Invoke a non-existent method (this will trigger the UnknownServiceHandler). + err := client.Invoke(ctx, "/UnknownService/UnknownMethod", nil, nil) + if err != nil { + log.Printf("Error from server: %v", err) + } else { + log.Println("Call succeeded (unexpected).") + } +} + +func doUnaryCall(sensor instana.TracerLogger, client pb.GreeterClient) { + + sp := sensor.Tracer(). + StartSpan("grpc-unary-client-call"). + SetTag(string(ext.SpanKind), "entry") + + sp.Finish() + + ctx := instana.ContextWithSpan(context.Background(), sp) + + log.Println("Starting Unary Call...") + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + + resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "World"}) + if err != nil { + log.Fatalf("Unary call failed: %v", err) + } + log.Printf("Unary Response: %s", resp.GetMessage()) +} + +func doStreamingCall(sensor instana.TracerLogger, client pb.GreeterClient) { + + sp := sensor.Tracer(). + StartSpan("grpc-stream-client-call"). + SetTag(string(ext.SpanKind), "entry") + + sp.Finish() + + ctx := instana.ContextWithSpan(context.Background(), sp) + + log.Println("Starting Streaming Call...") + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + stream, err := client.SayHelloStream(ctx, &pb.HelloRequest{Name: "World"}) + if err != nil { + log.Fatalf("Streaming call failed: %v", err) + } + + for { + resp, err := stream.Recv() + if err == io.EOF { + log.Println("Streaming completed.") + break + } + if err != nil { + log.Fatalf("Error receiving stream: %v", err) + } + log.Printf("Streaming Response: %s", resp.GetMessage()) + } +} diff --git a/example/grpc/go.mod b/example/grpc/go.mod new file mode 100644 index 000000000..692f55029 --- /dev/null +++ b/example/grpc/go.mod @@ -0,0 +1,20 @@ +module github.com/instana/go-sensor/example/grpc + +go 1.22.7 + +require ( + github.com/instana/go-sensor v1.65.0 + github.com/instana/go-sensor/instrumentation/instagrpc v1.29.0 + github.com/opentracing/opentracing-go v1.2.0 + google.golang.org/grpc v1.68.0 + google.golang.org/protobuf v1.35.2 +) + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/looplab/fsm v1.0.1 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect +) diff --git a/example/grpc/go.sum b/example/grpc/go.sum new file mode 100644 index 000000000..7bade8c00 --- /dev/null +++ b/example/grpc/go.sum @@ -0,0 +1,37 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/instana/go-sensor v1.65.0 h1:tvghnyNsSE2Bj1nsARLwLFc7GIR9+NXN7QAjMRw6EHg= +github.com/instana/go-sensor v1.65.0/go.mod h1:Ngi6H3q4iZ6yn4EH9zQYUflI/eDTULrM3B+RW9HN4zI= +github.com/instana/go-sensor/instrumentation/instagrpc v1.29.0 h1:YwdOePmbhNS2tGp1zfsdfl6ZxjKw5D79m3i7YyUVkbI= +github.com/instana/go-sensor/instrumentation/instagrpc v1.29.0/go.mod h1:7nSl2l6iVw75UnW5oIe3q0CFEUGJ+z+4sjmir3GnWAg= +github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU= +github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/example/grpc/hellopb/hello.pb.go b/example/grpc/hellopb/hello.pb.go new file mode 100644 index 000000000..3f632357b --- /dev/null +++ b/example/grpc/hellopb/hello.pb.go @@ -0,0 +1,194 @@ +// (c) Copyright IBM Corp. 2024 + +//go:build go1.22 +// +build go1.22 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc v5.29.0 +// source: proto/hello.proto + +package hellopb + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// The request message containing the user's name. +type HelloRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *HelloRequest) Reset() { + *x = HelloRequest{} + mi := &file_proto_hello_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloRequest) ProtoMessage() {} + +func (x *HelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_hello_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. +func (*HelloRequest) Descriptor() ([]byte, []int) { + return file_proto_hello_proto_rawDescGZIP(), []int{0} +} + +func (x *HelloRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// The response message containing the greeting. +type HelloReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *HelloReply) Reset() { + *x = HelloReply{} + mi := &file_proto_hello_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelloReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelloReply) ProtoMessage() {} + +func (x *HelloReply) ProtoReflect() protoreflect.Message { + mi := &file_proto_hello_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. +func (*HelloReply) Descriptor() ([]byte, []int) { + return file_proto_hello_proto_rawDescGZIP(), []int{1} +} + +func (x *HelloReply) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_proto_hello_proto protoreflect.FileDescriptor + +var file_proto_hello_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, + 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x79, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, + 0x72, 0x12, 0x32, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x13, 0x2e, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3a, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, + 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x13, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, + 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x30, + 0x01, 0x42, 0x13, 0x5a, 0x11, 0x2e, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x70, 0x62, 0x3b, 0x68, + 0x65, 0x6c, 0x6c, 0x6f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_hello_proto_rawDescOnce sync.Once + file_proto_hello_proto_rawDescData = file_proto_hello_proto_rawDesc +) + +func file_proto_hello_proto_rawDescGZIP() []byte { + file_proto_hello_proto_rawDescOnce.Do(func() { + file_proto_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_hello_proto_rawDescData) + }) + return file_proto_hello_proto_rawDescData +} + +var file_proto_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_hello_proto_goTypes = []any{ + (*HelloRequest)(nil), // 0: hello.HelloRequest + (*HelloReply)(nil), // 1: hello.HelloReply +} +var file_proto_hello_proto_depIdxs = []int32{ + 0, // 0: hello.Greeter.SayHello:input_type -> hello.HelloRequest + 0, // 1: hello.Greeter.SayHelloStream:input_type -> hello.HelloRequest + 1, // 2: hello.Greeter.SayHello:output_type -> hello.HelloReply + 1, // 3: hello.Greeter.SayHelloStream:output_type -> hello.HelloReply + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_hello_proto_init() } +func file_proto_hello_proto_init() { + if File_proto_hello_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_hello_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_hello_proto_goTypes, + DependencyIndexes: file_proto_hello_proto_depIdxs, + MessageInfos: file_proto_hello_proto_msgTypes, + }.Build() + File_proto_hello_proto = out.File + file_proto_hello_proto_rawDesc = nil + file_proto_hello_proto_goTypes = nil + file_proto_hello_proto_depIdxs = nil +} diff --git a/example/grpc/hellopb/hello_grpc.pb.go b/example/grpc/hellopb/hello_grpc.pb.go new file mode 100644 index 000000000..81ccf4c62 --- /dev/null +++ b/example/grpc/hellopb/hello_grpc.pb.go @@ -0,0 +1,177 @@ +// (c) Copyright IBM Corp. 2024 + +//go:build go1.22 +// +build go1.22 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.0 +// source: proto/hello.proto + +package hellopb + +import ( + context "context" + + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Greeter_SayHello_FullMethodName = "/hello.Greeter/SayHello" + Greeter_SayHelloStream_FullMethodName = "/hello.Greeter/SayHelloStream" +) + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The greeting service definition. +type GreeterClient interface { + // Unary RPC + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) + // Server-side streaming RPC + SayHelloStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelloReply], error) +} + +type greeterClient struct { + cc grpc.ClientConnInterface +} + +func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HelloReply) + err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelloReply], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], Greeter_SayHelloStream_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[HelloRequest, HelloReply]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Greeter_SayHelloStreamClient = grpc.ServerStreamingClient[HelloReply] + +// GreeterServer is the server API for Greeter service. +// All implementations must embed UnimplementedGreeterServer +// for forward compatibility. +// +// The greeting service definition. +type GreeterServer interface { + // Unary RPC + SayHello(context.Context, *HelloRequest) (*HelloReply, error) + // Server-side streaming RPC + SayHelloStream(*HelloRequest, grpc.ServerStreamingServer[HelloReply]) error + mustEmbedUnimplementedGreeterServer() +} + +// UnimplementedGreeterServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGreeterServer struct{} + +func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} +func (UnimplementedGreeterServer) SayHelloStream(*HelloRequest, grpc.ServerStreamingServer[HelloReply]) error { + return status.Errorf(codes.Unimplemented, "method SayHelloStream not implemented") +} +func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} +func (UnimplementedGreeterServer) testEmbeddedByValue() {} + +// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GreeterServer will +// result in compilation errors. +type UnsafeGreeterServer interface { + mustEmbedUnimplementedGreeterServer() +} + +func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { + // If the following call pancis, it indicates UnimplementedGreeterServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Greeter_ServiceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Greeter_SayHello_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Greeter_SayHelloStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(HelloRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GreeterServer).SayHelloStream(m, &grpc.GenericServerStream[HelloRequest, HelloReply]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type Greeter_SayHelloStreamServer = grpc.ServerStreamingServer[HelloReply] + +// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Greeter_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hello.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SayHelloStream", + Handler: _Greeter_SayHelloStream_Handler, + ServerStreams: true, + }, + }, + Metadata: "proto/hello.proto", +} diff --git a/example/grpc/proto/hello.proto b/example/grpc/proto/hello.proto new file mode 100644 index 000000000..ebdb98254 --- /dev/null +++ b/example/grpc/proto/hello.proto @@ -0,0 +1,26 @@ +// (c) Copyright IBM Corp. 2024 + +syntax = "proto3"; + +package hello; + +option go_package = "./hellopb;hellopb"; + +// The greeting service definition. +service Greeter { + // Unary RPC + rpc SayHello (HelloRequest) returns (HelloReply); + + // Server-side streaming RPC + rpc SayHelloStream (HelloRequest) returns (stream HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greeting. +message HelloReply { + string message = 1; +} diff --git a/example/grpc/server/main.go b/example/grpc/server/main.go new file mode 100644 index 000000000..7bc543cdc --- /dev/null +++ b/example/grpc/server/main.go @@ -0,0 +1,77 @@ +// (c) Copyright IBM Corp. 2024 + +//go:build go1.22 +// +build go1.22 + +package main + +import ( + "context" + "fmt" + "log" + "net" + "time" + + instana "github.com/instana/go-sensor" + pb "github.com/instana/go-sensor/example/grpc/hellopb" + "github.com/instana/go-sensor/instrumentation/instagrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Server is used to implement the Greeter service. +type Server struct { + pb.UnimplementedGreeterServer +} + +// SayHello implements the unary RPC. +func (s *Server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { + log.Printf("Unary call received: %v", req.GetName()) + return &pb.HelloReply{Message: "Hello " + req.GetName()}, nil +} + +// SayHelloStream implements the server-side streaming RPC. +func (s *Server) SayHelloStream(req *pb.HelloRequest, stream pb.Greeter_SayHelloStreamServer) error { + log.Printf("Streaming call received: %v", req.GetName()) + + for i := 1; i <= 5; i++ { + msg := &pb.HelloReply{Message: req.GetName() + ", this is message #" + fmt.Sprintf("%d", i)} + if err := stream.Send(msg); err != nil { + return err + } + time.Sleep(1 * time.Second) + } + return nil +} + +func main() { + + sensor := instana.NewSensor("grpc-server") + + lis, err := net.Listen("tcp", ":50051") + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + + // Define the UnknownServiceHandler. + unknownHandler := func(srv interface{}, stream grpc.ServerStream) error { + methodName, _ := grpc.MethodFromServerStream(stream) + log.Printf("Received call to unknown service/method: %s", methodName) + + return status.Errorf(codes.Unimplemented, "Service/method %s is not implemented on this server", methodName) + } + + grpcServer := grpc.NewServer( + grpc.UnknownServiceHandler(unknownHandler), + grpc.UnaryInterceptor(instagrpc.UnaryServerInterceptor(sensor)), + grpc.StreamInterceptor(instagrpc.StreamServerInterceptor(sensor)), + ) + + pb.RegisterGreeterServer(grpcServer, &Server{}) + + log.Println("Server is listening on port 50051...") + if err := grpcServer.Serve(lis); err != nil { + log.Fatalf("Failed to serve: %v", err) + } +}