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 }