github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/util/wrapper/opentracing.go (about)

     1  package wrapper
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/tickoalcantara12/micro/v3/service/client"
    12  	mmd "github.com/tickoalcantara12/micro/v3/service/context/metadata"
    13  	"github.com/tickoalcantara12/micro/v3/service/logger"
    14  	"github.com/tickoalcantara12/micro/v3/service/server"
    15  	"github.com/tickoalcantara12/micro/v3/util/opentelemetry"
    16  	"github.com/opentracing/opentracing-go"
    17  	"github.com/opentracing/opentracing-go/ext"
    18  )
    19  
    20  // OpenTraceHandler wraps a server handler to perform opentracing:
    21  func OpenTraceHandler() server.HandlerWrapper {
    22  	// return a handler wrapper
    23  	return func(h server.HandlerFunc) server.HandlerFunc {
    24  
    25  		// return a function that returns a function
    26  		return func(ctx context.Context, req server.Request, rsp interface{}) error {
    27  			// Concatenate the operation name:
    28  			operationName := fmt.Sprintf(req.Service() + "." + req.Endpoint())
    29  
    30  			// Don't trace calls to debug:
    31  			if strings.HasPrefix(req.Endpoint(), "Debug.") {
    32  				return h(ctx, req, rsp)
    33  			}
    34  			md, ok := mmd.FromContext(ctx)
    35  			if !ok {
    36  				md = mmd.Metadata{}
    37  			}
    38  			spanCtx, err := opentelemetry.DefaultOpenTracer.Extract(opentracing.TextMap, opentelemetry.MicroMetadataReaderWriter{md})
    39  			if err != nil && err != opentracing.ErrSpanContextNotFound {
    40  				logger.Errorf("Error reconstructing span %s", err)
    41  			}
    42  
    43  			// Start a span from context:
    44  			span, newCtx := opentracing.StartSpanFromContextWithTracer(ctx, opentelemetry.DefaultOpenTracer, operationName, opentracing.ChildOf(spanCtx), ext.SpanKindRPCServer)
    45  			// TODO remove me
    46  			ext.SamplingPriority.Set(span, 1)
    47  			defer span.Finish()
    48  
    49  			// Make the service call, and include error info (if any):
    50  			if err := h(newCtx, req, rsp); err != nil {
    51  				span.SetBaggageItem("error", err.Error())
    52  				return err
    53  			}
    54  
    55  			return nil
    56  		}
    57  	}
    58  }
    59  
    60  type httpWrapper struct {
    61  	handler http.Handler
    62  }
    63  
    64  func (hw *httpWrapper) ServeHTTP(rsp http.ResponseWriter, req *http.Request) {
    65  	// We'll use this for the operation name:
    66  	operationName := req.RequestURI
    67  
    68  	// Initialise a statusRecorder with an assumed 200 status:
    69  	statusRecorder := &statusRecorder{rsp, 200}
    70  
    71  	// Start a span:
    72  	span, newCtx := opentracing.StartSpanFromContext(req.Context(), operationName, ext.SpanKindRPCServer)
    73  	ext.SamplingPriority.Set(span, 1)
    74  	defer span.Finish()
    75  
    76  	// Handle the request:
    77  	hw.handler.ServeHTTP(statusRecorder, req.WithContext(newCtx))
    78  
    79  	// Add trace metadata:
    80  	span.SetTag("req.method", req.Method)
    81  	span.SetTag("rsp.code", statusRecorder.statusCode)
    82  
    83  	// Error text:
    84  	if statusRecorder.statusCode >= 500 {
    85  		span.SetBaggageItem("error", http.StatusText(statusRecorder.statusCode))
    86  	}
    87  }
    88  
    89  // HTTPWrapper returns an HTTP handler wrapper:
    90  func HTTPWrapper(h http.Handler) http.Handler {
    91  	return &httpWrapper{
    92  		handler: h,
    93  	}
    94  }
    95  
    96  // statusRecorder wraps http.ResponseWriter so we can actually capture the status code:
    97  type statusRecorder struct {
    98  	http.ResponseWriter
    99  	statusCode int
   100  }
   101  
   102  // Hijack returns the underlying connection
   103  func (sr *statusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   104  	return sr.ResponseWriter.(http.Hijacker).Hijack()
   105  }
   106  
   107  // WriteHeader is where we capture the status:
   108  func (sr *statusRecorder) WriteHeader(statusCode int) {
   109  	sr.statusCode = statusCode
   110  	sr.ResponseWriter.WriteHeader(statusCode)
   111  }
   112  
   113  type opentraceWrapper struct {
   114  	client.Client
   115  }
   116  
   117  func (o *opentraceWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
   118  	var span opentracing.Span
   119  	ctx, span = o.wrapContext(ctx, req, opts...)
   120  	defer span.Finish()
   121  	err := o.Client.Call(ctx, req, rsp, opts...)
   122  	if err != nil {
   123  		span.SetBaggageItem("error", err.Error())
   124  		return err
   125  	}
   126  	return nil
   127  
   128  }
   129  
   130  func (o *opentraceWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) {
   131  	var span opentracing.Span
   132  	ctx, span = o.wrapContext(ctx, req, opts...)
   133  	s, err := o.Client.Stream(ctx, req, opts...)
   134  	if err != nil {
   135  		span.SetBaggageItem("error", err.Error())
   136  		span.Finish()
   137  		return s, err
   138  	}
   139  	go func() {
   140  		<-s.Context().Done()
   141  		span.Finish()
   142  	}()
   143  	return s, nil
   144  
   145  }
   146  
   147  func (o *opentraceWrapper) wrapContext(ctx context.Context, req client.Request, opts ...client.CallOption) (context.Context, opentracing.Span) {
   148  	// set the open tracing headers
   149  	md := mmd.Metadata{}
   150  	operationName := fmt.Sprintf(req.Service() + "." + req.Endpoint())
   151  	span, newCtx := opentracing.StartSpanFromContextWithTracer(ctx, opentelemetry.DefaultOpenTracer, operationName, ext.SpanKindRPCClient)
   152  	if err := opentelemetry.DefaultOpenTracer.Inject(span.Context(), opentracing.TextMap, opentelemetry.MicroMetadataReaderWriter{md}); err != nil {
   153  		logger.Errorf("Error injecting span %s", err)
   154  	}
   155  	ctx = mmd.MergeContext(newCtx, md, true)
   156  
   157  	return ctx, span
   158  }
   159  
   160  // OpentraceClient wraps requests with the open tracing headers
   161  func OpentraceClient(c client.Client) client.Client {
   162  	return &opentraceWrapper{c}
   163  }