1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-03-21 21:17:35 +02:00

Resolve #72 - gRPC Interceptor (#621)

* Move interceptor to plugin

* Add basic net.peer info

* Ensure that grpc status match span status

See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-rpc.md#status

* Set rpc.service attribute

* Add StreamClientInterceptor and StreamServerInterceptor

* Fix: golint errors

* Apply automated go.mod changes from make

* Implement suggestions to improve readability
This commit is contained in:
Paul Reichelt 2020-04-23 22:07:14 +02:00 committed by GitHub
parent 0bb12d9b1b
commit 6de3dab6b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 920 additions and 186 deletions

View File

@ -1,16 +1,25 @@
// 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 "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
@ -22,39 +31,16 @@ var _ = math.Inf
// 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.ProtoPackageIsVersion3 // please upgrade the proto package
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type HelloRequest struct {
Greeting string `protobuf:"bytes,1,opt,name=greeting,proto3" json:"greeting,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
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 fileDescriptor_b5bbcd7ae0220f22, []int{0}
}
func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
}
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
}
func (m *HelloRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_HelloRequest.Merge(m, src)
}
func (m *HelloRequest) XXX_Size() int {
return xxx_messageInfo_HelloRequest.Size(m)
}
func (m *HelloRequest) XXX_DiscardUnknown() {
xxx_messageInfo_HelloRequest.DiscardUnknown(m)
}
var xxx_messageInfo_HelloRequest proto.InternalMessageInfo
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 {
@ -64,36 +50,13 @@ func (m *HelloRequest) GetGreeting() string {
}
type HelloResponse struct {
Reply string `protobuf:"bytes,1,opt,name=reply,proto3" json:"reply,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
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 fileDescriptor_b5bbcd7ae0220f22, []int{1}
}
func (m *HelloResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HelloResponse.Unmarshal(m, b)
}
func (m *HelloResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HelloResponse.Marshal(b, m, deterministic)
}
func (m *HelloResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_HelloResponse.Merge(m, src)
}
func (m *HelloResponse) XXX_Size() int {
return xxx_messageInfo_HelloResponse.Size(m)
}
func (m *HelloResponse) XXX_DiscardUnknown() {
xxx_messageInfo_HelloResponse.DiscardUnknown(m)
}
var xxx_messageInfo_HelloResponse proto.InternalMessageInfo
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 {
@ -107,22 +70,6 @@ func init() {
proto.RegisterType((*HelloResponse)(nil), "api.HelloResponse")
}
func init() { proto.RegisterFile("hello-service.proto", fileDescriptor_b5bbcd7ae0220f22) }
var fileDescriptor_b5bbcd7ae0220f22 = []byte{
// 146 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,
0x91, 0x23, 0xd4, 0xc8, 0x60, 0x88, 0x6d, 0x42, 0x86, 0x5c, 0x1c, 0xc1, 0x89, 0x95, 0x60, 0x21,
0x21, 0x41, 0xbd, 0xc4, 0x82, 0x4c, 0x3d, 0x64, 0x1b, 0xa5, 0x84, 0x90, 0x85, 0x20, 0x06, 0x27,
0xb1, 0x81, 0x5d, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xce, 0x48, 0xd8, 0xe7, 0xb8, 0x00,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
@ -131,11 +78,13 @@ var _ grpc.ClientConn
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// HelloServiceClient is the client API for HelloService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
// 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 {
@ -148,24 +97,117 @@ func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
out := new(HelloResponse)
err := c.cc.Invoke(ctx, "/api.HelloService/SayHello", in, out, opts...)
err := grpc.Invoke(ctx, "/api.HelloService/SayHello", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// HelloServiceServer is the server API for HelloService service.
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)
}
// UnimplementedHelloServiceServer can be embedded to have forward compatible implementations.
type UnimplementedHelloServiceServer struct {
}
func (*UnimplementedHelloServiceServer) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
SayHelloServerStream(*HelloRequest, HelloService_SayHelloServerStreamServer) error
SayHelloClientStream(HelloService_SayHelloClientStreamServer) error
SayHelloBidiStream(HelloService_SayHelloBidiStreamServer) error
}
func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
@ -190,6 +232,79 @@ func _HelloService_SayHello_Handler(srv interface{}, ctx context.Context, dec fu
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),
@ -199,6 +314,41 @@ var _HelloService_serviceDesc = grpc.ServiceDesc{
Handler: _HelloService_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
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,
}

