You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

109 lines
4.0 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package trace
  15. import (
  16. "encoding/hex"
  17. "fmt"
  18. "cloud.google.com/go/internal/tracecontext"
  19. "golang.org/x/net/context"
  20. "google.golang.org/grpc"
  21. "google.golang.org/grpc/metadata"
  22. )
  23. const grpcMetadataKey = "grpc-trace-bin"
  24. // GRPCClientInterceptor returns a grpc.UnaryClientInterceptor that traces all outgoing requests from a gRPC client.
  25. // The calling context should already have a *trace.Span; a child span will be
  26. // created for the outgoing gRPC call. If the calling context doesn't have a span,
  27. // the call will not be traced. If the client is nil, then the interceptor just
  28. // passes through the request.
  29. //
  30. // The functionality in gRPC that this feature relies on is currently experimental.
  31. func (c *Client) GRPCClientInterceptor() grpc.UnaryClientInterceptor {
  32. if c == nil {
  33. return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  34. return invoker(ctx, method, req, reply, cc, opts...)
  35. }
  36. }
  37. return grpc.UnaryClientInterceptor(c.grpcUnaryInterceptor)
  38. }
  39. func (c *Client) grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  40. // TODO: also intercept streams.
  41. span := FromContext(ctx).NewChild(method)
  42. if span == nil {
  43. span = c.NewSpan(method)
  44. }
  45. defer span.Finish()
  46. traceContext := make([]byte, tracecontext.Len)
  47. // traceID is a hex-encoded 128-bit value.
  48. // TODO(jbd): Decode trace IDs upon arrival and
  49. // represent trace IDs with 16 bytes internally.
  50. tid, err := hex.DecodeString(span.trace.traceID)
  51. if err != nil {
  52. return invoker(ctx, method, req, reply, cc, opts...)
  53. }
  54. tracecontext.Encode(traceContext, tid, span.span.SpanId, byte(span.trace.globalOptions))
  55. md, ok := metadata.FromOutgoingContext(ctx)
  56. if !ok {
  57. md = metadata.Pairs(grpcMetadataKey, string(traceContext))
  58. } else {
  59. md = md.Copy() // metadata is immutable, copy.
  60. md[grpcMetadataKey] = []string{string(traceContext)}
  61. }
  62. ctx = metadata.NewOutgoingContext(ctx, md)
  63. err = invoker(ctx, method, req, reply, cc, opts...)
  64. if err != nil {
  65. // TODO: standardize gRPC label names?
  66. span.SetLabel("error", err.Error())
  67. }
  68. return err
  69. }
  70. // GRPCServerInterceptor returns a grpc.UnaryServerInterceptor that enables the tracing of the incoming
  71. // gRPC calls. Incoming call's context can be used to extract the span on servers that enabled this option:
  72. //
  73. // span := trace.FromContext(ctx)
  74. //
  75. // If the client is nil, then the interceptor just invokes the handler.
  76. //
  77. // The functionality in gRPC that this feature relies on is currently experimental.
  78. func (c *Client) GRPCServerInterceptor() grpc.UnaryServerInterceptor {
  79. if c == nil {
  80. return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
  81. return handler(ctx, req)
  82. }
  83. }
  84. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  85. md, _ := metadata.FromIncomingContext(ctx)
  86. var traceHeader string
  87. if header, ok := md[grpcMetadataKey]; ok {
  88. traceID, spanID, opts, ok := tracecontext.Decode([]byte(header[0]))
  89. if ok {
  90. // TODO(jbd): Generate a span directly from string(traceID), spanID and opts.
  91. traceHeader = fmt.Sprintf("%x/%d;o=%d", traceID, spanID, opts)
  92. }
  93. }
  94. span := c.SpanFromHeader(info.FullMethod, traceHeader)
  95. defer span.Finish()
  96. ctx = NewContext(ctx, span)
  97. return handler(ctx, req)
  98. }
  99. }