Skip to content

Commit aa1cf1e

Browse files
committed
new
1 parent 7dd0376 commit aa1cf1e

11 files changed

+880
-3
lines changed

go.mod

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
99
github.com/miekg/dns v1.1.50
1010
github.com/opentracing/opentracing-go v1.2.0
11+
github.com/stretchr/testify v1.8.1
1112
github.com/uber/jaeger-client-go v2.30.0+incompatible
1213
github.com/xxjwxc/public v0.0.0-20230103091848-ecbc2d279c6a
1314
golang.org/x/net v0.7.0
@@ -17,9 +18,11 @@ require (
1718
require (
1819
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
1920
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect
21+
github.com/davecgh/go-spew v1.1.1 // indirect
2022
github.com/gookit/color v1.5.2 // indirect
23+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
2124
github.com/pkg/errors v0.9.1 // indirect
22-
github.com/stretchr/testify v1.8.1 // indirect
25+
github.com/pmezard/go-difflib v1.0.0 // indirect
2326
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
2427
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
2528
go.uber.org/atomic v1.10.0 // indirect
@@ -31,7 +34,9 @@ require (
3134
golang.org/x/tools v0.4.0 // indirect
3235
google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 // indirect
3336
google.golang.org/protobuf v1.28.1 // indirect
37+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
3438
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
39+
gopkg.in/yaml.v3 v3.0.1 // indirect
3540
)
3641

3742
// replace github.com/xxjwxc/public => ../public

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
127127
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
128128
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
129129
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
130+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
130131
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
131132
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
132133
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@@ -141,6 +142,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
141142
github.com/muesli/cache2go v0.0.0-20200423001931-a100c5aac93f/go.mod h1:414R+qZrt4f9S2TO/s6YVQMNAXR2KdwqQ7pW+O4oYzU=
142143
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
143144
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
145+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
144146
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
145147
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
146148
github.com/nsqio/go-nsq v1.0.8/go.mod h1:vKq36oyeVXgsS5Q8YEO7WghqidAVXQlcFxzQbQTuDEY=
@@ -381,6 +383,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
381383
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
382384
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
383385
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
386+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
384387
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
385388
gopkg.in/eapache/queue.v1 v1.1.0/go.mod h1:wNtmx1/O7kZSR9zNT1TTOJ7GLpm3Vn7srzlfylFbQwU=
386389
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2017 Michal Witkowski. All Rights Reserved.
2+
// See LICENSE for licensing terms.
3+
4+
package grpc_opentracing
5+
6+
import (
7+
"context"
8+
"io"
9+
"sync"
10+
11+
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
12+
opentracing "github.com/opentracing/opentracing-go"
13+
"github.com/opentracing/opentracing-go/ext"
14+
"github.com/opentracing/opentracing-go/log"
15+
"google.golang.org/grpc"
16+
"google.golang.org/grpc/grpclog"
17+
"google.golang.org/grpc/metadata"
18+
)
19+
20+
// UnaryClientInterceptor returns a new unary client interceptor for OpenTracing.
21+
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
22+
o := evaluateOptions(opts)
23+
return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
24+
if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) {
25+
return invoker(parentCtx, method, req, reply, cc, opts...)
26+
}
27+
newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method)
28+
if o.unaryRequestHandlerFunc != nil {
29+
o.unaryRequestHandlerFunc(clientSpan, req)
30+
}
31+
err := invoker(newCtx, method, req, reply, cc, opts...)
32+
finishClientSpan(clientSpan, err)
33+
return err
34+
}
35+
}
36+
37+
// StreamClientInterceptor returns a new streaming client interceptor for OpenTracing.
38+
func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
39+
o := evaluateOptions(opts)
40+
return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
41+
if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) {
42+
return streamer(parentCtx, desc, cc, method, opts...)
43+
}
44+
newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method)
45+
clientStream, err := streamer(newCtx, desc, cc, method, opts...)
46+
if err != nil {
47+
finishClientSpan(clientSpan, err)
48+
return nil, err
49+
}
50+
return &tracedClientStream{ClientStream: clientStream, clientSpan: clientSpan}, nil
51+
}
52+
}
53+
54+
// type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a
55+
// proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish
56+
// a new ClientStream according to the retry policy.
57+
type tracedClientStream struct {
58+
grpc.ClientStream
59+
mu sync.Mutex
60+
alreadyFinished bool
61+
clientSpan opentracing.Span
62+
}
63+
64+
func (s *tracedClientStream) Header() (metadata.MD, error) {
65+
h, err := s.ClientStream.Header()
66+
if err != nil {
67+
s.finishClientSpan(err)
68+
}
69+
return h, err
70+
}
71+
72+
func (s *tracedClientStream) SendMsg(m interface{}) error {
73+
err := s.ClientStream.SendMsg(m)
74+
if err != nil {
75+
s.finishClientSpan(err)
76+
}
77+
return err
78+
}
79+
80+
func (s *tracedClientStream) CloseSend() error {
81+
err := s.ClientStream.CloseSend()
82+
s.finishClientSpan(err)
83+
return err
84+
}
85+
86+
func (s *tracedClientStream) RecvMsg(m interface{}) error {
87+
err := s.ClientStream.RecvMsg(m)
88+
if err != nil {
89+
s.finishClientSpan(err)
90+
}
91+
return err
92+
}
93+
94+
func (s *tracedClientStream) finishClientSpan(err error) {
95+
s.mu.Lock()
96+
defer s.mu.Unlock()
97+
if !s.alreadyFinished {
98+
finishClientSpan(s.clientSpan, err)
99+
s.alreadyFinished = true
100+
}
101+
}
102+
103+
// ClientAddContextTags returns a context with specified opentracing tags, which
104+
// are used by UnaryClientInterceptor/StreamClientInterceptor when creating a
105+
// new span.
106+
func ClientAddContextTags(ctx context.Context, tags opentracing.Tags) context.Context {
107+
return context.WithValue(ctx, clientSpanTagKey{}, tags)
108+
}
109+
110+
type clientSpanTagKey struct{}
111+
112+
func newClientSpanFromContext(ctx context.Context, tracer opentracing.Tracer, fullMethodName string) (context.Context, opentracing.Span) {
113+
var parentSpanCtx opentracing.SpanContext
114+
if parent := opentracing.SpanFromContext(ctx); parent != nil {
115+
parentSpanCtx = parent.Context()
116+
}
117+
opts := []opentracing.StartSpanOption{
118+
opentracing.ChildOf(parentSpanCtx),
119+
ext.SpanKindRPCClient,
120+
grpcTag,
121+
}
122+
if tagx := ctx.Value(clientSpanTagKey{}); tagx != nil {
123+
if opt, ok := tagx.(opentracing.StartSpanOption); ok {
124+
opts = append(opts, opt)
125+
}
126+
}
127+
clientSpan := tracer.StartSpan(fullMethodName, opts...)
128+
// Make sure we add this to the metadata of the call, so it gets propagated:
129+
md := metautils.ExtractOutgoing(ctx).Clone()
130+
if err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, metadataTextMap(md)); err != nil {
131+
grpclog.Infof("grpc_opentracing: failed serializing trace information: %v", err)
132+
}
133+
ctxWithMetadata := md.ToOutgoing(ctx)
134+
return opentracing.ContextWithSpan(ctxWithMetadata, clientSpan), clientSpan
135+
}
136+
137+
func finishClientSpan(clientSpan opentracing.Span, err error) {
138+
if err != nil && err != io.EOF {
139+
ext.Error.Set(clientSpan, true)
140+
clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error()))
141+
}
142+
clientSpan.Finish()
143+
}