View File

@ -17,6 +17,12 @@ 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 {

View File

@ -16,23 +16,28 @@ 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"
"go.opentelemetry.io/otel/plugin/grpctrace"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"go.opentelemetry.io/otel/example/grpc/middleware/tracing"
)
func main() {
config.Init()
var conn *grpc.ClientConn
conn, err := grpc.Dial(":7777", grpc.WithInsecure(), grpc.WithUnaryInterceptor(tracing.UnaryClientInterceptor))
conn, err := grpc.Dial(":7777", grpc.WithInsecure(),
grpc.WithUnaryInterceptor(grpctrace.UnaryClientInterceptor(global.Tracer(""))),
grpc.WithStreamInterceptor(grpctrace.StreamClientInterceptor(global.Tracer(""))),
)
if err != nil {
log.Fatalf("did not connect: %s", err)
}
@ -40,11 +45,21 @@ func main() {
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 {
@ -52,3 +67,116 @@ func main() {
}
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
}

View File

@ -7,5 +7,6 @@ replace go.opentelemetry.io/otel => ../..
require (
github.com/golang/protobuf v1.3.2
go.opentelemetry.io/otel v0.4.2
golang.org/x/net v0.0.0-20190311183353-d8887717615a
google.golang.org/grpc v1.27.1
)

View File

@ -1,85 +0,0 @@
// 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 tracing
// gRPC tracing middleware
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-rpc.md
import (
"context"
"go.opentelemetry.io/otel/plugin/grpctrace"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/correlation"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/trace"
)
// UnaryServerInterceptor intercepts and extracts incoming trace data
func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
requestMetadata, _ := metadata.FromIncomingContext(ctx)
metadataCopy := requestMetadata.Copy()
entries, spanCtx := grpctrace.Extract(ctx, &metadataCopy)
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{
MultiKV: entries,
}))
grpcServerKey := key.New("grpc.server")
serverSpanAttrs := []core.KeyValue{
grpcServerKey.String("hello-world-server"),
}
tr := global.Tracer("example/grpc")
ctx, span := tr.Start(
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
"hello-api-op",
trace.WithAttributes(serverSpanAttrs...),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
return handler(ctx, req)
}
// UnaryClientInterceptor intercepts and injects outgoing trace
func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
requestMetadata, _ := metadata.FromOutgoingContext(ctx)
metadataCopy := requestMetadata.Copy()
tr := global.Tracer("example/grpc")
err := tr.WithSpan(ctx, "hello-api-op",
func(ctx context.Context) error {
grpctrace.Inject(ctx, &metadataCopy)
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
err := invoker(ctx, method, req, reply, cc, opts...)
setTraceStatus(ctx, err)
return err
})
return err
}
func setTraceStatus(ctx context.Context, err error) {
if err != nil {
s, _ := status.FromError(err)
trace.SpanFromContext(ctx).SetStatus(s.Code(), s.Message())
}
}

View File

