github.com/influx6/npkg@v0.8.8/ntrace/ntrace.go (about)

     1  package ntrace
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"log"
     8  	"net/http"
     9  
    10  	"github.com/influx6/npkg"
    11  	"github.com/influx6/npkg/nframes"
    12  
    13  	"github.com/opentracing/opentracing-go"
    14  	"github.com/uber/jaeger-client-go"
    15  	jaegercfg "github.com/uber/jaeger-client-go/config"
    16  	jaegerlog "github.com/uber/jaeger-client-go/log"
    17  	"github.com/uber/jaeger-client-go/zipkin"
    18  )
    19  
    20  const (
    21  	// SpanKey provides giving key-name used to store open-tracing span into the context.
    22  	SpanKey = contextKey("SPAN_KEY")
    23  )
    24  
    25  type contextKey string
    26  
    27  // WithKV as new key-value pair into the spans baggage store.
    28  //
    29  // This values get propagated down to child spans from this span.
    30  func WithKV(span opentracing.Span, key string, value string) {
    31  	if span == nil {
    32  		return
    33  	}
    34  	span.SetBaggageItem(key, value)
    35  }
    36  
    37  // WithTag adds a new tag into a giving span.
    38  func WithTag(span opentracing.Span, key string, value interface{}) {
    39  	if span == nil {
    40  		return
    41  	}
    42  	span.SetTag(key, value)
    43  }
    44  
    45  // WithTrace returns a new context.Context and a function which can be used to finish the
    46  // opentracing.Span attached to giving context.
    47  //
    48  // It's an alternative to using Trace.
    49  func WithTrace(ctx context.Context, methodName string) (context.Context, func()) {
    50  	var span opentracing.Span
    51  	ctx, span = NewSpanFromContext(ctx, methodName)
    52  
    53  	return ctx, func() {
    54  		if span == nil {
    55  			return
    56  		}
    57  		span.Finish()
    58  	}
    59  }
    60  
    61  // WithTrace returns a new context.Context and a function which can be used to finish the
    62  // opentracing.Span attached to giving context.
    63  //
    64  // It's an alternative to using Trace.
    65  func WithMethodTrace(ctx context.Context) (context.Context, func()) {
    66  	return WithTrace(ctx, nframes.GetCallerNameWith(2))
    67  }
    68  
    69  // GetSpanFromContext returns a OpenTracing span if available from provided context.
    70  //
    71  // WARNING: Second returned value can be nil if no parent span is in context.
    72  func GetSpanFromContext(ctx context.Context) (opentracing.Span, bool) {
    73  	if span, ok := ctx.Value(SpanKey).(opentracing.Span); ok {
    74  		return span, true
    75  	}
    76  	return nil, false
    77  }
    78  
    79  // NewMethodSpanFromContext returns a OpenTracing span if available from provided context.
    80  // It automatically gets the caller name using runtime.
    81  //
    82  // WARNING: Second returned value can be nil if no parent span is in context.
    83  func NewMethodSpanFromContext(ctx context.Context) (context.Context, opentracing.Span) {
    84  	return NewSpanFromContext(ctx, nframes.GetCallerNameWith(2))
    85  }
    86  
    87  // NewSpanFromContext returns a new OpenTracing child span which was created as a child of a
    88  // existing span from the underline context if it exists, else returning no Span.
    89  //
    90  // WARNING: Second returned value can be nil if no parent span is in context.
    91  func NewSpanFromContext(ctx context.Context, traceName string) (context.Context, opentracing.Span) {
    92  	if span, ok := ctx.Value(SpanKey).(opentracing.Span); ok {
    93  		var childSpan = opentracing.StartSpan(
    94  			traceName,
    95  			opentracing.ChildOf(span.Context()),
    96  		)
    97  
    98  		var newContext = context.WithValue(ctx, SpanKey, childSpan)
    99  		return newContext, childSpan
   100  	}
   101  	return ctx, nil
   102  }
   103  
   104  // CloseOpenTracingMWSpan returns a new middleware for closing the OpenTracing span which
   105  // then writes trace to underline tracing service.
   106  func CloseOpenTracingMWSpan(getter npkg.Getter) func(http.Handler) http.Handler {
   107  	return func(next http.Handler) http.Handler {
   108  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   109  			if span, ok := (*r).Context().Value(SpanKey).(opentracing.Span); ok {
   110  				// Get debug flag if enabled.
   111  				var debug = getter.Bool(npkg.DEBUGKey)
   112  				if debug {
   113  					log.Printf("Closing span, path: " + r.URL.Path)
   114  				}
   115  
   116  				defer span.Finish()
   117  			}
   118  			next.ServeHTTP(w, r)
   119  		})
   120  	}
   121  }
   122  
   123  // OpenTracingSpanMW returns a middleware function able to based on tracing configuration key, enable
   124  // and setup tracing using opentracing spans.
   125  func OpenTracingMW(tracingKey string, getter npkg.Getter) func(http.Handler) http.Handler {
   126  	return func(next http.Handler) http.Handler {
   127  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   128  			var methodName = requestMethodParser(r)
   129  
   130  			// Get debug flag if enabled.
   131  			var debug = getter.Bool(npkg.DEBUGKey)
   132  
   133  			// Get tracing flag if enabled, else skip
   134  			var enabled = getter.Bool(tracingKey)
   135  			if !enabled {
   136  				next.ServeHTTP(w, r)
   137  				return
   138  			}
   139  
   140  			var serverSpan opentracing.Span
   141  
   142  			// Extracting B3 tracing context from the request.
   143  			// This step is important to extract the actual request context
   144  			// from outside of the applications.
   145  			var wireContext, err = opentracing.GlobalTracer().Extract(
   146  				opentracing.HTTPHeaders,
   147  				opentracing.HTTPHeadersCarrier(r.Header),
   148  			)
   149  
   150  			if err != nil {
   151  				if debug {
   152  					log.Printf("Attaching span fails err: " + err.Error())
   153  					for k, h := range r.Header {
   154  						for _, v := range h {
   155  							log.Printf(fmt.Sprintf("Header: %s - %s", k, v))
   156  						}
   157  					}
   158  				}
   159  
   160  				// Create span as a parent without parent span.
   161  				serverSpan = opentracing.StartSpan(
   162  					methodName,
   163  				)
   164  			} else {
   165  				// Create span as a child of parent Span from wireContext.
   166  				serverSpan = opentracing.StartSpan(
   167  					methodName,
   168  					opentracing.ChildOf(wireContext),
   169  				)
   170  			}
   171  
   172  			if debug {
   173  				// Optional: Record span creation
   174  				log.Printf("Attaching span, Starting child span: " + methodName)
   175  			}
   176  
   177  			if traceID := serverSpan.BaggageItem("trace_id"); traceID != "" {
   178  				serverSpan.SetTag("trace_id", traceID)
   179  			} else {
   180  				var traceID = r.Header.Get("X-Request-Id")
   181  				if traceID != "" {
   182  					serverSpan.SetBaggageItem("trace_id", traceID)
   183  					serverSpan.SetTag("trace_id", traceID)
   184  				}
   185  			}
   186  
   187  			// We are passing the context as an item in Go context. Span is also attached
   188  			// so that we can close the span after the request. Span needs to be finished
   189  			// in order to report it to Jaeger collector
   190  			var newContext = context.WithValue(r.Context(), SpanKey, serverSpan)
   191  			next.ServeHTTP(w, r.WithContext(newContext))
   192  		})
   193  	}
   194  }
   195  
   196  // InitZipkinTracer inits the jaeger client
   197  // Service PageName in Jaeger Query - edit accordingly to the Kubernetes service name (ask us if you don't know)
   198  func InitZipkinTracer(srvName string, samplingServerURL, localAgentHost string) (io.Closer, error) {
   199  	// Standard option for starting Zipkin based Tracing, please include.
   200  	var jLogger = jaegerlog.StdLogger
   201  	var zipkinPropagator = zipkin.NewZipkinB3HTTPHeaderPropagator()
   202  	var injector = jaeger.TracerOptions.Injector(opentracing.HTTPHeaders, zipkinPropagator)
   203  	var extractor = jaeger.TracerOptions.Extractor(opentracing.HTTPHeaders, zipkinPropagator)
   204  	var zipkinSharedRPCSpan = jaeger.TracerOptions.ZipkinSharedRPCSpan(true)
   205  
   206  	// Create new jaeger reporter and sampler. URL must be fixed.
   207  	var samplercfg = &jaegercfg.SamplerConfig{
   208  		Type:              jaeger.SamplerTypeConst,
   209  		Param:             1,
   210  		SamplingServerURL: samplingServerURL,
   211  	}
   212  
   213  	var reportercfg = &jaegercfg.ReporterConfig{
   214  		LogSpans:           true,
   215  		LocalAgentHostPort: localAgentHost,
   216  	}
   217  
   218  	// Jaeger sampler and reporter
   219  	var jMetrics = jaeger.NewNullMetrics()
   220  
   221  	var sampler, err = samplercfg.NewSampler(srvName, jMetrics)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	var reporter jaeger.Reporter
   227  	reporter, err = reportercfg.NewReporter(srvName, jMetrics, jLogger)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	var tracer, closer = jaeger.NewTracer(srvName, sampler, reporter, injector, extractor, zipkinSharedRPCSpan)
   233  	opentracing.SetGlobalTracer(tracer)
   234  
   235  	return closer, nil
   236  }
   237  
   238  var requestMethodParser = func(r *http.Request) string {
   239  	var path = r.URL.Path
   240  	var count int
   241  	for i, c := range path {
   242  		if count == 3 {
   243  			return path[:i]
   244  		}
   245  		if c == '/' {
   246  			count++
   247  		}
   248  	}
   249  	return r.Method + path
   250  }