// Copyright 2017 Google LLC // // 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 trace import ( "context" "encoding/hex" "fmt" "cloud.google.com/go/internal/tracecontext" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) const grpcMetadataKey = "grpc-trace-bin" // GRPCClientInterceptor returns a grpc.UnaryClientInterceptor that traces all outgoing requests from a gRPC client. // The calling context should already have a *trace.Span; a child span will be // created for the outgoing gRPC call. If the calling context doesn't have a span, // the call will not be traced. If the client is nil, then the interceptor just // passes through the request. // // The functionality in gRPC that this feature relies on is currently experimental. // Deprecated: see https://cloud.google.com/trace/docs/setup/go. func (c *Client) GRPCClientInterceptor() grpc.UnaryClientInterceptor { if c == nil { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return invoker(ctx, method, req, reply, cc, opts...) } } return grpc.UnaryClientInterceptor(c.grpcUnaryInterceptor) } func (c *Client) grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { // TODO: also intercept streams. span := FromContext(ctx).NewChild(method) if span == nil { span = c.NewSpan(method) } defer span.Finish() traceContext := make([]byte, tracecontext.Len) // traceID is a hex-encoded 128-bit value. // TODO(jbd): Decode trace IDs upon arrival and // represent trace IDs with 16 bytes internally. tid, err := hex.DecodeString(span.trace.traceID) if err != nil { return invoker(ctx, method, req, reply, cc, opts...) } tracecontext.Encode(traceContext, tid, span.span.SpanId, byte(span.trace.globalOptions)) md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = metadata.Pairs(grpcMetadataKey, string(traceContext)) } else { md = md.Copy() // metadata is immutable, copy. md[grpcMetadataKey] = []string{string(traceContext)} } ctx = metadata.NewOutgoingContext(ctx, md) err = invoker(ctx, method, req, reply, cc, opts...) if err != nil { // TODO: standardize gRPC label names? span.SetLabel("error", err.Error()) } return err } // GRPCServerInterceptor returns a grpc.UnaryServerInterceptor that enables the tracing of the incoming // gRPC calls. Incoming call's context can be used to extract the span on servers that enabled this option: // // span := trace.FromContext(ctx) // // If the client is nil, then the interceptor just invokes the handler. // // The functionality in gRPC that this feature relies on is currently experimental. // // Deprecated: see https://cloud.google.com/trace/docs/setup/go. func (c *Client) GRPCServerInterceptor() grpc.UnaryServerInterceptor { if c == nil { return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return handler(ctx, req) } } return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { md, _ := metadata.FromIncomingContext(ctx) var traceHeader string if header, ok := md[grpcMetadataKey]; ok { traceID, spanID, opts, ok := tracecontext.Decode([]byte(header[0])) if ok { // TODO(jbd): Generate a span directly from string(traceID), spanID and opts. traceHeader = fmt.Sprintf("%x/%d;o=%d", traceID, spanID, opts) } } span := c.SpanFromHeader(info.FullMethod, traceHeader) defer span.Finish() ctx = NewContext(ctx, span) return handler(ctx, req) } }