@ -16,15 +16,18 @@ 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"
"go.opentelemetry.io/otel/plugin/grpctrace"
"google.golang.org/grpc"
"go.opentelemetry.io/otel/example/grpc/middleware/tracing"
)
const (
@ -33,15 +36,78 @@ const (
// server is used to implement api.HelloServiceServer
type server struct {
api.UnimplementedHelloServiceServer
api.HelloServiceServer
}
// SayHello implements api.HelloServiceServer
func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloResponse, error) {
log.Printf("Received: %v", in.GetGreeting())
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()
@ -50,7 +116,10 @@ func main() {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(grpc.UnaryInterceptor(tracing.UnaryServerInterceptor))
s := grpc.NewServer(
grpc.UnaryInterceptor(grpctrace.UnaryServerInterceptor(global.Tracer(""))),
grpc.StreamInterceptor(grpctrace.StreamServerInterceptor(global.Tracer(""))),
)
api.RegisterHelloServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7
github.com/benbjohnson/clock v1.0.0
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.3.2
github.com/google/go-cmp v0.4.0
github.com/google/gofuzz v1.0.0 // indirect
github.com/kr/pretty v0.1.0 // indirect

3
go.sum
View File

@ -45,6 +45,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
@ -52,7 +53,9 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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=

View File

@ -0,0 +1,461 @@
// 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 grpctrace
// gRPC tracing middleware
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md
import (
"context"
"io"
"net"
"regexp"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"go.opentelemetry.io/otel/api/core"
"go.opentelemetry.io/otel/api/correlation"
"go.opentelemetry.io/otel/api/key"
"go.opentelemetry.io/otel/api/trace"
)
var (
rpcServiceKey = key.New("rpc.service")
netPeerIPKey = key.New("net.peer.ip")
netPeerPortKey = key.New("net.peer.port")
messageTypeKey = key.New("message.type")
messageIDKey = key.New("message.id")
messageUncompressedSizeKey = key.New("message.uncompressed_size")
)
const (
messageTypeSent = "SENT"
messageTypeReceived = "RECEIVED"
)
// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor suitable
// for use in a grpc.Dial call.
//
// For example:
// tracer := global.Tracer("client-tracer")
// s := grpc.NewServer(
// grpc.WithUnaryInterceptor(grpctrace.UnaryClientInterceptor(tracer)),
// ..., // (existing DialOptions))
func UnaryClientInterceptor(tracer trace.Tracer) grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, reply interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
requestMetadata, _ := metadata.FromOutgoingContext(ctx)
metadataCopy := requestMetadata.Copy()
var span trace.Span
ctx, span = tracer.Start(
ctx, method,
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(peerInfoFromTarget(cc.Target())...),
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(method))),
)
defer span.End()
Inject(ctx, &metadataCopy)
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
addEventForMessageSent(ctx, 1, req)
err := invoker(ctx, method, req, reply, cc, opts...)
addEventForMessageReceived(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
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.events <- streamEvent{receiveEndEvent, nil}
} else if err == io.EOF {
w.events <- streamEvent{receiveEndEvent, nil}
} else if err != nil {
w.events <- streamEvent{errorEvent, err}
} else {
w.receivedMessageID++
addEventForMessageReceived(w.Context(), w.receivedMessageID, m)
}
return err
}
func (w *clientStream) SendMsg(m interface{}) error {
err := w.ClientStream.SendMsg(m)
w.sentMessageID++
addEventForMessageSent(w.Context(), w.sentMessageID, m)
if err != nil {
w.events <- streamEvent{errorEvent, err}
}
return err
}
func (w *clientStream) Header() (metadata.MD, error) {
md, err := w.ClientStream.Header()
if err != nil {
w.events <- streamEvent{errorEvent, err}
}
return md, err
}
func (w *clientStream) CloseSend() error {
err := w.ClientStream.CloseSend()
if err != nil {
w.events <- streamEvent{errorEvent, err}
} else {
w.events <- streamEvent{closeEvent, nil}
}
return err
}
const (
clientClosedState byte = 1 << iota
receiveEndedState
)
func wrapClientStream(s grpc.ClientStream, desc *grpc.StreamDesc) *clientStream {
events := make(chan streamEvent, 1)
finished := make(chan error)
go func() {
// 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
close(events)
}
if state == clientClosedState|receiveEndedState {
finished <- nil
close(events)
}
}
}()
return &clientStream{
ClientStream: s,
desc: desc,
events: events,
finished: finished,
}
}
// StreamClientInterceptor returns a grpc.StreamClientInterceptor suitable
// for use in a grpc.Dial call.
//
// For example:
// tracer := global.Tracer("client-tracer")
// s := grpc.Dial(
// grpc.WithStreamInterceptor(grpctrace.StreamClientInterceptor(tracer)),
// ..., // (existing DialOptions))
func StreamClientInterceptor(tracer trace.Tracer) grpc.StreamClientInterceptor {
return func(
ctx context.Context,
desc *grpc.StreamDesc,
cc *grpc.ClientConn,
method string,
streamer grpc.Streamer,
opts ...grpc.CallOption,
) (grpc.ClientStream, error) {
requestMetadata, _ := metadata.FromOutgoingContext(ctx)
metadataCopy := requestMetadata.Copy()
var span trace.Span
ctx, span = tracer.Start(
ctx, method,
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(peerInfoFromTarget(cc.Target())...),
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(method))),
)
Inject(ctx, &metadataCopy)
ctx = metadata.NewOutgoingContext(ctx, metadataCopy)
s, err := streamer(ctx, desc, cc, method, opts...)
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.
//
// For example:
// tracer := global.Tracer("client-tracer")
// s := grpc.Dial(
// grpc.UnaryInterceptor(grpctrace.UnaryServerInterceptor(tracer)),
// ..., // (existing ServerOptions))
func UnaryServerInterceptor(tracer trace.Tracer) 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)
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{
MultiKV: entries,
}))
ctx, span := tracer.Start(
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
info.FullMethod,
trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(peerInfoFromContext(ctx)...),
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(info.FullMethod))),
)
defer span.End()
addEventForMessageReceived(ctx, 1, req)
resp, err := handler(ctx, req)
addEventForMessageSent(ctx, 1, resp)
if err != nil {
s, _ := status.FromError(err)
span.SetStatus(s.Code(), s.Message())
}
return resp, err
}
}
// clientStream 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++
addEventForMessageReceived(w.Context(), w.receivedMessageID, m)
}
return err
}
func (w *serverStream) SendMsg(m interface{}) error {
err := w.ServerStream.SendMsg(m)
w.sentMessageID++
addEventForMessageSent(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.
//
// For example:
// tracer := global.Tracer("client-tracer")
// s := grpc.Dial(
// grpc.StreamInterceptor(grpctrace.StreamServerInterceptor(tracer)),
// ..., // (existing ServerOptions))
func StreamServerInterceptor(tracer trace.Tracer) 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)
ctx = correlation.ContextWithMap(ctx, correlation.NewMap(correlation.MapUpdate{
MultiKV: entries,
}))
ctx, span := tracer.Start(
trace.ContextWithRemoteSpanContext(ctx, spanCtx),
info.FullMethod,
trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(peerInfoFromContext(ctx)...),
trace.WithAttributes(rpcServiceKey.String(serviceFromFullMethod(info.FullMethod))),
)
defer span.End()
err := handler(srv, wrapServerStream(ctx, ss))
if err != nil {
s, _ := status.FromError(err)
span.SetStatus(s.Code(), s.Message())
}
return err
}
}
func peerInfoFromTarget(target string) []core.KeyValue {
host, port, err := net.SplitHostPort(target)
if err != nil {
return []core.KeyValue{}
}
if host == "" {
host = "127.0.0.1"
}
return []core.KeyValue{
netPeerIPKey.String(host),
netPeerPortKey.String(port),
}
}
func peerInfoFromContext(ctx context.Context) []core.KeyValue {
p, ok := peer.FromContext(ctx)
if !ok {
return []core.KeyValue{}
}
return peerInfoFromTarget(p.Addr.String())
}
var fullMethodRegexp = regexp.MustCompile(`^/\S*\.(\S*)/\S*$`)
func serviceFromFullMethod(method string) string {
match := fullMethodRegexp.FindAllStringSubmatch(method, 1)
if len(match) != 1 && len(match[1]) != 2 {
return ""
}
return match[0][1]
}
func addEventForMessageReceived(ctx context.Context, id int, m interface{}) {
size := proto.Size(m.(proto.Message))
span := trace.SpanFromContext(ctx)
span.AddEvent(ctx, "message",
messageTypeKey.String(messageTypeReceived),
messageIDKey.Int(id),
messageUncompressedSizeKey.Int(size),
)
}
func addEventForMessageSent(ctx context.Context, id int, m interface{}) {
size := proto.Size(m.(proto.Message))
span := trace.SpanFromContext(ctx)
span.AddEvent(ctx, "message",
messageTypeKey.String(messageTypeSent),
messageIDKey.Int(id),
messageUncompressedSizeKey.Int(size),
)
}