|
- // 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 (
- "encoding/hex"
- "fmt"
-
- "cloud.google.com/go/internal/tracecontext"
- "golang.org/x/net/context"
- "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.
- 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.
- 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)
- }
- }
|