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 }