mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-01-28 03:57:09 +02:00
Move grpctrace to contrib repo (#1027)
* Remove grpctrace package This is being moved to the contrib repo with https://github.com/open-telemetry/opentelemetry-go-contrib/pull/189 as part of #976. * Update Changelog * Remove the grpc example Moved to contrib repo * Fix spelling error * Update Changelog
This commit is contained in:
parent
ccfa2e7bdf
commit
f0620dc0ad
@ -12,6 +12,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
|
||||
- Renamed `go.opentelemetry.io/otel/api/standard` package to `go.opentelemetry.io/otel/semconv` to avoid the ambiguous and generic name `standard` and better describe the package as containing OpenTelemetry semantic conventions. (#1016)
|
||||
|
||||
### Removed
|
||||
|
||||
- The `grpctrace` instrumentation was moved to the `go.opentelemetry.io/contrib` repository and out of this repository.
|
||||
This move includes moving the `grpc` example to the `go.opentelemetry.io/contrib` as well. (#1027)
|
||||
|
||||
### Fixed
|
||||
|
||||
- The `semconv.HTTPServerMetricAttributesFromHTTPRequest()` function no longer generates the high-cardinality `http.request.content.length` label. (#1031)
|
||||
|
@ -1,28 +0,0 @@
|
||||
# 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
|
||||
```
|
@ -1,354 +0,0 @@
|
||||
// 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,
|
||||
}
|
@ -1,34 +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.
|
||||
|
||||
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;
|
||||
}
|
@ -1,182 +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 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/instrumentation/grpctrace"
|
||||
|
||||
"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(grpctrace.UnaryClientInterceptor(global.Tracer(""))),
|
||||
grpc.WithStreamInterceptor(grpctrace.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
|
||||
}
|
@ -1,39 +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 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)
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
module go.opentelemetry.io/otel/example/grpc
|
||||
|
||||
go 1.14
|
||||
|
||||
replace (
|
||||
go.opentelemetry.io/otel => ../..
|
||||
go.opentelemetry.io/otel/exporters/stdout => ../../exporters/stdout
|
||||
go.opentelemetry.io/otel/sdk => ../../sdk
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.4.2
|
||||
go.opentelemetry.io/otel 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-20190613194153-d28f0bde5980
|
||||
google.golang.org/grpc v1.31.0
|
||||
)
|
@ -1,104 +0,0 @@
|
||||
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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
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=
|
||||
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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
|
||||
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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
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.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=
|
@ -1,128 +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 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/instrumentation/grpctrace"
|
||||
|
||||
"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(grpctrace.UnaryServerInterceptor(global.Tracer(""))),
|
||||
grpc.StreamInterceptor(grpctrace.StreamServerInterceptor(global.Tracer(""))),
|
||||
)
|
||||
|
||||
api.RegisterHelloServiceServer(s, &server{})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
@ -1,51 +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 grpctrace
|
||||
|
||||
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)),
|
||||
)
|
||||
}
|
@ -1,95 +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 grpctrace
|
||||
|
||||
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 grpctrace 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
|
||||
}
|
@ -1,459 +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 grpctrace
|
||||
|
||||
// gRPC tracing middleware
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
|
||||
"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/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),
|
||||
semconv.RPCMessageIDKey.Int(id),
|
||||
semconv.RPCMessageUncompressedSizeKey.Int(proto.Size(p)),
|
||||
)
|
||||
} else {
|
||||
span.AddEvent(ctx, "message",
|
||||
kv.KeyValue(m),
|
||||
semconv.RPCMessageIDKey.Int(id),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
messageSent = messageType(semconv.RPCMessageTypeSent)
|
||||
messageReceived = messageType(semconv.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{semconv.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{
|
||||
semconv.NetPeerIPKey.String(host),
|
||||
semconv.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, semconv.RPCServiceKey.String(service))
|
||||
}
|
||||
if method := parts[1]; method != "" {
|
||||
attrs = append(attrs, semconv.RPCMethodKey.String(method))
|
||||
}
|
||||
return name, attrs
|
||||
}
|
@ -1,428 +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 grpctrace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/api/trace/testtrace"
|
||||
"go.opentelemetry.io/otel/semconv"
|
||||
|
||||
"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("grpctrace/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{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.RPCServiceKey: kv.StringValue("github.com.serviceName"),
|
||||
semconv.RPCMethodKey: kv.StringValue("bar"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.NetPeerPortKey: kv.StringValue("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]kv.Value{
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("SENT"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("RECEIVED"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "/serviceName/bar",
|
||||
name: "serviceName/bar",
|
||||
expectedAttr: map[kv.Key]kv.Value{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.RPCServiceKey: kv.StringValue("serviceName"),
|
||||
semconv.RPCMethodKey: kv.StringValue("bar"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.NetPeerPortKey: kv.StringValue("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]kv.Value{
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("SENT"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("RECEIVED"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "serviceName/bar",
|
||||
name: "serviceName/bar",
|
||||
expectedAttr: map[kv.Key]kv.Value{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.RPCServiceKey: kv.StringValue("serviceName"),
|
||||
semconv.RPCMethodKey: kv.StringValue("bar"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.NetPeerPortKey: kv.StringValue("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]kv.Value{
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("SENT"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("RECEIVED"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(reply))),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
method: "invalidName",
|
||||
name: "invalidName",
|
||||
expectedAttr: map[kv.Key]kv.Value{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.NetPeerPortKey: kv.StringValue("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]kv.Value{
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("SENT"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("RECEIVED"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.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{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.RPCServiceKey: kv.StringValue("github.com.foo.serviceName_123"),
|
||||
semconv.RPCMethodKey: kv.StringValue("method"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.NetPeerPortKey: kv.StringValue("connection"),
|
||||
},
|
||||
eventsAttr: []map[kv.Key]kv.Value{
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("SENT"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.RPCMessageUncompressedSizeKey: kv.IntValue(proto.Size(proto.Message(req))),
|
||||
},
|
||||
{
|
||||
semconv.RPCMessageTypeKey: kv.StringValue("RECEIVED"),
|
||||
semconv.RPCMessageIDKey: kv.IntValue(1),
|
||||
semconv.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("grpctrace/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{
|
||||
semconv.RPCSystemKey: kv.StringValue("grpc"),
|
||||
semconv.RPCServiceKey: kv.StringValue("github.com.serviceName"),
|
||||
semconv.RPCMethodKey: kv.StringValue("bar"),
|
||||
semconv.NetPeerIPKey: kv.StringValue("fake"),
|
||||
semconv.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 == semconv.RPCMessageTypeKey && v.AsString() != eventName {
|
||||
t.Errorf("invalid event on index: %d expecting %s event, receive %s event", i, eventName, v.AsString())
|
||||
}
|
||||
if k == semconv.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("grpctrace/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{
|
||||
semconv.RPCServiceKey.String("grpc.test.EchoService"),
|
||||
semconv.RPCMethodKey.String("Echo"),
|
||||
},
|
||||
}, {
|
||||
fullMethod: "/com.example.ExampleRmiService/exampleMethod",
|
||||
name: "com.example.ExampleRmiService/exampleMethod",
|
||||
attr: []kv.KeyValue{
|
||||
semconv.RPCServiceKey.String("com.example.ExampleRmiService"),
|
||||
semconv.RPCMethodKey.String("exampleMethod"),
|
||||
},
|
||||
}, {
|
||||
fullMethod: "/MyCalcService.Calculator/Add",
|
||||
name: "MyCalcService.Calculator/Add",
|
||||
attr: []kv.KeyValue{
|
||||
semconv.RPCServiceKey.String("MyCalcService.Calculator"),
|
||||
semconv.RPCMethodKey.String("Add"),
|
||||
},
|
||||
}, {
|
||||
fullMethod: "/MyServiceReference.ICalculator/Add",
|
||||
name: "MyServiceReference.ICalculator/Add",
|
||||
attr: []kv.KeyValue{
|
||||
semconv.RPCServiceKey.String("MyServiceReference.ICalculator"),
|
||||
semconv.RPCMethodKey.String("Add"),
|
||||
},
|
||||
}, {
|
||||
fullMethod: "/MyServiceWithNoPackage/theMethod",
|
||||
name: "MyServiceWithNoPackage/theMethod",
|
||||
attr: []kv.KeyValue{
|
||||
semconv.RPCServiceKey.String("MyServiceWithNoPackage"),
|
||||
semconv.RPCMethodKey.String("theMethod"),
|
||||
},
|
||||
}, {
|
||||
fullMethod: "/pkg.srv",
|
||||
name: "pkg.srv",
|
||||
attr: []kv.KeyValue(nil),
|
||||
}, {
|
||||
fullMethod: "/pkg.srv/",
|
||||
name: "pkg.srv/",
|
||||
attr: []kv.KeyValue{
|
||||
semconv.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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user