gitlab.com/gitlab-org/labkit@v1.21.0/tracing/impl/stackdriver_tracer.go (about) 1 // +build tracer_static,tracer_static_stackdriver 2 3 package impl 4 5 import ( 6 "context" 7 "encoding/base64" 8 "fmt" 9 "io" 10 "reflect" 11 "strconv" 12 "time" 13 14 "contrib.go.opencensus.io/exporter/stackdriver" 15 opentracing "github.com/opentracing/opentracing-go" 16 opentracinglog "github.com/opentracing/opentracing-go/log" 17 log "github.com/sirupsen/logrus" 18 "go.opencensus.io/trace" 19 "go.opencensus.io/trace/propagation" 20 ) 21 22 // to play nice with grpc_opentracing tagsCarrier 23 // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/id_extract.go#L30 24 // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/options.go#L48 25 var traceHeader = "uber-trace-id" 26 27 // https://pkg.go.dev/github.com/opentracing/opentracing-go#Tracer 28 type adapterTracer struct { 29 exporter *stackdriver.Exporter 30 } 31 32 // opencensus does not support overwriting StartTime; so we only implement Tags 33 // https://pkg.go.dev/github.com/opentracing/opentracing-go#StartSpanOption 34 func (tracer *adapterTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { 35 sso := opentracing.StartSpanOptions{} 36 for _, o := range opts { 37 o.Apply(&sso) 38 } 39 40 var ctx context.Context 41 var adapterSpanCtx *adapterSpanContext 42 ctx = context.Background() 43 44 for _, ref := range sso.References { 45 if ref.Type == opentracing.ChildOfRef { 46 if v, ok := ref.ReferencedContext.(adapterSpanContext); ok { 47 ctx = v.ctx 48 adapterSpanCtx = &v 49 break 50 } 51 } 52 } 53 54 var span *trace.Span 55 if adapterSpanCtx != nil && adapterSpanCtx.remote { 56 ctx, span = trace.StartSpanWithRemoteParent(ctx, operationName, *adapterSpanCtx.ocSpanCtx) 57 } else { 58 ctx, span = trace.StartSpan(ctx, operationName) 59 } 60 61 spanContext := span.SpanContext() 62 adapterSpan := &adapterSpan{span, tracer, adapterSpanContext{ctx, false, &spanContext}} 63 for k, v := range sso.Tags { 64 adapterSpan.SetTag(k, v) 65 } 66 return adapterSpan 67 } 68 69 func (tracer *adapterTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error { 70 c, ok := sm.(adapterSpanContext) 71 if !ok { 72 return opentracing.ErrInvalidSpanContext 73 } 74 if format != opentracing.TextMap && format != opentracing.HTTPHeaders { 75 return opentracing.ErrUnsupportedFormat 76 } 77 78 ocSpanCtx := trace.FromContext(c.ctx).SpanContext() 79 encoded := base64.StdEncoding.EncodeToString(propagation.Binary(ocSpanCtx)) 80 carrier.(opentracing.TextMapWriter).Set(traceHeader, string(encoded)) 81 82 return nil 83 } 84 85 func (tracer *adapterTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { 86 if format != opentracing.TextMap && format != opentracing.HTTPHeaders { 87 return nil, opentracing.ErrUnsupportedFormat 88 } 89 90 var ocSpanCtx trace.SpanContext 91 92 err := carrier.(opentracing.TextMapReader).ForeachKey(func(key, val string) error { 93 var ok bool 94 if key == traceHeader { 95 decoded, err := base64.StdEncoding.DecodeString(val) 96 if err != nil { 97 return err 98 } 99 ocSpanCtx, ok = propagation.FromBinary(decoded) 100 if !ok { 101 return opentracing.ErrInvalidCarrier 102 } 103 } 104 return nil 105 }) 106 if err != nil { 107 return nil, err 108 } 109 110 return adapterSpanContext{ 111 ctx: context.Background(), 112 remote: true, 113 ocSpanCtx: &ocSpanCtx, 114 }, nil 115 } 116 117 type adapterSpanContext struct { 118 ctx context.Context 119 remote bool 120 ocSpanCtx *trace.SpanContext 121 } 122 123 func (c adapterSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} 124 func (c adapterSpanContext) IsSampled() bool { 125 return c.ocSpanCtx.IsSampled() 126 } 127 128 // https://pkg.go.dev/go.opencensus.io/trace#Span 129 // https://pkg.go.dev/github.com/opentracing/opentracing-go#Span 130 type adapterSpan struct { 131 span trace.SpanInterface 132 tracer opentracing.Tracer 133 spanContext adapterSpanContext 134 } 135 136 func (span *adapterSpan) Finish() { 137 span.span.End() 138 } 139 140 func (span *adapterSpan) FinishWithOptions(opts opentracing.FinishOptions) { 141 span.span.End() 142 } 143 144 func (span *adapterSpan) Context() opentracing.SpanContext { 145 return span.spanContext 146 } 147 148 func (span *adapterSpan) SetOperationName(operationName string) opentracing.Span { 149 span.span.SetName(operationName) 150 return span 151 } 152 153 func castToAttribute(key string, value interface{}) []trace.Attribute { 154 switch v := reflect.ValueOf(value); v.Kind() { 155 case reflect.Bool: 156 return []trace.Attribute{trace.BoolAttribute(key, v.Bool())} 157 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 158 return []trace.Attribute{trace.Int64Attribute(key, v.Int())} 159 case reflect.Float32, reflect.Float64: 160 return []trace.Attribute{trace.Float64Attribute(key, v.Float())} 161 case reflect.String: 162 chunks := chunkString(v.String(), 256) 163 attrs := []trace.Attribute{} 164 for i, chunk := range chunks { 165 var k string 166 if i == 0 { 167 k = key 168 } else { 169 k = key + "." + strconv.Itoa(i) 170 } 171 attrs = append(attrs, trace.StringAttribute(k, chunk)) 172 } 173 return attrs 174 default: 175 return []trace.Attribute{trace.StringAttribute(key, fmt.Sprintf("castToAttribute not implemented for type %+v", v.Kind()))} 176 } 177 } 178 179 // stackdriver limits attribute values to 256 bytes 180 // we use runes here, hopefully that's close enough 181 // values get truncated silently, so we can set $key to the full value 182 // and then $key.1, $key.2, $key.3 are explicitly sliced. 183 func chunkString(s string, chunkSize int) []string { 184 if len(s) == 0 { 185 return []string{s} 186 } 187 var strs []string 188 for i := 0; i*chunkSize < len(s); i++ { 189 end := (i + 1) * chunkSize 190 if end > len(s) { 191 end = len(s) 192 } 193 strs = append(strs, s[i*chunkSize:end]) 194 } 195 return strs 196 } 197 198 func (span *adapterSpan) SetTag(key string, value interface{}) opentracing.Span { 199 span.span.AddAttributes(castToAttribute(key, value)...) 200 return span 201 } 202 203 func (span *adapterSpan) LogFields(fields ...opentracinglog.Field) { 204 eventName := "" 205 attributes := []trace.Attribute{} 206 for _, field := range fields { 207 if field.Key() == "event" { 208 eventName = field.String() 209 continue 210 } 211 attributes = append(attributes, castToAttribute(field.Key(), field.Value())...) 212 } 213 span.span.Annotate(attributes, eventName) 214 } 215 216 func (span *adapterSpan) LogKV(alternatingKeyValues ...interface{}) { 217 if (len(alternatingKeyValues) % 2) != 0 { 218 log.Print("stackdriver tracer: warning: even number of arguments required to LogKV") 219 } 220 221 attributes := []trace.Attribute{} 222 eventName := "" 223 224 for i := 0; i < len(alternatingKeyValues); i += 2 { 225 key := alternatingKeyValues[i].(string) 226 value := alternatingKeyValues[i+1] 227 if key == "event" { 228 eventName = value.(string) 229 continue 230 } 231 attributes = append(attributes, castToAttribute(key, value)...) 232 } 233 234 span.span.Annotate(attributes, eventName) 235 } 236 237 func (span *adapterSpan) SetBaggageItem(restrictedKey, value string) opentracing.Span { 238 return span 239 } 240 241 func (span *adapterSpan) BaggageItem(restrictedKey string) string { 242 // not implemented 243 return "" 244 } 245 246 func (span *adapterSpan) Tracer() opentracing.Tracer { 247 return span.tracer 248 } 249 250 // Deprecated: use LogFields or LogKV. 251 func (span *adapterSpan) LogEvent(event string) { 252 // not implemented 253 } 254 255 // Deprecated: use LogFields or LogKV. 256 func (span *adapterSpan) LogEventWithPayload(event string, payload interface{}) { 257 // not implemented 258 } 259 260 // Deprecated: use LogFields or LogKV. 261 func (span *adapterSpan) Log(data opentracing.LogData) { 262 // not implemented 263 } 264 265 type stackdriverCloser struct { 266 exporter *stackdriver.Exporter 267 } 268 269 func (c stackdriverCloser) Close() error { 270 c.exporter.Flush() 271 return nil 272 } 273 274 type stackdriverConfig struct { 275 options *stackdriver.Options 276 sampler trace.Sampler 277 } 278 279 // https://pkg.go.dev/contrib.go.opencensus.io/exporter/stackdriver#Options 280 var stackdriverConfigMapper = map[string]func(traceCfg *stackdriverConfig, value string) error{ 281 "project_id": func(stackdriverCfg *stackdriverConfig, value string) error { 282 stackdriverCfg.options.ProjectID = value 283 return nil 284 }, 285 "location": func(stackdriverCfg *stackdriverConfig, value string) error { 286 stackdriverCfg.options.Location = value 287 return nil 288 }, 289 "bundle_delay_threshold": func(stackdriverCfg *stackdriverConfig, value string) error { 290 d, err := time.ParseDuration(value) 291 if err != nil { 292 return err 293 } 294 stackdriverCfg.options.BundleDelayThreshold = d 295 return nil 296 }, 297 "bundle_count_threshold": func(stackdriverCfg *stackdriverConfig, value string) error { 298 v, err := strconv.Atoi(value) 299 if err != nil { 300 return err 301 } 302 stackdriverCfg.options.BundleCountThreshold = v 303 return nil 304 }, 305 "trace_spans_buffer_max_bytes": func(stackdriverCfg *stackdriverConfig, value string) error { 306 v, err := strconv.Atoi(value) 307 if err != nil { 308 return err 309 } 310 stackdriverCfg.options.TraceSpansBufferMaxBytes = v 311 return nil 312 }, 313 "timeout": func(stackdriverCfg *stackdriverConfig, value string) error { 314 d, err := time.ParseDuration(value) 315 if err != nil { 316 return err 317 } 318 stackdriverCfg.options.Timeout = d 319 return nil 320 }, 321 "number_of_workers": func(stackdriverCfg *stackdriverConfig, value string) error { 322 v, err := strconv.Atoi(value) 323 if err != nil { 324 return err 325 } 326 stackdriverCfg.options.NumberOfWorkers = v 327 return nil 328 }, 329 "sampler_probability": func(stackdriverCfg *stackdriverConfig, value string) error { 330 v, err := strconv.ParseFloat(value, 64) 331 if err != nil { 332 return err 333 } 334 stackdriverCfg.sampler = trace.ProbabilitySampler(v) 335 return nil 336 }, 337 } 338 339 func stackdriverTracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) { 340 stackdriverCfg := &stackdriverConfig{ 341 options: &stackdriver.Options{}, 342 sampler: trace.NeverSample(), 343 } 344 345 for k, v := range config { 346 mapper := stackdriverConfigMapper[k] 347 if mapper != nil { 348 err := mapper(stackdriverCfg, v) 349 if err != nil { 350 return nil, nil, err 351 } 352 } else { 353 log.Printf("stackdriver tracer: warning: ignoring unknown configuration option: %s", k) 354 } 355 } 356 357 exporter, err := stackdriver.NewExporter(*stackdriverCfg.options) 358 if err != nil { 359 return nil, nil, err 360 } 361 362 trace.RegisterExporter(exporter) 363 trace.ApplyConfig(trace.Config{DefaultSampler: stackdriverCfg.sampler}) 364 365 return &adapterTracer{exporter}, &stackdriverCloser{exporter}, nil 366 } 367 368 func init() { 369 registerTracer("stackdriver", stackdriverTracerFactory) 370 }