github.com/thanos-io/thanos@v0.32.5/internal/cortex/util/spanlogger/spanlogger.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package spanlogger 5 6 import ( 7 "context" 8 9 "github.com/go-kit/log" 10 "github.com/go-kit/log/level" 11 opentracing "github.com/opentracing/opentracing-go" 12 "github.com/opentracing/opentracing-go/ext" 13 otlog "github.com/opentracing/opentracing-go/log" 14 15 "github.com/thanos-io/thanos/internal/cortex/tenant" 16 util_log "github.com/thanos-io/thanos/internal/cortex/util/log" 17 ) 18 19 type loggerCtxMarker struct{} 20 21 const ( 22 TenantIDTagName = "tenant_ids" 23 ) 24 25 var ( 26 loggerCtxKey = &loggerCtxMarker{} 27 ) 28 29 // SpanLogger unifies tracing and logging, to reduce repetition. 30 type SpanLogger struct { 31 log.Logger 32 opentracing.Span 33 } 34 35 // New makes a new SpanLogger, where logs will be sent to the global logger. 36 func New(ctx context.Context, method string, kvps ...interface{}) (*SpanLogger, context.Context) { 37 return NewWithLogger(ctx, util_log.Logger, method, kvps...) 38 } 39 40 // NewWithLogger makes a new SpanLogger with a custom log.Logger to send logs 41 // to. The provided context will have the logger attached to it and can be 42 // retrieved with FromContext or FromContextWithFallback. 43 func NewWithLogger(ctx context.Context, l log.Logger, method string, kvps ...interface{}) (*SpanLogger, context.Context) { 44 span, ctx := opentracing.StartSpanFromContext(ctx, method) 45 if ids, _ := tenant.TenantIDs(ctx); len(ids) > 0 { 46 span.SetTag(TenantIDTagName, ids) 47 } 48 logger := &SpanLogger{ 49 Logger: log.With(util_log.WithContext(ctx, l), "method", method), 50 Span: span, 51 } 52 if len(kvps) > 0 { 53 level.Debug(logger).Log(kvps...) 54 } 55 56 ctx = context.WithValue(ctx, loggerCtxKey, l) 57 return logger, ctx 58 } 59 60 // FromContext returns a span logger using the current parent span. If there 61 // is no parent span, the SpanLogger will only log to the logger 62 // in the context. If the context doesn't have a logger, the global logger 63 // is used. 64 func FromContext(ctx context.Context) *SpanLogger { 65 return FromContextWithFallback(ctx, util_log.Logger) 66 } 67 68 // FromContextWithFallback returns a span logger using the current parent span. 69 // IF there is no parent span, the SpanLogger will only log to the logger 70 // within the context. If the context doesn't have a logger, the fallback 71 // logger is used. 72 func FromContextWithFallback(ctx context.Context, fallback log.Logger) *SpanLogger { 73 logger, ok := ctx.Value(loggerCtxKey).(log.Logger) 74 if !ok { 75 logger = fallback 76 } 77 sp := opentracing.SpanFromContext(ctx) 78 if sp == nil { 79 sp = defaultNoopSpan 80 } 81 return &SpanLogger{ 82 Logger: util_log.WithContext(ctx, logger), 83 Span: sp, 84 } 85 } 86 87 // Log implements gokit's Logger interface; sends logs to underlying logger and 88 // also puts the on the spans. 89 func (s *SpanLogger) Log(kvps ...interface{}) error { 90 s.Logger.Log(kvps...) 91 fields, err := otlog.InterleavedKVToFields(kvps...) 92 if err != nil { 93 return err 94 } 95 s.Span.LogFields(fields...) 96 return nil 97 } 98 99 // Error sets error flag and logs the error on the span, if non-nil. Returns the err passed in. 100 func (s *SpanLogger) Error(err error) error { 101 if err == nil { 102 return nil 103 } 104 ext.Error.Set(s.Span, true) 105 s.Span.LogFields(otlog.Error(err)) 106 return err 107 }