diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8df94a2089e..79a88438980 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -49,6 +49,10 @@ updates: directory: "/instrumentation/go.mongodb.org/mongo-driver" # Location of package manifests schedule: interval: "daily" + - package-ecosystem: "gomod" # See documentation for possible values + directory: "/instrumentation/google.golang.org/grpc" # Location of package manifests + schedule: + interval: "daily" - package-ecosystem: "gomod" # See documentation for possible values directory: "/instrumentation/gopkg.in/macaron.v1" # Location of package manifests schedule: diff --git a/.gitignore b/.gitignore index fb52920f3a5..a89a24ec5e4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ Thumbs.db *.so coverage.* +instrumentation/google.golang.org/grpc/example/server/server +instrumentation/google.golang.org/grpc/example/client/client diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ff1938f79..3c23f32a37a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- The `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc` module has been added to replace the instrumentation that had previoiusly existed in the `go.opentelemetry.io/otel/instrumentation/grpctrace` package. (#189) + ## [0.10.0] - 2020-07-31 This release upgrades its [go.opentelemetry.io/otel](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v0.10.0) dependency to v0.10.0 and includes new instrumentation for popular Kafka and Cassandra clients. diff --git a/instrumentation/google.golang.org/grpc/example/README.md b/instrumentation/google.golang.org/grpc/example/README.md new file mode 100644 index 00000000000..ff53b6cc7bc --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/README.md @@ -0,0 +1,28 @@ +# gRPC Tracing Example + +Traces client and server calls via interceptors. + +### Compile .proto + +Only required if the service definition (.proto) changes. + +```sh +cd ./example/grpc + +# protobuf v1.3.2 +protoc -I api --go_out=plugins=grpc,paths=source_relative:./api api/hello-service.proto +``` + +### Run server + +```sh +cd ./example/grpc + +go run ./server +``` + +### Run client + +```sh +go run ./client +``` \ No newline at end of file diff --git a/instrumentation/google.golang.org/grpc/example/api/hello-service.pb.go b/instrumentation/google.golang.org/grpc/example/api/hello-service.pb.go new file mode 100644 index 00000000000..e6ec648f829 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/api/hello-service.pb.go @@ -0,0 +1,354 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hello-service.proto + +/* +Package api is a generated protocol buffer package. + +It is generated from these files: + hello-service.proto + +It has these top-level messages: + HelloRequest + HelloResponse +*/ +package api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type HelloRequest struct { + Greeting string `protobuf:"bytes,1,opt,name=greeting" json:"greeting,omitempty"` +} + +func (m *HelloRequest) Reset() { *m = HelloRequest{} } +func (m *HelloRequest) String() string { return proto.CompactTextString(m) } +func (*HelloRequest) ProtoMessage() {} +func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *HelloRequest) GetGreeting() string { + if m != nil { + return m.Greeting + } + return "" +} + +type HelloResponse struct { + Reply string `protobuf:"bytes,1,opt,name=reply" json:"reply,omitempty"` +} + +func (m *HelloResponse) Reset() { *m = HelloResponse{} } +func (m *HelloResponse) String() string { return proto.CompactTextString(m) } +func (*HelloResponse) ProtoMessage() {} +func (*HelloResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *HelloResponse) GetReply() string { + if m != nil { + return m.Reply + } + return "" +} + +func init() { + proto.RegisterType((*HelloRequest)(nil), "api.HelloRequest") + proto.RegisterType((*HelloResponse)(nil), "api.HelloResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for HelloService service + +type HelloServiceClient interface { + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) + SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) + SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) + SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) +} + +type helloServiceClient struct { + cc *grpc.ClientConn +} + +func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient { + return &helloServiceClient{cc} +} + +func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) { + out := new(HelloResponse) + err := grpc.Invoke(ctx, "/api.HelloService/SayHello", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *helloServiceClient) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (HelloService_SayHelloServerStreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[0], c.cc, "/api.HelloService/SayHelloServerStream", opts...) + if err != nil { + return nil, err + } + x := &helloServiceSayHelloServerStreamClient{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 +} + +type HelloService_SayHelloServerStreamClient interface { + Recv() (*HelloResponse, error) + grpc.ClientStream +} + +type helloServiceSayHelloServerStreamClient struct { + grpc.ClientStream +} + +func (x *helloServiceSayHelloServerStreamClient) Recv() (*HelloResponse, error) { + m := new(HelloResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *helloServiceClient) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloClientStreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[1], c.cc, "/api.HelloService/SayHelloClientStream", opts...) + if err != nil { + return nil, err + } + x := &helloServiceSayHelloClientStreamClient{stream} + return x, nil +} + +type HelloService_SayHelloClientStreamClient interface { + Send(*HelloRequest) error + CloseAndRecv() (*HelloResponse, error) + grpc.ClientStream +} + +type helloServiceSayHelloClientStreamClient struct { + grpc.ClientStream +} + +func (x *helloServiceSayHelloClientStreamClient) Send(m *HelloRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *helloServiceSayHelloClientStreamClient) CloseAndRecv() (*HelloResponse, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(HelloResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *helloServiceClient) SayHelloBidiStream(ctx context.Context, opts ...grpc.CallOption) (HelloService_SayHelloBidiStreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_HelloService_serviceDesc.Streams[2], c.cc, "/api.HelloService/SayHelloBidiStream", opts...) + if err != nil { + return nil, err + } + x := &helloServiceSayHelloBidiStreamClient{stream} + return x, nil +} + +type HelloService_SayHelloBidiStreamClient interface { + Send(*HelloRequest) error + Recv() (*HelloResponse, error) + grpc.ClientStream +} + +type helloServiceSayHelloBidiStreamClient struct { + grpc.ClientStream +} + +func (x *helloServiceSayHelloBidiStreamClient) Send(m *HelloRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *helloServiceSayHelloBidiStreamClient) Recv() (*HelloResponse, error) { + m := new(HelloResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for HelloService service + +type HelloServiceServer interface { + SayHello(context.Context, *HelloRequest) (*HelloResponse, error) + SayHelloServerStream(*HelloRequest, HelloService_SayHelloServerStreamServer) error + SayHelloClientStream(HelloService_SayHelloClientStreamServer) error + SayHelloBidiStream(HelloService_SayHelloBidiStreamServer) error +} + +func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { + s.RegisterService(&_HelloService_serviceDesc, srv) +} + +func _HelloService_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.(HelloServiceServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/api.HelloService/SayHello", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HelloService_SayHelloServerStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(HelloRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(HelloServiceServer).SayHelloServerStream(m, &helloServiceSayHelloServerStreamServer{stream}) +} + +type HelloService_SayHelloServerStreamServer interface { + Send(*HelloResponse) error + grpc.ServerStream +} + +type helloServiceSayHelloServerStreamServer struct { + grpc.ServerStream +} + +func (x *helloServiceSayHelloServerStreamServer) Send(m *HelloResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _HelloService_SayHelloClientStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(HelloServiceServer).SayHelloClientStream(&helloServiceSayHelloClientStreamServer{stream}) +} + +type HelloService_SayHelloClientStreamServer interface { + SendAndClose(*HelloResponse) error + Recv() (*HelloRequest, error) + grpc.ServerStream +} + +type helloServiceSayHelloClientStreamServer struct { + grpc.ServerStream +} + +func (x *helloServiceSayHelloClientStreamServer) SendAndClose(m *HelloResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *helloServiceSayHelloClientStreamServer) Recv() (*HelloRequest, error) { + m := new(HelloRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _HelloService_SayHelloBidiStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(HelloServiceServer).SayHelloBidiStream(&helloServiceSayHelloBidiStreamServer{stream}) +} + +type HelloService_SayHelloBidiStreamServer interface { + Send(*HelloResponse) error + Recv() (*HelloRequest, error) + grpc.ServerStream +} + +type helloServiceSayHelloBidiStreamServer struct { + grpc.ServerStream +} + +func (x *helloServiceSayHelloBidiStreamServer) Send(m *HelloResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *helloServiceSayHelloBidiStreamServer) Recv() (*HelloRequest, error) { + m := new(HelloRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _HelloService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "api.HelloService", + HandlerType: (*HelloServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _HelloService_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "SayHelloServerStream", + Handler: _HelloService_SayHelloServerStream_Handler, + ServerStreams: true, + }, + { + StreamName: "SayHelloClientStream", + Handler: _HelloService_SayHelloClientStream_Handler, + ClientStreams: true, + }, + { + StreamName: "SayHelloBidiStream", + Handler: _HelloService_SayHelloBidiStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "hello-service.proto", +} + +func init() { proto.RegisterFile("hello-service.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 192 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xce, 0x48, 0xcd, 0xc9, + 0xc9, 0xd7, 0x2d, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, + 0x62, 0x4e, 0x2c, 0xc8, 0x54, 0xd2, 0xe2, 0xe2, 0xf1, 0x00, 0xc9, 0x05, 0xa5, 0x16, 0x96, 0xa6, + 0x16, 0x97, 0x08, 0x49, 0x71, 0x71, 0xa4, 0x17, 0xa5, 0xa6, 0x96, 0x64, 0xe6, 0xa5, 0x4b, 0x30, + 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xf9, 0x4a, 0xaa, 0x5c, 0xbc, 0x50, 0xb5, 0xc5, 0x05, 0xf9, + 0x79, 0xc5, 0xa9, 0x42, 0x22, 0x5c, 0xac, 0x45, 0xa9, 0x05, 0x39, 0x95, 0x50, 0x95, 0x10, 0x8e, + 0x51, 0x0b, 0x13, 0xd4, 0xcc, 0x60, 0x88, 0x75, 0x42, 0x86, 0x5c, 0x1c, 0xc1, 0x89, 0x95, 0x60, + 0x21, 0x21, 0x41, 0xbd, 0xc4, 0x82, 0x4c, 0x3d, 0x64, 0x2b, 0xa5, 0x84, 0x90, 0x85, 0xa0, 0x26, + 0xdb, 0x73, 0x89, 0xc0, 0xb4, 0x80, 0x4c, 0x49, 0x2d, 0x0a, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0x25, + 0x52, 0xbb, 0x01, 0x23, 0xb2, 0x01, 0xce, 0x39, 0x99, 0xa9, 0x79, 0x25, 0x24, 0x19, 0xa0, 0x01, + 0x32, 0x40, 0x08, 0x66, 0x80, 0x53, 0x66, 0x4a, 0x26, 0x89, 0xda, 0x0d, 0x18, 0x93, 0xd8, 0xc0, + 0xa1, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xd5, 0x1c, 0xd2, 0x7c, 0x01, 0x00, 0x00, +} diff --git a/instrumentation/google.golang.org/grpc/example/api/hello-service.proto b/instrumentation/google.golang.org/grpc/example/api/hello-service.proto new file mode 100644 index 00000000000..699645127e7 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/api/hello-service.proto @@ -0,0 +1,34 @@ +// 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. + +syntax = "proto3"; +package api; + +service HelloService { + rpc SayHello (HelloRequest) returns (HelloResponse); + + rpc SayHelloServerStream (HelloRequest) returns (stream HelloResponse); + + rpc SayHelloClientStream (stream HelloRequest) returns (HelloResponse); + + rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloResponse); +} + +message HelloRequest { + string greeting = 1; +} + +message HelloResponse { + string reply = 1; +} diff --git a/instrumentation/google.golang.org/grpc/example/client/main.go b/instrumentation/google.golang.org/grpc/example/client/main.go new file mode 100644 index 00000000000..8ac4933e7f8 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/client/main.go @@ -0,0 +1,183 @@ +// 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. + +package main + +import ( + "context" + "io" + "log" + "time" + + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/example/grpc/api" + "go.opentelemetry.io/otel/example/grpc/config" + + grpcotel "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +func main() { + config.Init() + + var conn *grpc.ClientConn + conn, err := grpc.Dial(":7777", grpc.WithInsecure(), + grpc.WithUnaryInterceptor(grpcotel.UnaryClientInterceptor(global.Tracer(""))), + grpc.WithStreamInterceptor(grpcotel.StreamClientInterceptor(global.Tracer(""))), + ) + + if err != nil { + log.Fatalf("did not connect: %s", err) + } + defer func() { _ = conn.Close() }() + + c := api.NewHelloServiceClient(conn) + + callSayHello(c) + callSayHelloClientStream(c) + callSayHelloServerStream(c) + callSayHelloBidiStream(c) + + time.Sleep(10 * time.Millisecond) +} + +func callSayHello(c api.HelloServiceClient) { + md := metadata.Pairs( + "timestamp", time.Now().Format(time.StampNano), + "client-id", "web-api-client-us-east-1", + "user-id", "some-test-user-id", + ) + + ctx := metadata.NewOutgoingContext(context.Background(), md) + response, err := c.SayHello(ctx, &api.HelloRequest{Greeting: "World"}) + if err != nil { + log.Fatalf("Error when calling SayHello: %s", err) + } + log.Printf("Response from server: %s", response.Reply) +} + +func callSayHelloClientStream(c api.HelloServiceClient) { + md := metadata.Pairs( + "timestamp", time.Now().Format(time.StampNano), + "client-id", "web-api-client-us-east-1", + "user-id", "some-test-user-id", + ) + + ctx := metadata.NewOutgoingContext(context.Background(), md) + stream, err := c.SayHelloClientStream(ctx) + if err != nil { + log.Fatalf("Error when opening SayHelloClientStream: %s", err) + } + + for i := 0; i < 5; i++ { + err := stream.Send(&api.HelloRequest{Greeting: "World"}) + + time.Sleep(time.Duration(i*50) * time.Millisecond) + + if err != nil { + log.Fatalf("Error when sending to SayHelloClientStream: %s", err) + } + } + + response, err := stream.CloseAndRecv() + if err != nil { + log.Fatalf("Error when closing SayHelloClientStream: %s", err) + } + + log.Printf("Response from server: %s", response.Reply) +} + +func callSayHelloServerStream(c api.HelloServiceClient) { + md := metadata.Pairs( + "timestamp", time.Now().Format(time.StampNano), + "client-id", "web-api-client-us-east-1", + "user-id", "some-test-user-id", + ) + + ctx := metadata.NewOutgoingContext(context.Background(), md) + stream, err := c.SayHelloServerStream(ctx, &api.HelloRequest{Greeting: "World"}) + if err != nil { + log.Fatalf("Error when opening SayHelloServerStream: %s", err) + } + + for { + response, err := stream.Recv() + if err == io.EOF { + break + } else if err != nil { + log.Fatalf("Error when receiving from SayHelloServerStream: %s", err) + } + + log.Printf("Response from server: %s", response.Reply) + time.Sleep(50 * time.Millisecond) + } +} + +func callSayHelloBidiStream(c api.HelloServiceClient) { + md := metadata.Pairs( + "timestamp", time.Now().Format(time.StampNano), + "client-id", "web-api-client-us-east-1", + "user-id", "some-test-user-id", + ) + + ctx := metadata.NewOutgoingContext(context.Background(), md) + stream, err := c.SayHelloBidiStream(ctx) + if err != nil { + log.Fatalf("Error when opening SayHelloBidiStream: %s", err) + } + + serverClosed := make(chan struct{}) + clientClosed := make(chan struct{}) + + go func() { + for i := 0; i < 5; i++ { + err := stream.Send(&api.HelloRequest{Greeting: "World"}) + + if err != nil { + log.Fatalf("Error when sending to SayHelloBidiStream: %s", err) + } + + time.Sleep(50 * time.Millisecond) + } + + err := stream.CloseSend() + if err != nil { + log.Fatalf("Error when closing SayHelloBidiStream: %s", err) + } + + clientClosed <- struct{}{} + }() + + go func() { + for { + response, err := stream.Recv() + if err == io.EOF { + break + } else if err != nil { + log.Fatalf("Error when receiving from SayHelloBidiStream: %s", err) + } + + log.Printf("Response from server: %s", response.Reply) + time.Sleep(50 * time.Millisecond) + } + + serverClosed <- struct{}{} + }() + + // Wait until client and server both closed the connection. + <-clientClosed + <-serverClosed +} diff --git a/instrumentation/google.golang.org/grpc/example/config/config.go b/instrumentation/google.golang.org/grpc/example/config/config.go new file mode 100644 index 00000000000..9c1034e7ebf --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/config/config.go @@ -0,0 +1,39 @@ +// 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. + +package config + +import ( + "log" + + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/exporters/stdout" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +// Init configures an OpenTelemetry exporter and trace provider +func Init() { + exporter, err := stdout.NewExporter(stdout.WithPrettyPrint()) + if err != nil { + log.Fatal(err) + } + tp, err := sdktrace.NewProvider( + sdktrace.WithConfig(sdktrace.Config{DefaultSampler: sdktrace.AlwaysSample()}), + sdktrace.WithSyncer(exporter), + ) + if err != nil { + log.Fatal(err) + } + global.SetTraceProvider(tp) +} diff --git a/instrumentation/google.golang.org/grpc/example/go.mod b/instrumentation/google.golang.org/grpc/example/go.mod new file mode 100644 index 00000000000..7bf5376d5fb --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/go.mod @@ -0,0 +1,16 @@ +module go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/example + +go 1.14 + +replace go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc => ../ + +require ( + github.com/golang/protobuf v1.4.2 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc v0.10.0 + go.opentelemetry.io/otel v0.10.0 + go.opentelemetry.io/otel/example/grpc v0.10.0 + go.opentelemetry.io/otel/exporters/stdout v0.10.0 + go.opentelemetry.io/otel/sdk v0.10.0 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 + google.golang.org/grpc v1.31.0 +) diff --git a/instrumentation/google.golang.org/grpc/example/go.sum b/instrumentation/google.golang.org/grpc/example/go.sum new file mode 100644 index 00000000000..dc5c89756a6 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/go.sum @@ -0,0 +1,114 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/sketches-go v0.0.1 h1:RtG+76WKgZuz6FIaGsjoPePmadDBkuD/KC6+ZWu78b8= +github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= +github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v0.10.0 h1:2y/HYj1dIfG1nPh0Z15X4se8WwYWuTyKHLSgRb/mbQ0= +go.opentelemetry.io/otel v0.10.0/go.mod h1:n3v1JGUBpn5DafiF1UeoDs5fr5XZMG+43kigDtFB8Vk= +go.opentelemetry.io/otel/example/grpc v0.10.0 h1:YTeYhq5zgFNSjqHrXs6+SLSwAIJoA39JxjxF5Mv75L4= +go.opentelemetry.io/otel/example/grpc v0.10.0/go.mod h1:J3WIuF7m8Tz5+cQWbXZuT2IJBlka49xn6RJZDD/lxqs= +go.opentelemetry.io/otel/exporters/stdout v0.10.0 h1:5dhUv/AMKF+9p2igV0pAmS7sWQvX0r+eimf7uiEDWd8= +go.opentelemetry.io/otel/exporters/stdout v0.10.0/go.mod h1:c7hVyiDzqbxgcerYbLreBNI0+MNE8x/hbekVx3lu+gM= +go.opentelemetry.io/otel/sdk v0.10.0 h1:iQWVDfmGB+5TjbrO9yFlezGCWBaJ73vxJTHB+ttdTQk= +go.opentelemetry.io/otel/sdk v0.10.0/go.mod h1:T5752PMr00aUHAVEbaDAYU5tzM2PWOmyy7Lc5OzSrs8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/instrumentation/google.golang.org/grpc/example/server/main.go b/instrumentation/google.golang.org/grpc/example/server/main.go new file mode 100644 index 00000000000..e3f81e47cb5 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example/server/main.go @@ -0,0 +1,129 @@ +// 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. + +package main + +import ( + "context" + "fmt" + "io" + "log" + "net" + "time" + + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/example/grpc/api" + "go.opentelemetry.io/otel/example/grpc/config" + + grpcotel "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc" + + "google.golang.org/grpc" +) + +const ( + port = ":7777" +) + +// server is used to implement api.HelloServiceServer +type server struct { + api.HelloServiceServer +} + +// SayHello implements api.HelloServiceServer +func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloResponse, error) { + log.Printf("Received: %v\n", in.GetGreeting()) + time.Sleep(50 * time.Millisecond) + + return &api.HelloResponse{Reply: "Hello " + in.Greeting}, nil +} + +func (s *server) SayHelloServerStream(in *api.HelloRequest, out api.HelloService_SayHelloServerStreamServer) error { + log.Printf("Received: %v\n", in.GetGreeting()) + + for i := 0; i < 5; i++ { + err := out.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) + if err != nil { + return err + } + + time.Sleep(time.Duration(i*50) * time.Millisecond) + } + + return nil +} + +func (s *server) SayHelloClientStream(stream api.HelloService_SayHelloClientStreamServer) error { + i := 0 + + for { + in, err := stream.Recv() + + if err == io.EOF { + break + } else if err != nil { + log.Printf("Non EOF error: %v\n", err) + return err + } + + log.Printf("Received: %v\n", in.GetGreeting()) + i++ + } + + time.Sleep(50 * time.Millisecond) + + return stream.SendAndClose(&api.HelloResponse{Reply: fmt.Sprintf("Hello (%v times)", i)}) +} + +func (s *server) SayHelloBidiStream(stream api.HelloService_SayHelloBidiStreamServer) error { + for { + in, err := stream.Recv() + + if err == io.EOF { + break + } else if err != nil { + log.Printf("Non EOF error: %v\n", err) + return err + } + + time.Sleep(50 * time.Millisecond) + + log.Printf("Received: %v\n", in.GetGreeting()) + err = stream.Send(&api.HelloResponse{Reply: "Hello " + in.Greeting}) + + if err != nil { + return err + } + } + + return nil +} + +func main() { + config.Init() + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + s := grpc.NewServer( + grpc.UnaryInterceptor(grpcotel.UnaryServerInterceptor(global.Tracer(""))), + grpc.StreamInterceptor(grpcotel.StreamServerInterceptor(global.Tracer(""))), + ) + + api.RegisterHelloServiceServer(s, &server{}) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} diff --git a/instrumentation/google.golang.org/grpc/example_interceptor_test.go b/instrumentation/google.golang.org/grpc/example_interceptor_test.go new file mode 100644 index 00000000000..6f63b4fb6c6 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/example_interceptor_test.go @@ -0,0 +1,51 @@ +// 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. + +package grpc + +import ( + "google.golang.org/grpc" + + "go.opentelemetry.io/otel/api/global" +) + +func ExampleStreamClientInterceptor() { + tracer := global.Tracer("client-instrumentation") + _, _ = grpc.Dial( + "localhost", + grpc.WithStreamInterceptor(StreamClientInterceptor(tracer)), + ) +} + +func ExampleUnaryClientInterceptor() { + tracer := global.Tracer("client-instrumentation") + _, _ = grpc.Dial( + "localhost", + grpc.WithUnaryInterceptor(UnaryClientInterceptor(tracer)), + ) +} + +func ExampleStreamServerInterceptor() { + tracer := global.Tracer("server-instrumentation") + _ = grpc.NewServer( + grpc.StreamInterceptor(StreamServerInterceptor(tracer)), + ) +} + +func ExampleUnaryServerInterceptor() { + tracer := global.Tracer("server-instrumentation") + _ = grpc.NewServer( + grpc.UnaryInterceptor(UnaryServerInterceptor(tracer)), + ) +} diff --git a/instrumentation/google.golang.org/grpc/go.mod b/instrumentation/google.golang.org/grpc/go.mod new file mode 100644 index 00000000000..2d7e13af4c4 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/go.mod @@ -0,0 +1,10 @@ +module go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc + +go 1.14 + +require ( + github.com/golang/protobuf v1.4.2 + github.com/stretchr/testify v1.6.1 + go.opentelemetry.io/otel v0.10.0 + google.golang.org/grpc v1.31.0 +) diff --git a/instrumentation/google.golang.org/grpc/go.sum b/instrumentation/google.golang.org/grpc/go.sum new file mode 100644 index 00000000000..cfaa310cdcb --- /dev/null +++ b/instrumentation/google.golang.org/grpc/go.sum @@ -0,0 +1,97 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v0.10.0 h1:2y/HYj1dIfG1nPh0Z15X4se8WwYWuTyKHLSgRb/mbQ0= +go.opentelemetry.io/otel v0.10.0/go.mod h1:n3v1JGUBpn5DafiF1UeoDs5fr5XZMG+43kigDtFB8Vk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 h1:4HYDjxeNXAOTv3o1N2tjo8UUSlhQgAD52FVkwxnWgM8= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/instrumentation/google.golang.org/grpc/grpctrace.go b/instrumentation/google.golang.org/grpc/grpctrace.go new file mode 100644 index 00000000000..c75308eb8d4 --- /dev/null +++ b/instrumentation/google.golang.org/grpc/grpctrace.go @@ -0,0 +1,95 @@ +// 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. + +package grpc + +import ( + "context" + + "google.golang.org/grpc/metadata" + + "go.opentelemetry.io/otel/api/correlation" + "go.opentelemetry.io/otel/api/global" + "go.opentelemetry.io/otel/api/kv" + "go.opentelemetry.io/otel/api/propagation" + "go.opentelemetry.io/otel/api/trace" +) + +// Option is a function that allows configuration of the grpc Extract() +// and Inject() functions +type Option func(*config) + +type config struct { + propagators propagation.Propagators +} + +func newConfig(opts []Option) *config { + c := &config{propagators: global.Propagators()} + for _, o := range opts { + o(c) + } + return c +} + +// WithPropagators sets the propagators to use for Extraction and Injection +func WithPropagators(props propagation.Propagators) Option { + return func(c *config) { + c.propagators = props + } +} + +type metadataSupplier struct { + metadata *metadata.MD +} + +func (s *metadataSupplier) Get(key string) string { + values := s.metadata.Get(key) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (s *metadataSupplier) Set(key string, value string) { + s.metadata.Set(key, value) +} + +// Inject injects correlation context and span context into the gRPC +// metadata object. This function is meant to be used on outgoing +// requests. +func Inject(ctx context.Context, metadata *metadata.MD, opts ...Option) { + c := newConfig(opts) + propagation.InjectHTTP(ctx, c.propagators, &metadataSupplier{ + metadata: metadata, + }) +} + +// Extract returns the correlation context and span context that +// another service encoded in the gRPC metadata object with Inject. +// This function is meant to be used on incoming requests. +func Extract(ctx context.Context, metadata *metadata.MD, opts ...Option) ([]kv.KeyValue, trace.SpanContext) { + c := newConfig(opts) + ctx = propagation.ExtractHTTP(ctx, c.propagators, &metadataSupplier{ + metadata: metadata, + }) + + spanContext := trace.RemoteSpanContextFromContext(ctx) + var correlationCtxKVs []kv.KeyValue + correlation.MapFromContext(ctx).Foreach(func(kv kv.KeyValue) bool { + correlationCtxKVs = append(correlationCtxKVs, kv) + return true + }) + + return correlationCtxKVs, spanContext +} diff --git a/instrumentation/google.golang.org/grpc/interceptor.go b/instrumentation/google.golang.org/grpc/interceptor.go new file mode 100644 index 00000000000..ffdb08cf1db --- /dev/null +++ b/instrumentation/google.golang.org/grpc/interceptor.go @@ -0,0 +1,458 @@ +// 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. + +package grpc + +// gRPC tracing middleware +// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md +import ( + "context" + "io" + "net" + "strings" + + "github.com/golang/protobuf/proto" //nolint:staticcheck + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" + + "go.opentelemetry.io/otel/api/correlation" + "go.opentelemetry.io/otel/api/kv" + "go.opentelemetry.io/otel/api/standard" + "go.opentelemetry.io/otel/api/trace" +) + +type messageType kv.KeyValue + +// Event adds an event of the messageType to the span associated with the +// passed context with id and size (if message is a proto message). +func (m messageType) Event(ctx context.Context, id int, message interface{}) { + span := trace.SpanFromContext(ctx) + if p, ok := message.(proto.Message); ok { + span.AddEvent(ctx, "message", + kv.KeyValue(m), + standard.RPCMessageIDKey.Int(id), + standard.RPCMessageUncompressedSizeKey.Int(proto.Size(p)), + ) + } else { + span.AddEvent(ctx, "message", + kv.KeyValue(m), + standard.RPCMessageIDKey.Int(id), + ) + } +} + +var ( + messageSent = messageType(standard.RPCMessageTypeSent) + messageReceived = messageType(standard.RPCMessageTypeReceived) +) + +// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable +// for use in a grpc.Dial call. +func UnaryClientInterceptor(tracer trace.Tracer, opts ...Option) grpc.UnaryClientInterceptor { + return func( + ctx context.Context, + method string, + req, reply interface{}, + cc *grpc.ClientConn, + invoker grpc.UnaryInvoker, + callOpts ...grpc.CallOption, + ) error { + requestMetadata, _ := metadata.FromOutgoingContext(ctx) + metadataCopy := requestMetadata.Copy() + + name, attr := spanInfo(method, cc.Target()) + var span trace.Span + ctx, span = tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attr...), + ) + defer span.End() + + Inject(ctx, &metadataCopy, opts...) + ctx = metadata.NewOutgoingContext(ctx, metadataCopy) + + messageSent.Event(ctx, 1, req) + + err := invoker(ctx, method, req, reply, cc, callOpts...) + + messageReceived.Event(ctx, 1, reply) + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(s.Code(), s.Message()) + } + + return err + } +} + +type streamEventType int + +type streamEvent struct { + Type streamEventType + Err error +} + +const ( + closeEvent streamEventType = iota + receiveEndEvent + errorEvent +) + +// clientStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and +// SendMsg method call. +type clientStream struct { + grpc.ClientStream + + desc *grpc.StreamDesc + events chan streamEvent + eventsDone chan struct{} + finished chan error + + receivedMessageID int + sentMessageID int +} + +var _ = proto.Marshal + +func (w *clientStream) RecvMsg(m interface{}) error { + err := w.ClientStream.RecvMsg(m) + + if err == nil && !w.desc.ServerStreams { + w.sendStreamEvent(receiveEndEvent, nil) + } else if err == io.EOF { + w.sendStreamEvent(receiveEndEvent, nil) + } else if err != nil { + w.sendStreamEvent(errorEvent, err) + } else { + w.receivedMessageID++ + messageReceived.Event(w.Context(), w.receivedMessageID, m) + } + + return err +} + +func (w *clientStream) SendMsg(m interface{}) error { + err := w.ClientStream.SendMsg(m) + + w.sentMessageID++ + messageSent.Event(w.Context(), w.sentMessageID, m) + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } + + return err +} + +func (w *clientStream) Header() (metadata.MD, error) { + md, err := w.ClientStream.Header() + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } + + return md, err +} + +func (w *clientStream) CloseSend() error { + err := w.ClientStream.CloseSend() + + if err != nil { + w.sendStreamEvent(errorEvent, err) + } else { + w.sendStreamEvent(closeEvent, nil) + } + + return err +} + +const ( + clientClosedState byte = 1 << iota + receiveEndedState +) + +func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream { + events := make(chan streamEvent) + eventsDone := make(chan struct{}) + finished := make(chan error) + + go func() { + defer close(eventsDone) + + // Both streams have to be closed + state := byte(0) + + for event := range events { + switch event.Type { + case closeEvent: + state |= clientClosedState + case receiveEndEvent: + state |= receiveEndedState + case errorEvent: + finished <- event.Err + return + } + + if state == clientClosedState|receiveEndedState { + finished <- nil + return + } + } + }() + + return &clientStream{ + ClientStream: s, + desc: desc, + events: events, + eventsDone: eventsDone, + finished: finished, + } +} + +func (w *clientStream) sendStreamEvent(eventType streamEventType, err error) { + select { + case <-w.eventsDone: + case w.events <- streamEvent{Type: eventType, Err: err}: + } +} + +// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable +// for use in a grpc.Dial call. +func StreamClientInterceptor(tracer trace.Tracer, opts ...Option) grpc.StreamClientInterceptor { + return func( + ctx context.Context, + desc *grpc.StreamDesc, + cc *grpc.ClientConn, + method string, + streamer grpc.Streamer, + callOpts ...grpc.CallOption, + ) (grpc.ClientStream, error) { + requestMetadata, _ := metadata.FromOutgoingContext(ctx) + metadataCopy := requestMetadata.Copy() + + name, attr := spanInfo(method, cc.Target()) + var span trace.Span + ctx, span = tracer.Start( + ctx, + name, + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attr...), + ) + + Inject(ctx, &metadataCopy, opts...) + ctx = metadata.NewOutgoingContext(ctx, metadataCopy) + + s, err := streamer(ctx, desc, cc, method, callOpts...) + stream := wrapClientStream(s, desc) + + go func() { + if err == nil { + err = <-stream.finished + } + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(s.Code(), s.Message()) + } + + span.End() + }() + + return stream, err + } +} + +// UnaryServerInterceptor returns a grpc.UnaryServerInterceptor suitable +// for use in a grpc.NewServer call. +func UnaryServerInterceptor(tracer trace.Tracer, opts ...Option) grpc.UnaryServerInterceptor { + return func( + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler, + ) (interface{}, error) { + requestMetadata, _ := metadata.FromIncomingContext(ctx) + metadataCopy := requestMetadata.Copy() + + entries, spanCtx := Extract(ctx, &metadataCopy, opts...) + ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{ + MultiKV: entries, + })) + + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + ctx, span := tracer.Start( + trace.ContextWithRemoteSpanContext(ctx, spanCtx), + name, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attr...), + ) + defer span.End() + + messageReceived.Event(ctx, 1, req) + + resp, err := handler(ctx, req) + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(s.Code(), s.Message()) + messageSent.Event(ctx, 1, s.Proto()) + } else { + messageSent.Event(ctx, 1, resp) + } + + return resp, err + } +} + +// serverStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and +// SendMsg method call. +type serverStream struct { + grpc.ServerStream + ctx context.Context + + receivedMessageID int + sentMessageID int +} + +func (w *serverStream) Context() context.Context { + return w.ctx +} + +func (w *serverStream) RecvMsg(m interface{}) error { + err := w.ServerStream.RecvMsg(m) + + if err == nil { + w.receivedMessageID++ + messageReceived.Event(w.Context(), w.receivedMessageID, m) + } + + return err +} + +func (w *serverStream) SendMsg(m interface{}) error { + err := w.ServerStream.SendMsg(m) + + w.sentMessageID++ + messageSent.Event(w.Context(), w.sentMessageID, m) + + return err +} + +func wrapServerStream(ctx context.Context, ss grpc.ServerStream) *serverStream { + return &serverStream{ + ServerStream: ss, + ctx: ctx, + } +} + +// StreamServerInterceptor returns a grpc.StreamServerInterceptor suitable +// for use in a grpc.NewServer call. +func StreamServerInterceptor(tracer trace.Tracer, opts ...Option) grpc.StreamServerInterceptor { + return func( + srv interface{}, + ss grpc.ServerStream, + info *grpc.StreamServerInfo, + handler grpc.StreamHandler, + ) error { + ctx := ss.Context() + + requestMetadata, _ := metadata.FromIncomingContext(ctx) + metadataCopy := requestMetadata.Copy() + + entries, spanCtx := Extract(ctx, &metadataCopy, opts...) + ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{ + MultiKV: entries, + })) + + name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx)) + ctx, span := tracer.Start( + trace.ContextWithRemoteSpanContext(ctx, spanCtx), + name, + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes(attr...), + ) + defer span.End() + + err := handler(srv, wrapServerStream(ctx, ss)) + + if err != nil { + s, _ := status.FromError(err) + span.SetStatus(s.Code(), s.Message()) + } + + return err + } +} + +// spanInfo returns a span name and all appropriate attributes from the gRPC +// method and peer address. +func spanInfo(fullMethod, peerAddress string) (string, []kv.KeyValue) { + attrs := []kv.KeyValue{standard.RPCSystemGRPC} + name, mAttrs := parseFullMethod(fullMethod) + attrs = append(attrs, mAttrs...) + attrs = append(attrs, peerAttr(peerAddress)...) + return name, attrs +} + +// peerAttr returns attributes about the peer address. +func peerAttr(addr string) []kv.KeyValue { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return []kv.KeyValue(nil) + } + + if host == "" { + host = "127.0.0.1" + } + + return []kv.KeyValue{ + standard.NetPeerIPKey.String(host), + standard.NetPeerPortKey.String(port), + } +} + +// peerFromCtx returns a peer address from a context, if one exists. +func peerFromCtx(ctx context.Context) string { + p, ok := peer.FromContext(ctx) + if !ok { + return "" + } + return p.Addr.String() +} + +// parseFullMethod returns a span name following the OpenTelemetry semantic +// conventions as well as all applicable span kv.KeyValue attributes based +// on a gRPC's FullMethod. +func parseFullMethod(fullMethod string) (string, []kv.KeyValue) { + name := strings.TrimLeft(fullMethod, "/") + parts := strings.SplitN(name, "/", 2) + if len(parts) != 2 { + // Invalid format, does not follow `/package.service/method`. + return name, []kv.KeyValue(nil) + } + + var attrs []kv.KeyValue + if service := parts[0]; service != "" { + attrs = append(attrs, standard.RPCServiceKey.String(service)) + } + if method := parts[1]; method != "" { + attrs = append(attrs, standard.RPCMethodKey.String(method)) + } + return name, attrs +} diff --git a/instrumentation/google.golang.org/grpc/interceptor_test.go b/instrumentation/google.golang.org/grpc/interceptor_test.go new file mode 100644 index 00000000000..4d09abf6d3a --- /dev/null +++ b/instrumentation/google.golang.org/grpc/interceptor_test.go @@ -0,0 +1,428 @@ +// 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. +package grpc + +import ( + "context" + "sync" + "testing" + "time" + + "go.opentelemetry.io/otel/api/standard" + "go.opentelemetry.io/otel/api/trace/testtrace" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/golang/protobuf/proto" //nolint:staticcheck + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "go.opentelemetry.io/otel/api/kv" +) + +type SpanRecorder struct { + mu sync.RWMutex + spans map[string]*testtrace.Span +} + +func NewSpanRecorder() *SpanRecorder { + return &SpanRecorder{spans: make(map[string]*testtrace.Span)} +} + +func (sr *SpanRecorder) OnStart(span *testtrace.Span) {} + +func (sr *SpanRecorder) OnEnd(span *testtrace.Span) { + sr.mu.Lock() + defer sr.mu.Unlock() + sr.spans[span.Name()] = span +} + +func (sr *SpanRecorder) Get(name string) (*testtrace.Span, bool) { + sr.mu.RLock() + defer sr.mu.RUnlock() + s, ok := sr.spans[name] + return s, ok +} + +type mockUICInvoker struct { + ctx context.Context +} + +func (mcuici *mockUICInvoker) invoker(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error { + mcuici.ctx = ctx + return nil +} + +type mockProtoMessage struct{} + +func (mm *mockProtoMessage) Reset() { +} + +func (mm *mockProtoMessage) String() string { + return "mock" +} + +func (mm *mockProtoMessage) ProtoMessage() { +} + +func TestUnaryClientInterceptor(t *testing.T) { + clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure()) + if err != nil { + t.Fatalf("failed to create client connection: %v", err) + } + + sr := NewSpanRecorder() + tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr)) + tracer := tp.Tracer("grpc/client") + unaryInterceptor := UnaryClientInterceptor(tracer) + + req := &mockProtoMessage{} + reply := &mockProtoMessage{} + uniInterceptorInvoker := &mockUICInvoker{} + + checks := []struct { + method string + name string + expectedAttr map[kv.Key]kv.Value + eventsAttr []map[kv.Key]kv.Value + }{ + { + method: "/github.com.serviceName/bar", + name: "github.com.serviceName/bar", + expectedAttr: map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.RPCServiceKey: kv.StringValue("github.com.serviceName"), + standard.RPCMethodKey: kv.StringValue("bar"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + }, + eventsAttr: []map[kv.Key]kv.Value{ + { + standard.RPCMessageTypeKey: kv.StringValue("SENT"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))), + }, + { + standard.RPCMessageTypeKey: kv.StringValue("RECEIVED"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))), + }, + }, + }, + { + method: "/serviceName/bar", + name: "serviceName/bar", + expectedAttr: map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.RPCServiceKey: kv.StringValue("serviceName"), + standard.RPCMethodKey: kv.StringValue("bar"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + }, + eventsAttr: []map[kv.Key]kv.Value{ + { + standard.RPCMessageTypeKey: kv.StringValue("SENT"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))), + }, + { + standard.RPCMessageTypeKey: kv.StringValue("RECEIVED"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))), + }, + }, + }, + { + method: "serviceName/bar", + name: "serviceName/bar", + expectedAttr: map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.RPCServiceKey: kv.StringValue("serviceName"), + standard.RPCMethodKey: kv.StringValue("bar"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + }, + eventsAttr: []map[kv.Key]kv.Value{ + { + standard.RPCMessageTypeKey: kv.StringValue("SENT"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))), + }, + { + standard.RPCMessageTypeKey: kv.StringValue("RECEIVED"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))), + }, + }, + }, + { + method: "invalidName", + name: "invalidName", + expectedAttr: map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + }, + eventsAttr: []map[kv.Key]kv.Value{ + { + standard.RPCMessageTypeKey: kv.StringValue("SENT"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))), + }, + { + standard.RPCMessageTypeKey: kv.StringValue("RECEIVED"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))), + }, + }, + }, + { + method: "/github.com.foo.serviceName_123/method", + name: "github.com.foo.serviceName_123/method", + expectedAttr: map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.RPCServiceKey: kv.StringValue("github.com.foo.serviceName_123"), + standard.RPCMethodKey: kv.StringValue("method"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + }, + eventsAttr: []map[kv.Key]kv.Value{ + { + standard.RPCMessageTypeKey: kv.StringValue("SENT"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))), + }, + { + standard.RPCMessageTypeKey: kv.StringValue("RECEIVED"), + standard.RPCMessageIDKey: kv.IntValue(1), + standard.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))), + }, + }, + }, + } + + for _, check := range checks { + if !assert.NoError(t, unaryInterceptor(context.Background(), check.method, req, reply, clientConn, uniInterceptorInvoker.invoker)) { + continue + } + span, ok := sr.Get(check.name) + if !assert.True(t, ok, "missing span %q", check.name) { + continue + } + assert.Equal(t, check.expectedAttr, span.Attributes()) + assert.Equal(t, check.eventsAttr, eventAttrMap(span.Events())) + } +} + +func eventAttrMap(events []testtrace.Event) []map[kv.Key]kv.Value { + maps := make([]map[kv.Key]kv.Value, len(events)) + for i, event := range events { + maps[i] = event.Attributes + } + return maps +} + +type mockClientStream struct { + Desc *grpc.StreamDesc + Ctx context.Context +} + +func (mockClientStream) SendMsg(m interface{}) error { return nil } +func (mockClientStream) RecvMsg(m interface{}) error { return nil } +func (mockClientStream) CloseSend() error { return nil } +func (c mockClientStream) Context() context.Context { return c.Ctx } +func (mockClientStream) Header() (metadata.MD, error) { return nil, nil } +func (mockClientStream) Trailer() metadata.MD { return nil } + +func TestStreamClientInterceptor(t *testing.T) { + clientConn, err := grpc.Dial("fake:connection", grpc.WithInsecure()) + if err != nil { + t.Fatalf("failed to create client connection: %v", err) + } + + // tracer + sr := NewSpanRecorder() + tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr)) + tracer := tp.Tracer("grpc/Server") + streamCI := StreamClientInterceptor(tracer) + + var mockClStr mockClientStream + method := "/github.com.serviceName/bar" + name := "github.com.serviceName/bar" + + streamClient, err := streamCI( + context.Background(), + &grpc.StreamDesc{ServerStreams: true}, + clientConn, + method, + func(ctx context.Context, + desc *grpc.StreamDesc, + cc *grpc.ClientConn, + method string, + opts ...grpc.CallOption) (grpc.ClientStream, error) { + mockClStr = mockClientStream{Desc: desc, Ctx: ctx} + return mockClStr, nil + }, + ) + require.NoError(t, err, "initialize grpc stream client") + _, ok := sr.Get(name) + require.False(t, ok, "span should ended while stream is open") + + req := &mockProtoMessage{} + reply := &mockProtoMessage{} + + // send and receive fake data + for i := 0; i < 10; i++ { + _ = streamClient.SendMsg(req) + _ = streamClient.RecvMsg(reply) + } + + // close client and server stream + _ = streamClient.CloseSend() + mockClStr.Desc.ServerStreams = false + _ = streamClient.RecvMsg(reply) + + // added retry because span end is called in separate go routine + var span *testtrace.Span + for retry := 0; retry < 5; retry++ { + span, ok = sr.Get(name) + if ok { + break + } + time.Sleep(time.Second * 1) + } + require.True(t, ok, "missing span %s", name) + + expectedAttr := map[kv.Key]kv.Value{ + standard.RPCSystemKey: kv.StringValue("grpc"), + standard.RPCServiceKey: kv.StringValue("github.com.serviceName"), + standard.RPCMethodKey: kv.StringValue("bar"), + standard.NetPeerIPKey: kv.StringValue("fake"), + standard.NetPeerPortKey: kv.StringValue("connection"), + } + assert.Equal(t, expectedAttr, span.Attributes()) + + events := span.Events() + require.Len(t, events, 20) + for i := 0; i < 20; i += 2 { + msgID := i/2 + 1 + validate := func(eventName string, attrs map[kv.Key]kv.Value) { + for k, v := range attrs { + if k == standard.RPCMessageTypeKey && v.AsString() != eventName { + t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString()) + } + if k == standard.RPCMessageIDKey && v != kv.IntValue(msgID) { + t.Errorf("invalid id for message event expected %d received %d", msgID, v.AsInt32()) + } + } + } + validate("SENT", events[i].Attributes) + validate("RECEIVED", events[i+1].Attributes) + } + + // ensure CloseSend can be subsequently called + _ = streamClient.CloseSend() +} + +func TestServerInterceptorError(t *testing.T) { + sr := NewSpanRecorder() + tp := testtrace.NewProvider(testtrace.WithSpanRecorder(sr)) + tracer := tp.Tracer("grpc/Server") + usi := UnaryServerInterceptor(tracer) + deniedErr := status.Error(codes.PermissionDenied, "PERMISSION_DENIED_TEXT") + handler := func(_ context.Context, _ interface{}) (interface{}, error) { + return nil, deniedErr + } + _, err := usi(context.Background(), &mockProtoMessage{}, &grpc.UnaryServerInfo{}, handler) + require.Error(t, err) + assert.Equal(t, err, deniedErr) + + span, ok := sr.Get("") + if !ok { + t.Fatalf("failed to export error span") + } + assert.Equal(t, span.StatusCode(), codes.PermissionDenied) + assert.Contains(t, deniedErr.Error(), span.StatusMessage()) + assert.Len(t, span.Events(), 2) + assert.Equal(t, map[kv.Key]kv.Value{ + kv.Key("message.type"): kv.StringValue("SENT"), + kv.Key("message.id"): kv.IntValue(1), + kv.Key("message.uncompressed_size"): kv.IntValue(26), + }, span.Events()[1].Attributes) +} + +func TestParseFullMethod(t *testing.T) { + tests := []struct { + fullMethod string + name string + attr []kv.KeyValue + }{ + { + fullMethod: "/grpc.test.EchoService/Echo", + name: "grpc.test.EchoService/Echo", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("grpc.test.EchoService"), + standard.RPCMethodKey.String("Echo"), + }, + }, { + fullMethod: "/com.example.ExampleRmiService/exampleMethod", + name: "com.example.ExampleRmiService/exampleMethod", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("com.example.ExampleRmiService"), + standard.RPCMethodKey.String("exampleMethod"), + }, + }, { + fullMethod: "/MyCalcService.Calculator/Add", + name: "MyCalcService.Calculator/Add", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("MyCalcService.Calculator"), + standard.RPCMethodKey.String("Add"), + }, + }, { + fullMethod: "/MyServiceReference.ICalculator/Add", + name: "MyServiceReference.ICalculator/Add", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("MyServiceReference.ICalculator"), + standard.RPCMethodKey.String("Add"), + }, + }, { + fullMethod: "/MyServiceWithNoPackage/theMethod", + name: "MyServiceWithNoPackage/theMethod", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("MyServiceWithNoPackage"), + standard.RPCMethodKey.String("theMethod"), + }, + }, { + fullMethod: "/pkg.srv", + name: "pkg.srv", + attr: []kv.KeyValue(nil), + }, { + fullMethod: "/pkg.srv/", + name: "pkg.srv/", + attr: []kv.KeyValue{ + standard.RPCServiceKey.String("pkg.srv"), + }, + }, + } + + for _, test := range tests { + n, a := parseFullMethod(test.fullMethod) + assert.Equal(t, test.name, n) + assert.Equal(t, test.attr, a) + } +}