Skip to content

Commit 63ed326

Browse files
committed
feat(v0.4.5): v0.4.5
1 parent 646a508 commit 63ed326

File tree

8 files changed

+840
-0
lines changed

8 files changed

+840
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package ktracing
2+
3+
import (
4+
"context"
5+
6+
"go.opentelemetry.io/otel/propagation"
7+
8+
"github.com/go-kratos/kratos/v2"
9+
"github.com/go-kratos/kratos/v2/metadata"
10+
)
11+
12+
const serviceHeader = "x-md-service-name"
13+
14+
// Metadata is tracing metadata propagator
15+
type Metadata struct{}
16+
17+
var _ propagation.TextMapPropagator = Metadata{}
18+
19+
// Inject sets metadata key-values from ctx into the carrier.
20+
func (b Metadata) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
21+
app, ok := kratos.FromContext(ctx)
22+
if ok {
23+
carrier.Set(serviceHeader, app.Name())
24+
}
25+
}
26+
27+
// Extract returns a copy of parent with the metadata from the carrier added.
28+
func (b Metadata) Extract(parent context.Context, carrier propagation.TextMapCarrier) context.Context {
29+
name := carrier.Get(serviceHeader)
30+
if name == "" {
31+
return parent
32+
}
33+
if md, ok := metadata.FromServerContext(parent); ok {
34+
md.Set(serviceHeader, name)
35+
return parent
36+
}
37+
md := metadata.New()
38+
md.Set(serviceHeader, name)
39+
parent = metadata.NewServerContext(parent, md)
40+
return parent
41+
}
42+
43+
// Fields returns the keys who's values are set with Inject.
44+
func (b Metadata) Fields() []string {
45+
return []string{serviceHeader}
46+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package ktracing
2+
3+
import (
4+
"context"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/go-kratos/kratos/v2"
9+
"github.com/go-kratos/kratos/v2/metadata"
10+
11+
"go.opentelemetry.io/otel/propagation"
12+
)
13+
14+
func TestMetadata_Inject(t *testing.T) {
15+
type args struct {
16+
appName string
17+
carrier propagation.TextMapCarrier
18+
}
19+
tests := []struct {
20+
name string
21+
args args
22+
want string
23+
}{
24+
{
25+
name: "https://go-kratos.dev",
26+
args: args{"https://go-kratos.dev", propagation.HeaderCarrier{}},
27+
want: "https://go-kratos.dev",
28+
},
29+
{
30+
name: "https://github.com/go-kratos/kratos",
31+
args: args{"https://github.com/go-kratos/kratos", propagation.HeaderCarrier{"mode": []string{"test"}}},
32+
want: "https://github.com/go-kratos/kratos",
33+
},
34+
}
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
a := kratos.New(kratos.Name(tt.args.appName))
38+
ctx := kratos.NewContext(context.Background(), a)
39+
m := new(Metadata)
40+
m.Inject(ctx, tt.args.carrier)
41+
if res := tt.args.carrier.Get(serviceHeader); tt.want != res {
42+
t.Errorf("Get(serviceHeader) :%s want: %s", res, tt.want)
43+
}
44+
})
45+
}
46+
}
47+
48+
func TestMetadata_Extract(t *testing.T) {
49+
type args struct {
50+
parent context.Context
51+
carrier propagation.TextMapCarrier
52+
}
53+
tests := []struct {
54+
name string
55+
args args
56+
want string
57+
crash bool
58+
}{
59+
{
60+
name: "https://go-kratos.dev",
61+
args: args{
62+
parent: context.Background(),
63+
carrier: propagation.HeaderCarrier{"X-Md-Service-Name": []string{"https://go-kratos.dev"}},
64+
},
65+
want: "https://go-kratos.dev",
66+
},
67+
{
68+
name: "https://github.com/go-kratos/kratos",
69+
args: args{
70+
parent: metadata.NewServerContext(context.Background(), metadata.Metadata{}),
71+
carrier: propagation.HeaderCarrier{"X-Md-Service-Name": []string{"https://github.com/go-kratos/kratos"}},
72+
},
73+
want: "https://github.com/go-kratos/kratos",
74+
},
75+
{
76+
name: "https://github.com/go-kratos/kratos",
77+
args: args{
78+
parent: metadata.NewServerContext(context.Background(), metadata.Metadata{}),
79+
carrier: propagation.HeaderCarrier{"X-Md-Service-Name": nil},
80+
},
81+
crash: true,
82+
},
83+
}
84+
for _, tt := range tests {
85+
t.Run(tt.name, func(t *testing.T) {
86+
b := Metadata{}
87+
ctx := b.Extract(tt.args.parent, tt.args.carrier)
88+
md, ok := metadata.FromServerContext(ctx)
89+
if !ok {
90+
if tt.crash {
91+
return
92+
}
93+
t.Errorf("expect %v, got %v", true, ok)
94+
}
95+
if !reflect.DeepEqual(md.Get(serviceHeader), tt.want) {
96+
t.Errorf("expect %v, got %v", tt.want, md.Get(serviceHeader))
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestFields(t *testing.T) {
103+
b := Metadata{}
104+
if !reflect.DeepEqual(b.Fields(), []string{"x-md-service-name"}) {
105+
t.Errorf("expect %v, got %v", []string{"x-md-service-name"}, b.Fields())
106+
}
107+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package ktracing
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/url"
7+
"strings"
8+
9+
"github.com/go-kratos/kratos/v2/metadata"
10+
"github.com/go-kratos/kratos/v2/transport"
11+
"github.com/go-kratos/kratos/v2/transport/http"
12+
13+
"go.opentelemetry.io/otel/attribute"
14+
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
15+
"go.opentelemetry.io/otel/trace"
16+
"google.golang.org/grpc/peer"
17+
"google.golang.org/protobuf/proto"
18+
)
19+
20+
func setClientSpan(ctx context.Context, span trace.Span, m interface{}) {
21+
var (
22+
attrs []attribute.KeyValue
23+
remote string
24+
operation string
25+
rpcKind string
26+
)
27+
tr, ok := transport.FromClientContext(ctx)
28+
if ok {
29+
operation = tr.Operation()
30+
rpcKind = tr.Kind().String()
31+
switch tr.Kind() {
32+
case transport.KindHTTP:
33+
if ht, ok := tr.(http.Transporter); ok {
34+
method := ht.Request().Method
35+
route := ht.PathTemplate()
36+
path := ht.Request().URL.Path
37+
attrs = append(attrs, semconv.HTTPMethodKey.String(method))
38+
attrs = append(attrs, semconv.HTTPRouteKey.String(route))
39+
attrs = append(attrs, semconv.HTTPTargetKey.String(path))
40+
remote = ht.Request().Host
41+
}
42+
case transport.KindGRPC:
43+
remote, _ = parseTarget(tr.Endpoint())
44+
}
45+
}
46+
attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
47+
_, mAttrs := parseFullMethod(operation)
48+
attrs = append(attrs, mAttrs...)
49+
if remote != "" {
50+
attrs = append(attrs, peerAttr(remote)...)
51+
}
52+
if p, ok := m.(proto.Message); ok {
53+
attrs = append(attrs, attribute.Key("send_msg.size").Int(proto.Size(p)))
54+
}
55+
56+
span.SetAttributes(attrs...)
57+
}
58+
59+
func setServerSpan(ctx context.Context, span trace.Span, m interface{}) {
60+
var (
61+
attrs []attribute.KeyValue
62+
remote string
63+
operation string
64+
rpcKind string
65+
)
66+
tr, ok := transport.FromServerContext(ctx)
67+
if ok {
68+
operation = tr.Operation()
69+
rpcKind = tr.Kind().String()
70+
switch tr.Kind() {
71+
case transport.KindHTTP:
72+
if ht, ok := tr.(http.Transporter); ok {
73+
method := ht.Request().Method
74+
route := ht.PathTemplate()
75+
path := ht.Request().URL.Path
76+
attrs = append(attrs, semconv.HTTPMethodKey.String(method))
77+
attrs = append(attrs, semconv.HTTPRouteKey.String(route))
78+
attrs = append(attrs, semconv.HTTPTargetKey.String(path))
79+
remote = ht.Request().RemoteAddr
80+
}
81+
case transport.KindGRPC:
82+
if p, ok := peer.FromContext(ctx); ok {
83+
remote = p.Addr.String()
84+
}
85+
}
86+
}
87+
attrs = append(attrs, semconv.RPCSystemKey.String(rpcKind))
88+
_, mAttrs := parseFullMethod(operation)
89+
attrs = append(attrs, mAttrs...)
90+
attrs = append(attrs, peerAttr(remote)...)
91+
if p, ok := m.(proto.Message); ok {
92+
attrs = append(attrs, attribute.Key("recv_msg.size").Int(proto.Size(p)))
93+
}
94+
if md, ok := metadata.FromServerContext(ctx); ok {
95+
attrs = append(attrs, semconv.PeerServiceKey.String(md.Get(serviceHeader)))
96+
}
97+
98+
span.SetAttributes(attrs...)
99+
}
100+
101+
// parseFullMethod returns a span name following the OpenTelemetry semantic
102+
// conventions as well as all applicable span attribute.KeyValue attributes based
103+
// on a gRPC's FullMethod.
104+
func parseFullMethod(fullMethod string) (string, []attribute.KeyValue) {
105+
name := strings.TrimLeft(fullMethod, "/")
106+
parts := strings.SplitN(name, "/", 2)
107+
if len(parts) != 2 { //nolint:mnd
108+
// Invalid format, does not follow `/package.service/method`.
109+
return name, []attribute.KeyValue{attribute.Key("rpc.operation").String(fullMethod)}
110+
}
111+
112+
var attrs []attribute.KeyValue
113+
if service := parts[0]; service != "" {
114+
attrs = append(attrs, semconv.RPCServiceKey.String(service))
115+
}
116+
if method := parts[1]; method != "" {
117+
attrs = append(attrs, semconv.RPCMethodKey.String(method))
118+
}
119+
return name, attrs
120+
}
121+
122+
// peerAttr returns attributes about the peer address.
123+
func peerAttr(addr string) []attribute.KeyValue {
124+
host, port, err := net.SplitHostPort(addr)
125+
if err != nil {
126+
return []attribute.KeyValue(nil)
127+
}
128+
129+
if host == "" {
130+
host = "127.0.0.1"
131+
}
132+
133+
return []attribute.KeyValue{
134+
semconv.NetPeerIPKey.String(host),
135+
semconv.NetPeerPortKey.String(port),
136+
}
137+
}
138+
139+
func parseTarget(endpoint string) (address string, err error) {
140+
var u *url.URL
141+
u, err = url.Parse(endpoint)
142+
if err != nil {
143+
if u, err = url.Parse("http://" + endpoint); err != nil {
144+
return "", err
145+
}
146+
return u.Host, nil
147+
}
148+
if len(u.Path) > 1 {
149+
return u.Path[1:], nil
150+
}
151+
return endpoint, nil
152+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package ktracing
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"go.opentelemetry.io/otel/trace"
8+
"google.golang.org/grpc/peer"
9+
"google.golang.org/grpc/stats"
10+
)
11+
12+
// ClientHandler is tracing ClientHandler
13+
type ClientHandler struct{}
14+
15+
// HandleConn exists to satisfy gRPC stats.Handler.
16+
func (c *ClientHandler) HandleConn(_ context.Context, _ stats.ConnStats) {
17+
fmt.Println("Handle connection.")
18+
}
19+
20+
// TagConn exists to satisfy gRPC stats.Handler.
21+
func (c *ClientHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context {
22+
return ctx
23+
}
24+
25+
// HandleRPC implements per-RPC tracing and stats instrumentation.
26+
func (c *ClientHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
27+
if _, ok := rs.(*stats.OutHeader); !ok {
28+
return
29+
}
30+
p, ok := peer.FromContext(ctx)
31+
if !ok {
32+
return
33+
}
34+
span := trace.SpanFromContext(ctx)
35+
if span.SpanContext().IsValid() {
36+
span.SetAttributes(peerAttr(p.Addr.String())...)
37+
}
38+
}
39+
40+
// TagRPC implements per-RPC context management.
41+
func (c *ClientHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context {
42+
return ctx
43+
}

0 commit comments

Comments
 (0)