grpc_opentracing/doc.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2017 Michal Witkowski. All Rights Reserved.
2+
// See LICENSE for licensing terms.
3+
4+
/*
5+
`grpc_opentracing` adds OpenTracing
6+
7+
# OpenTracing Interceptors
8+
9+
These are both client-side and server-side interceptors for OpenTracing. They are a provider-agnostic, with backends
10+
such as Zipkin, or Google Stackdriver Trace.
11+
12+
For a service that sends out requests and receives requests, you *need* to use both, otherwise downstream requests will
13+
not have the appropriate requests propagated.
14+
15+
All server-side spans are tagged with grpc_ctxtags information.
16+
17+
For more information see:
18+
http://opentracing.io/documentation/
19+
https://github.com/opentracing/specification/blob/master/semantic_conventions.md
20+
*/
21+
package grpc_opentracing

grpc_opentracing/id_extract.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package grpc_opentracing
2+
3+
import (
4+
"strings"
5+
6+
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
7+
opentracing "github.com/opentracing/opentracing-go"
8+
"google.golang.org/grpc/grpclog"
9+
)
10+
11+
const (
12+
TagTraceId = "trace.traceid"
13+
TagSpanId = "trace.spanid"
14+
TagSampled = "trace.sampled"
15+
jaegerNotSampledFlag = "0"
16+
)
17+
18+
// injectOpentracingIdsToTags writes trace data to ctxtags.
19+
// This is done in an incredibly hacky way, because the public-facing interface of opentracing doesn't give access to
20+
// the TraceId and SpanId of the SpanContext. Only the Tracer's Inject/Extract methods know what these are.
21+
// Most tracers have them encoded as keys with 'traceid' and 'spanid':
22+
// https://github.com/openzipkin/zipkin-go-opentracing/blob/594640b9ef7e5c994e8d9499359d693c032d738c/propagation_ot.go#L29
23+
// https://github.com/opentracing/basictracer-go/blob/1b32af207119a14b1b231d451df3ed04a72efebf/propagation_ot.go#L26
24+
// Jaeger from Uber use one-key schema with next format '{trace-id}:{span-id}:{parent-span-id}:{flags}'
25+
// https://www.jaegertracing.io/docs/client-libraries/#trace-span-identity
26+
// Datadog uses keys ending with 'trace-id' and 'parent-id' (for span) by default:
27+
// https://github.com/DataDog/dd-trace-go/blob/v1/ddtrace/tracer/textmap.go#L77
28+
func injectOpentracingIdsToTags(traceHeaderName string, span opentracing.Span, tags grpc_ctxtags.Tags) {
29+
if err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders,
30+
&tagsCarrier{Tags: tags, traceHeaderName: traceHeaderName}); err != nil {
31+
grpclog.Infof("grpc_opentracing: failed extracting trace info into ctx %v", err)
32+
}
33+
}
34+
35+
// tagsCarrier is a really hacky way of
36+
type tagsCarrier struct {
37+
grpc_ctxtags.Tags
38+
traceHeaderName string
39+
}
40+
41+
func (t *tagsCarrier) Set(key, val string) {
42+
key = strings.ToLower(key)
43+
44+
if key == t.traceHeaderName {
45+
parts := strings.Split(val, ":")
46+
if len(parts) == 4 {
47+
t.Tags.Set(TagTraceId, parts[0])
48+
t.Tags.Set(TagSpanId, parts[1])
49+
50+
if parts[3] != jaegerNotSampledFlag {
51+
t.Tags.Set(TagSampled, "true")
52+
} else {
53+
t.Tags.Set(TagSampled, "false")
54+
}
55+
56+
return
57+
}
58+
}
59+
60+
if strings.Contains(key, "traceid") {
61+
t.Tags.Set(TagTraceId, val) // this will most likely be base-16 (hex) encoded
62+
}
63+
64+
if strings.Contains(key, "spanid") && !strings.Contains(strings.ToLower(key), "parent") {
65+
t.Tags.Set(TagSpanId, val) // this will most likely be base-16 (hex) encoded
66+
}
67+
68+
if strings.Contains(key, "sampled") {
69+
switch val {
70+
case "true", "false":
71+
t.Tags.Set(TagSampled, val)
72+
}
73+
}
74+
75+
if strings.HasSuffix(key, "trace-id") {
76+
t.Tags.Set(TagTraceId, val)
77+
}
78+
79+
if strings.HasSuffix(key, "parent-id") {
80+
t.Tags.Set(TagSpanId, val)
81+
}
82+
}

grpc_opentracing/id_extract_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package grpc_opentracing
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestTagsCarrier_Set_JaegerTraceFormat(t *testing.T) {
12+
var (
13+
fakeTraceSampled = 1
14+
fakeInboundTraceId = "deadbeef"
15+
fakeInboundSpanId = "c0decafe"
16+
traceHeaderName = "uber-trace-id"
17+
)
18+
19+
traceHeaderValue := fmt.Sprintf("%s:%s:%s:%d", fakeInboundTraceId, fakeInboundSpanId, fakeInboundSpanId, fakeTraceSampled)
20+
21+
c := &tagsCarrier{
22+
Tags: grpc_ctxtags.NewTags(),
23+
traceHeaderName: traceHeaderName,
24+
}
25+
26+
c.Set(traceHeaderName, traceHeaderValue)
27+
28+
assert.EqualValues(t, map[string]interface{}{
29+
TagTraceId: fakeInboundTraceId,
30+
TagSpanId: fakeInboundSpanId,
31+
TagSampled: "true",
32+
}, c.Tags.Values())
33+
}

0 commit comments

Comments
 (0)