mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Adding a way to inject a request ID (#1046)
* Adding a way to inject a request ID It is very useful to associate a request ID to each incoming request, this change allows to provide a function to do that via Server Option. The change comes with a default function which will generate a new request ID. The request ID is put in the request context along with a common logger which always logs the request-id We add gRPC interceptors to the server so it can get the request ID out of the gRPC metadata and put it in the common logger stored in the context so as all the log lines using the common logger from the context will have the request ID logged
This commit is contained in:
118
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/DOC.md
generated
vendored
Normal file
118
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/DOC.md
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
# grpc_opentracing
|
||||
`import "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"`
|
||||
|
||||
* [Overview](#pkg-overview)
|
||||
* [Imported Packages](#pkg-imports)
|
||||
* [Index](#pkg-index)
|
||||
|
||||
## <a name="pkg-overview">Overview</a>
|
||||
`grpc_opentracing` adds OpenTracing
|
||||
|
||||
### OpenTracing Interceptors
|
||||
These are both client-side and server-side interceptors for OpenTracing. They are a provider-agnostic, with backends
|
||||
such as Zipkin, or Google Stackdriver Trace.
|
||||
|
||||
For a service that sends out requests and receives requests, you *need* to use both, otherwise downstream requests will
|
||||
not have the appropriate requests propagated.
|
||||
|
||||
All server-side spans are tagged with grpc_ctxtags information.
|
||||
|
||||
For more information see:
|
||||
<a href="http://opentracing.io/documentation/">http://opentracing.io/documentation/</a>
|
||||
<a href="https://github.com/opentracing/specification/blob/master/semantic_conventions.md">https://github.com/opentracing/specification/blob/master/semantic_conventions.md</a>
|
||||
|
||||
## <a name="pkg-imports">Imported Packages</a>
|
||||
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware](./../..)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/tags](./../../tags)
|
||||
- [github.com/grpc-ecosystem/go-grpc-middleware/util/metautils](./../../util/metautils)
|
||||
- [github.com/opentracing/opentracing-go](https://godoc.org/github.com/opentracing/opentracing-go)
|
||||
- [github.com/opentracing/opentracing-go/ext](https://godoc.org/github.com/opentracing/opentracing-go/ext)
|
||||
- [github.com/opentracing/opentracing-go/log](https://godoc.org/github.com/opentracing/opentracing-go/log)
|
||||
- [golang.org/x/net/context](https://godoc.org/golang.org/x/net/context)
|
||||
- [google.golang.org/grpc](https://godoc.org/google.golang.org/grpc)
|
||||
- [google.golang.org/grpc/grpclog](https://godoc.org/google.golang.org/grpc/grpclog)
|
||||
- [google.golang.org/grpc/metadata](https://godoc.org/google.golang.org/grpc/metadata)
|
||||
|
||||
## <a name="pkg-index">Index</a>
|
||||
* [Constants](#pkg-constants)
|
||||
* [func ClientAddContextTags(ctx context.Context, tags opentracing.Tags) context.Context](#ClientAddContextTags)
|
||||
* [func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor](#StreamClientInterceptor)
|
||||
* [func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor](#StreamServerInterceptor)
|
||||
* [func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor](#UnaryClientInterceptor)
|
||||
* [func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor](#UnaryServerInterceptor)
|
||||
* [type FilterFunc](#FilterFunc)
|
||||
* [type Option](#Option)
|
||||
* [func WithFilterFunc(f FilterFunc) Option](#WithFilterFunc)
|
||||
* [func WithTracer(tracer opentracing.Tracer) Option](#WithTracer)
|
||||
|
||||
#### <a name="pkg-files">Package files</a>
|
||||
[client_interceptors.go](./client_interceptors.go) [doc.go](./doc.go) [id_extract.go](./id_extract.go) [metadata.go](./metadata.go) [options.go](./options.go) [server_interceptors.go](./server_interceptors.go)
|
||||
|
||||
## <a name="pkg-constants">Constants</a>
|
||||
``` go
|
||||
const (
|
||||
TagTraceId = "trace.traceid"
|
||||
TagSpanId = "trace.spanid"
|
||||
)
|
||||
```
|
||||
|
||||
## <a name="ClientAddContextTags">func</a> [ClientAddContextTags](./client_interceptors.go#L105)
|
||||
``` go
|
||||
func ClientAddContextTags(ctx context.Context, tags opentracing.Tags) context.Context
|
||||
```
|
||||
ClientAddContextTags returns a context with specified opentracing tags, which
|
||||
are used by UnaryClientInterceptor/StreamClientInterceptor when creating a
|
||||
new span.
|
||||
|
||||
## <a name="StreamClientInterceptor">func</a> [StreamClientInterceptor](./client_interceptors.go#L35)
|
||||
``` go
|
||||
func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor
|
||||
```
|
||||
StreamClientInterceptor returns a new streaming client interceptor for OpenTracing.
|
||||
|
||||
## <a name="StreamServerInterceptor">func</a> [StreamServerInterceptor](./server_interceptors.go#L37)
|
||||
``` go
|
||||
func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor
|
||||
```
|
||||
StreamServerInterceptor returns a new streaming server interceptor for OpenTracing.
|
||||
|
||||
## <a name="UnaryClientInterceptor">func</a> [UnaryClientInterceptor](./client_interceptors.go#L21)
|
||||
``` go
|
||||
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor
|
||||
```
|
||||
UnaryClientInterceptor returns a new unary client interceptor for OpenTracing.
|
||||
|
||||
## <a name="UnaryServerInterceptor">func</a> [UnaryServerInterceptor](./server_interceptors.go#L23)
|
||||
``` go
|
||||
func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor
|
||||
```
|
||||
UnaryServerInterceptor returns a new unary server interceptor for OpenTracing.
|
||||
|
||||
## <a name="FilterFunc">type</a> [FilterFunc](./options.go#L22)
|
||||
``` go
|
||||
type FilterFunc func(ctx context.Context, fullMethodName string) bool
|
||||
```
|
||||
FilterFunc allows users to provide a function that filters out certain methods from being traced.
|
||||
|
||||
If it returns false, the given request will not be traced.
|
||||
|
||||
## <a name="Option">type</a> [Option](./options.go#L41)
|
||||
``` go
|
||||
type Option func(*options)
|
||||
```
|
||||
|
||||
### <a name="WithFilterFunc">func</a> [WithFilterFunc](./options.go#L44)
|
||||
``` go
|
||||
func WithFilterFunc(f FilterFunc) Option
|
||||
```
|
||||
WithFilterFunc customizes the function used for deciding whether a given call is traced or not.
|
||||
|
||||
### <a name="WithTracer">func</a> [WithTracer](./options.go#L51)
|
||||
``` go
|
||||
func WithTracer(tracer opentracing.Tracer) Option
|
||||
```
|
||||
WithTracer sets a custom tracer to be used for this middleware, otherwise the opentracing.GlobalTracer is used.
|
||||
|
||||
- - -
|
||||
Generated by [godoc2ghmd](https://github.com/GandalfUK/godoc2ghmd)
|
||||
1
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/README.md
generated
vendored
Symbolic link
1
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/README.md
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
DOC.md
|
||||
142
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/client_interceptors.go
generated
vendored
Normal file
142
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/client_interceptors.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// UnaryClientInterceptor returns a new unary client interceptor for OpenTracing.
|
||||
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
|
||||
o := evaluateOptions(opts)
|
||||
return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) {
|
||||
return invoker(parentCtx, method, req, reply, cc, opts...)
|
||||
}
|
||||
newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method)
|
||||
err := invoker(newCtx, method, req, reply, cc, opts...)
|
||||
finishClientSpan(clientSpan, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// StreamClientInterceptor returns a new streaming client interceptor for OpenTracing.
|
||||
func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
|
||||
o := evaluateOptions(opts)
|
||||
return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) {
|
||||
return streamer(parentCtx, desc, cc, method, opts...)
|
||||
}
|
||||
newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method)
|
||||
clientStream, err := streamer(newCtx, desc, cc, method, opts...)
|
||||
if err != nil {
|
||||
finishClientSpan(clientSpan, err)
|
||||
return nil, err
|
||||
}
|
||||
return &tracedClientStream{ClientStream: clientStream, clientSpan: clientSpan}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a
|
||||
// proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish
|
||||
// a new ClientStream according to the retry policy.
|
||||
type tracedClientStream struct {
|
||||
grpc.ClientStream
|
||||
mu sync.Mutex
|
||||
alreadyFinished bool
|
||||
clientSpan opentracing.Span
|
||||
}
|
||||
|
||||
func (s *tracedClientStream) Header() (metadata.MD, error) {
|
||||
h, err := s.ClientStream.Header()
|
||||
if err != nil {
|
||||
s.finishClientSpan(err)
|
||||
}
|
||||
return h, err
|
||||
}
|
||||
|
||||
func (s *tracedClientStream) SendMsg(m interface{}) error {
|
||||
err := s.ClientStream.SendMsg(m)
|
||||
if err != nil {
|
||||
s.finishClientSpan(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *tracedClientStream) CloseSend() error {
|
||||
err := s.ClientStream.CloseSend()
|
||||
if err != nil {
|
||||
s.finishClientSpan(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *tracedClientStream) RecvMsg(m interface{}) error {
|
||||
err := s.ClientStream.RecvMsg(m)
|
||||
if err != nil {
|
||||
s.finishClientSpan(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *tracedClientStream) finishClientSpan(err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.alreadyFinished {
|
||||
finishClientSpan(s.clientSpan, err)
|
||||
s.alreadyFinished = true
|
||||
}
|
||||
}
|
||||
|
||||
// ClientAddContextTags returns a context with specified opentracing tags, which
|
||||
// are used by UnaryClientInterceptor/StreamClientInterceptor when creating a
|
||||
// new span.
|
||||
func ClientAddContextTags(ctx context.Context, tags opentracing.Tags) context.Context {
|
||||
return context.WithValue(ctx, clientSpanTagKey{}, tags)
|
||||
}
|
||||
|
||||
type clientSpanTagKey struct{}
|
||||
|
||||
func newClientSpanFromContext(ctx context.Context, tracer opentracing.Tracer, fullMethodName string) (context.Context, opentracing.Span) {
|
||||
var parentSpanCtx opentracing.SpanContext
|
||||
if parent := opentracing.SpanFromContext(ctx); parent != nil {
|
||||
parentSpanCtx = parent.Context()
|
||||
}
|
||||
opts := []opentracing.StartSpanOption{
|
||||
opentracing.ChildOf(parentSpanCtx),
|
||||
ext.SpanKindRPCClient,
|
||||
grpcTag,
|
||||
}
|
||||
if tagx := ctx.Value(clientSpanTagKey{}); tagx != nil {
|
||||
if opt, ok := tagx.(opentracing.StartSpanOption); ok {
|
||||
opts = append(opts, opt)
|
||||
}
|
||||
}
|
||||
clientSpan := tracer.StartSpan(fullMethodName, opts...)
|
||||
// Make sure we add this to the metadata of the call, so it gets propagated:
|
||||
md := metautils.ExtractOutgoing(ctx).Clone()
|
||||
if err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, metadataTextMap(md)); err != nil {
|
||||
grpclog.Printf("grpc_opentracing: failed serializing trace information: %v", err)
|
||||
}
|
||||
ctxWithMetadata := md.ToOutgoing(ctx)
|
||||
return opentracing.ContextWithSpan(ctxWithMetadata, clientSpan), clientSpan
|
||||
}
|
||||
|
||||
func finishClientSpan(clientSpan opentracing.Span, err error) {
|
||||
if err != nil && err != io.EOF {
|
||||
ext.Error.Set(clientSpan, true)
|
||||
clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error()))
|
||||
}
|
||||
clientSpan.Finish()
|
||||
}
|
||||
22
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/doc.go
generated
vendored
Normal file
22
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/doc.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
/*
|
||||
`grpc_opentracing` adds OpenTracing
|
||||
|
||||
OpenTracing Interceptors
|
||||
|
||||
These are both client-side and server-side interceptors for OpenTracing. They are a provider-agnostic, with backends
|
||||
such as Zipkin, or Google Stackdriver Trace.
|
||||
|
||||
For a service that sends out requests and receives requests, you *need* to use both, otherwise downstream requests will
|
||||
not have the appropriate requests propagated.
|
||||
|
||||
All server-side spans are tagged with grpc_ctxtags information.
|
||||
|
||||
For more information see:
|
||||
http://opentracing.io/documentation/
|
||||
https://github.com/opentracing/specification/blob/master/semantic_conventions.md
|
||||
|
||||
*/
|
||||
package grpc_opentracing
|
||||
42
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/id_extract.go
generated
vendored
Normal file
42
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/id_extract.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
const (
|
||||
TagTraceId = "trace.traceid"
|
||||
TagSpanId = "trace.spanid"
|
||||
)
|
||||
|
||||
// hackyInjectOpentracingIdsToTags writes the given context to the ctxtags.
|
||||
// This is done in an incredibly hacky way, because the public-facing interface of opentracing doesn't give access to
|
||||
// the TraceId and SpanId of the SpanContext. Only the Tracer's Inject/Extract methods know what these are.
|
||||
// Most tracers have them encoded as keys with 'traceid' and 'spanid':
|
||||
// https://github.com/openzipkin/zipkin-go-opentracing/blob/594640b9ef7e5c994e8d9499359d693c032d738c/propagation_ot.go#L29
|
||||
// https://github.com/opentracing/basictracer-go/blob/1b32af207119a14b1b231d451df3ed04a72efebf/propagation_ot.go#L26
|
||||
func hackyInjectOpentracingIdsToTags(span opentracing.Span, tags grpc_ctxtags.Tags) {
|
||||
if err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, &hackyTagsCarrier{tags}); err != nil {
|
||||
grpclog.Printf("grpc_opentracing: failed extracting trace info into ctx %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// hackyTagsCarrier is a really hacky way of
|
||||
type hackyTagsCarrier struct {
|
||||
grpc_ctxtags.Tags
|
||||
}
|
||||
|
||||
func (t *hackyTagsCarrier) Set(key, val string) {
|
||||
if strings.Contains(key, "traceid") || strings.Contains(strings.ToLower(key), "traceid") {
|
||||
t.Tags.Set(TagTraceId, val) // this will most likely be base-16 (hex) encoded
|
||||
} else if (strings.Contains(key, "spanid") && !strings.Contains(key, "parent")) || (strings.Contains(strings.ToLower(key), "spanid") && !strings.Contains(strings.ToLower(key), "parent")) {
|
||||
t.Tags.Set(TagSpanId, val) // this will most likely be base-16 (hex) encoded
|
||||
}
|
||||
}
|
||||
206
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/interceptors_test.go
generated
vendored
Normal file
206
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/interceptors_test.go
generated
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
grpc_testing "github.com/grpc-ecosystem/go-grpc-middleware/testing"
|
||||
pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
|
||||
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/mocktracer"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
goodPing = &pb_testproto.PingRequest{Value: "something", SleepTimeMs: 9999}
|
||||
fakeInboundTraceId = 1337
|
||||
fakeInboundSpanId = 999
|
||||
)
|
||||
|
||||
func tagsToJson(value map[string]interface{}) string {
|
||||
str, _ := json.Marshal(value)
|
||||
return string(str)
|
||||
}
|
||||
|
||||
func tagsFromJson(t *testing.T, jstring string) map[string]interface{} {
|
||||
var msgMapTemplate interface{}
|
||||
err := json.Unmarshal([]byte(jstring), &msgMapTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("failed unmarshaling tags from response %v", err)
|
||||
}
|
||||
return msgMapTemplate.(map[string]interface{})
|
||||
}
|
||||
|
||||
type tracingAssertService struct {
|
||||
pb_testproto.TestServiceServer
|
||||
T *testing.T
|
||||
}
|
||||
|
||||
func (s *tracingAssertService) Ping(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) {
|
||||
assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail")
|
||||
tags := grpc_ctxtags.Extract(ctx)
|
||||
assert.True(s.T, tags.Has(grpc_opentracing.TagTraceId), "tags must contain traceid")
|
||||
assert.True(s.T, tags.Has(grpc_opentracing.TagSpanId), "tags must contain traceid")
|
||||
return s.TestServiceServer.Ping(ctx, ping)
|
||||
}
|
||||
|
||||
func (s *tracingAssertService) PingError(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.Empty, error) {
|
||||
assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail")
|
||||
return s.TestServiceServer.PingError(ctx, ping)
|
||||
}
|
||||
|
||||
func (s *tracingAssertService) PingList(ping *pb_testproto.PingRequest, stream pb_testproto.TestService_PingListServer) error {
|
||||
assert.NotNil(s.T, opentracing.SpanFromContext(stream.Context()), "handlers must have the spancontext in their context, otherwise propagation will fail")
|
||||
tags := grpc_ctxtags.Extract(stream.Context())
|
||||
assert.True(s.T, tags.Has(grpc_opentracing.TagTraceId), "tags must contain traceid")
|
||||
assert.True(s.T, tags.Has(grpc_opentracing.TagSpanId), "tags must contain traceid")
|
||||
return s.TestServiceServer.PingList(ping, stream)
|
||||
}
|
||||
|
||||
func (s *tracingAssertService) PingEmpty(ctx context.Context, empty *pb_testproto.Empty) (*pb_testproto.PingResponse, error) {
|
||||
assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail")
|
||||
return s.TestServiceServer.PingEmpty(ctx, empty)
|
||||
}
|
||||
|
||||
func TestTaggingSuite(t *testing.T) {
|
||||
mockTracer := mocktracer.New()
|
||||
opts := []grpc_opentracing.Option{
|
||||
grpc_opentracing.WithTracer(mockTracer),
|
||||
}
|
||||
s := &OpentracingSuite{
|
||||
mockTracer: mockTracer,
|
||||
InterceptorTestSuite: &grpc_testing.InterceptorTestSuite{
|
||||
TestService: &tracingAssertService{TestServiceServer: &grpc_testing.TestPingService{T: t}, T: t},
|
||||
ClientOpts: []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor(opts...)),
|
||||
grpc.WithStreamInterceptor(grpc_opentracing.StreamClientInterceptor(opts...)),
|
||||
},
|
||||
ServerOpts: []grpc.ServerOption{
|
||||
grpc_middleware.WithStreamServerChain(
|
||||
grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_opentracing.StreamServerInterceptor(opts...)),
|
||||
grpc_middleware.WithUnaryServerChain(
|
||||
grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
|
||||
grpc_opentracing.UnaryServerInterceptor(opts...)),
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.Run(t, s)
|
||||
}
|
||||
|
||||
type OpentracingSuite struct {
|
||||
*grpc_testing.InterceptorTestSuite
|
||||
mockTracer *mocktracer.MockTracer
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) SetupTest() {
|
||||
s.mockTracer.Reset()
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) createContextFromFakeHttpRequestParent(ctx context.Context) context.Context {
|
||||
hdr := http.Header{}
|
||||
hdr.Set("mockpfx-ids-traceid", fmt.Sprint(fakeInboundTraceId))
|
||||
hdr.Set("mockpfx-ids-spanid", fmt.Sprint(fakeInboundSpanId))
|
||||
hdr.Set("mockpfx-ids-sampled", fmt.Sprint(true))
|
||||
parentSpanContext, err := s.mockTracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(hdr))
|
||||
require.NoError(s.T(), err, "parsing a fake HTTP request headers shouldn't fail, ever")
|
||||
fakeSpan := s.mockTracer.StartSpan(
|
||||
"/fake/parent/http/request",
|
||||
// this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty.
|
||||
opentracing.ChildOf(parentSpanContext),
|
||||
)
|
||||
fakeSpan.Finish()
|
||||
return opentracing.ContextWithSpan(ctx, fakeSpan)
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) assertTracesCreated(methodName string) (clientSpan *mocktracer.MockSpan, serverSpan *mocktracer.MockSpan) {
|
||||
spans := s.mockTracer.FinishedSpans()
|
||||
for _, span := range spans {
|
||||
s.T().Logf("span: %v, tags: %v", span, span.Tags())
|
||||
}
|
||||
require.Len(s.T(), spans, 3, "should record 3 spans: one fake inbound, one client, one server")
|
||||
traceIdAssert := fmt.Sprintf("traceId=%d", fakeInboundTraceId)
|
||||
for _, span := range spans {
|
||||
assert.Contains(s.T(), span.String(), traceIdAssert, "not part of the fake parent trace: %v", span)
|
||||
if span.OperationName == methodName {
|
||||
kind := fmt.Sprintf("%v", span.Tag("span.kind"))
|
||||
if kind == "client" {
|
||||
clientSpan = span
|
||||
} else if kind == "server" {
|
||||
serverSpan = span
|
||||
}
|
||||
assert.EqualValues(s.T(), span.Tag("component"), "gRPC", "span must be tagged with gRPC component")
|
||||
}
|
||||
}
|
||||
require.NotNil(s.T(), clientSpan, "client span must be there")
|
||||
require.NotNil(s.T(), serverSpan, "server span must be there")
|
||||
assert.EqualValues(s.T(), serverSpan.Tag("grpc.request.value"), "something", "grpc_ctxtags must be propagated, in this case ones from request fields")
|
||||
return clientSpan, serverSpan
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) TestPing_PropagatesTraces() {
|
||||
ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx())
|
||||
_, err := s.Client.Ping(ctx, goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an on a successful call")
|
||||
s.assertTracesCreated("/mwitkow.testproto.TestService/Ping")
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) TestPing_ClientContextTags() {
|
||||
const name = "opentracing.custom"
|
||||
ctx := grpc_opentracing.ClientAddContextTags(
|
||||
s.createContextFromFakeHttpRequestParent(s.SimpleCtx()),
|
||||
opentracing.Tags{name: ""},
|
||||
)
|
||||
|
||||
_, err := s.Client.Ping(ctx, goodPing)
|
||||
require.NoError(s.T(), err, "there must be not be an on a successful call")
|
||||
|
||||
for _, span := range s.mockTracer.FinishedSpans() {
|
||||
if span.OperationName == "/mwitkow.testproto.TestService/Ping" {
|
||||
kind := fmt.Sprintf("%v", span.Tag("span.kind"))
|
||||
if kind == "client" {
|
||||
assert.Contains(s.T(), span.Tags(), name, "custom opentracing.Tags must be included in context")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) TestPingList_PropagatesTraces() {
|
||||
ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx())
|
||||
stream, err := s.Client.PingList(ctx, goodPing)
|
||||
require.NoError(s.T(), err, "should not fail on establishing the stream")
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(s.T(), err, "reading stream should not fail")
|
||||
}
|
||||
s.assertTracesCreated("/mwitkow.testproto.TestService/PingList")
|
||||
}
|
||||
|
||||
func (s *OpentracingSuite) TestPingError_PropagatesTraces() {
|
||||
ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx())
|
||||
erroringPing := &pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(codes.OutOfRange)}
|
||||
_, err := s.Client.PingError(ctx, erroringPing)
|
||||
require.Error(s.T(), err, "there must be an error returned here")
|
||||
clientSpan, serverSpan := s.assertTracesCreated("/mwitkow.testproto.TestService/PingError")
|
||||
assert.Equal(s.T(), true, clientSpan.Tag("error"), "client span needs to be marked as an error")
|
||||
assert.Equal(s.T(), true, serverSpan.Tag("error"), "server span needs to be marked as an error")
|
||||
}
|
||||
56
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/metadata.go
generated
vendored
Normal file
56
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/metadata.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
binHdrSuffix = "-bin"
|
||||
)
|
||||
|
||||
// metadataTextMap extends a metadata.MD to be an opentracing textmap
|
||||
type metadataTextMap metadata.MD
|
||||
|
||||
// Set is a opentracing.TextMapReader interface that extracts values.
|
||||
func (m metadataTextMap) Set(key, val string) {
|
||||
// gRPC allows for complex binary values to be written.
|
||||
encodedKey, encodedVal := encodeKeyValue(key, val)
|
||||
// The metadata object is a multimap, and previous values may exist, but for opentracing headers, we do not append
|
||||
// we just override.
|
||||
m[encodedKey] = []string{encodedVal}
|
||||
}
|
||||
|
||||
// ForeachKey is a opentracing.TextMapReader interface that extracts values.
|
||||
func (m metadataTextMap) ForeachKey(callback func(key, val string) error) error {
|
||||
for k, vv := range m {
|
||||
for _, v := range vv {
|
||||
if decodedKey, decodedVal, err := metadata.DecodeKeyValue(k, v); err == nil {
|
||||
if err = callback(decodedKey, decodedVal); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed decoding opentracing from gRPC metadata: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeKeyValue encodes key and value qualified for transmission via gRPC.
|
||||
// note: copy pasted from private values of grpc.metadata
|
||||
func encodeKeyValue(k, v string) (string, string) {
|
||||
k = strings.ToLower(k)
|
||||
if strings.HasSuffix(k, binHdrSuffix) {
|
||||
val := base64.StdEncoding.EncodeToString([]byte(v))
|
||||
v = string(val)
|
||||
}
|
||||
return k, v
|
||||
}
|
||||
55
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/options.go
generated
vendored
Normal file
55
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/options.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultOptions = &options{
|
||||
filterOutFunc: nil,
|
||||
tracer: nil,
|
||||
}
|
||||
)
|
||||
|
||||
// FilterFunc allows users to provide a function that filters out certain methods from being traced.
|
||||
//
|
||||
// If it returns false, the given request will not be traced.
|
||||
type FilterFunc func(ctx context.Context, fullMethodName string) bool
|
||||
|
||||
type options struct {
|
||||
filterOutFunc FilterFunc
|
||||
tracer opentracing.Tracer
|
||||
}
|
||||
|
||||
func evaluateOptions(opts []Option) *options {
|
||||
optCopy := &options{}
|
||||
*optCopy = *defaultOptions
|
||||
for _, o := range opts {
|
||||
o(optCopy)
|
||||
}
|
||||
if optCopy.tracer == nil {
|
||||
optCopy.tracer = opentracing.GlobalTracer()
|
||||
}
|
||||
return optCopy
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
// WithFilterFunc customizes the function used for deciding whether a given call is traced or not.
|
||||
func WithFilterFunc(f FilterFunc) Option {
|
||||
return func(o *options) {
|
||||
o.filterOutFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// WithTracer sets a custom tracer to be used for this middleware, otherwise the opentracing.GlobalTracer is used.
|
||||
func WithTracer(tracer opentracing.Tracer) Option {
|
||||
return func(o *options) {
|
||||
o.tracer = tracer
|
||||
}
|
||||
}
|
||||
86
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/server_interceptors.go
generated
vendored
Normal file
86
vendor/github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing/server_interceptors.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017 Michal Witkowski. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpc_opentracing
|
||||
|
||||
import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/tags"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
"github.com/opentracing/opentracing-go"
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
var (
|
||||
grpcTag = opentracing.Tag{Key: string(ext.Component), Value: "gRPC"}
|
||||
)
|
||||
|
||||
// UnaryServerInterceptor returns a new unary server interceptor for OpenTracing.
|
||||
func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
|
||||
o := evaluateOptions(opts)
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
if o.filterOutFunc != nil && !o.filterOutFunc(ctx, info.FullMethod) {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
newCtx, serverSpan := newServerSpanFromInbound(ctx, o.tracer, info.FullMethod)
|
||||
resp, err := handler(newCtx, req)
|
||||
finishServerSpan(ctx, serverSpan, err)
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// StreamServerInterceptor returns a new streaming server interceptor for OpenTracing.
|
||||
func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
|
||||
o := evaluateOptions(opts)
|
||||
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
if o.filterOutFunc != nil && !o.filterOutFunc(stream.Context(), info.FullMethod) {
|
||||
return handler(srv, stream)
|
||||
}
|
||||
newCtx, serverSpan := newServerSpanFromInbound(stream.Context(), o.tracer, info.FullMethod)
|
||||
wrappedStream := grpc_middleware.WrapServerStream(stream)
|
||||
wrappedStream.WrappedContext = newCtx
|
||||
err := handler(srv, wrappedStream)
|
||||
finishServerSpan(newCtx, serverSpan, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func newServerSpanFromInbound(ctx context.Context, tracer opentracing.Tracer, fullMethodName string) (context.Context, opentracing.Span) {
|
||||
md := metautils.ExtractIncoming(ctx)
|
||||
parentSpanContext, err := tracer.Extract(opentracing.HTTPHeaders, metadataTextMap(md))
|
||||
if err != nil && err != opentracing.ErrSpanContextNotFound {
|
||||
grpclog.Printf("grpc_opentracing: failed parsing trace information: %v", err)
|
||||
}
|
||||
|
||||
serverSpan := tracer.StartSpan(
|
||||
fullMethodName,
|
||||
// this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty.
|
||||
ext.RPCServerOption(parentSpanContext),
|
||||
grpcTag,
|
||||
)
|
||||
hackyInjectOpentracingIdsToTags(serverSpan, grpc_ctxtags.Extract(ctx))
|
||||
return opentracing.ContextWithSpan(ctx, serverSpan), serverSpan
|
||||
}
|
||||
|
||||
func finishServerSpan(ctx context.Context, serverSpan opentracing.Span, err error) {
|
||||
// Log context information
|
||||
tags := grpc_ctxtags.Extract(ctx)
|
||||
for k, v := range tags.Values() {
|
||||
// Don't tag errors, log them instead.
|
||||
if vErr, ok := v.(error); ok {
|
||||
serverSpan.LogKV(k, vErr.Error())
|
||||
|
||||
} else {
|
||||
serverSpan.SetTag(k, v)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
ext.Error.Set(serverSpan, true)
|
||||
serverSpan.LogFields(log.String("event", "error"), log.String("message", err.Error()))
|
||||
}
|
||||
serverSpan.Finish()
|
||||
}
|
||||
Reference in New Issue
Block a user