Files
grafana/pkg/infra/tracing/tracing.go
T
Marcus Efraimsson 862a6a2fa6 Logging: Introduce API for contextual logging (#55198)
Introduces a FromContext method on the log.Logger interface that 
allows contextual key/value pairs to be attached, e.g. per request, 
so that any logger using this API will automatically get the per request 
context attached. The proposal makes the traceID available for 
contextual logger , if available, and would allow logs originating from 
a certain HTTP request to be correlated with traceID.
In addition, when tracing not enabled, skip adding
traceID=00000000000000000000000000000000
to logs.
2022-09-20 18:32:06 +02:00

290 lines
7.0 KiB
Go

package tracing
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
opentracing "github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
ol "github.com/opentracing/opentracing-go/log"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
"github.com/uber/jaeger-client-go/zipkin"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
trace "go.opentelemetry.io/otel/trace"
)
const (
envJaegerAgentHost = "JAEGER_AGENT_HOST"
envJaegerAgentPort = "JAEGER_AGENT_PORT"
)
func ProvideService(cfg *setting.Cfg) (Tracer, error) {
ts, ots, err := parseSettings(cfg)
if err != nil {
return nil, err
}
log.RegisterContextualLogProvider(func(ctx context.Context) ([]interface{}, bool) {
if traceID := TraceIDFromContext(ctx, false); traceID != "" {
return []interface{}{"traceID", traceID}, true
}
return nil, false
})
if ts.enabled {
return ts, ts.initJaegerGlobalTracer()
}
return ots, ots.initOpentelemetryTracer()
}
func parseSettings(cfg *setting.Cfg) (*Opentracing, *Opentelemetry, error) {
ts := &Opentracing{
Cfg: cfg,
log: log.New("tracing"),
}
err := ts.parseSettings()
if err != nil {
return ts, nil, err
}
if ts.enabled {
cfg.Logger.Warn("[Deprecated] the configuration setting 'tracing.jaeger' is deprecated, please use 'tracing.opentelemetry.jaeger' instead")
return ts, nil, nil
}
ots := &Opentelemetry{
Cfg: cfg,
log: log.New("tracing"),
}
err = ots.parseSettingsOpentelemetry()
return ts, ots, err
}
type traceKey struct{}
type traceValue struct {
ID string
IsSampled bool
}
func TraceIDFromContext(c context.Context, requireSampled bool) string {
v := c.Value(traceKey{})
// Return traceID if a) it is present and b) it is sampled when requireSampled param is true
if trace, ok := v.(traceValue); ok && (!requireSampled || trace.IsSampled) {
return trace.ID
}
return ""
}
type Opentracing struct {
enabled bool
address string
customTags map[string]string
samplerType string
samplerParam float64
samplingServerURL string
log log.Logger
closer io.Closer
zipkinPropagation bool
disableSharedZipkinSpans bool
Cfg *setting.Cfg
}
type OpentracingSpan struct {
span opentracing.Span
}
func (ts *Opentracing) parseSettings() error {
var section, err = ts.Cfg.Raw.GetSection("tracing.jaeger")
if err != nil {
return err
}
ts.address = section.Key("address").MustString("")
if ts.address == "" {
host := os.Getenv(envJaegerAgentHost)
port := os.Getenv(envJaegerAgentPort)
if host != "" || port != "" {
ts.address = fmt.Sprintf("%s:%s", host, port)
}
}
if ts.address != "" {
ts.enabled = true
}
ts.customTags = splitTagSettings(section.Key("always_included_tag").MustString(""))
ts.samplerType = section.Key("sampler_type").MustString("")
ts.samplerParam = section.Key("sampler_param").MustFloat64(1)
ts.zipkinPropagation = section.Key("zipkin_propagation").MustBool(false)
ts.disableSharedZipkinSpans = section.Key("disable_shared_zipkin_spans").MustBool(false)
ts.samplingServerURL = section.Key("sampling_server_url").MustString("")
return nil
}
func (ts *Opentracing) initJaegerCfg() (jaegercfg.Configuration, error) {
cfg := jaegercfg.Configuration{
ServiceName: "grafana",
Disabled: !ts.enabled,
Sampler: &jaegercfg.SamplerConfig{
Type: ts.samplerType,
Param: ts.samplerParam,
SamplingServerURL: ts.samplingServerURL,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: false,
LocalAgentHostPort: ts.address,
},
}
_, err := cfg.FromEnv()
if err != nil {
return cfg, err
}
return cfg, nil
}
func (ts *Opentracing) initJaegerGlobalTracer() error {
cfg, err := ts.initJaegerCfg()
if err != nil {
return err
}
jLogger := &jaegerLogWrapper{logger: log.New("jaeger")}
options := []jaegercfg.Option{}
options = append(options, jaegercfg.Logger(jLogger))
for tag, value := range ts.customTags {
options = append(options, jaegercfg.Tag(tag, value))
}
if ts.zipkinPropagation {
zipkinPropagator := zipkin.NewZipkinB3HTTPHeaderPropagator()
options = append(options,
jaegercfg.Injector(opentracing.HTTPHeaders, zipkinPropagator),
jaegercfg.Extractor(opentracing.HTTPHeaders, zipkinPropagator),
)
if !ts.disableSharedZipkinSpans {
options = append(options, jaegercfg.ZipkinSharedRPCSpan(true))
}
}
tracer, closer, err := cfg.NewTracer(options...)
if err != nil {
return err
}
opentracing.SetGlobalTracer(tracer)
ts.closer = closer
return nil
}
func (ts *Opentracing) Run(ctx context.Context) error {
<-ctx.Done()
if ts.closer != nil {
ts.log.Info("Closing tracing")
return ts.closer.Close()
}
return nil
}
func (ts *Opentracing) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span) {
span, ctx := opentracing.StartSpanFromContext(ctx, spanName)
opentracingSpan := OpentracingSpan{span: span}
if sctx, ok := span.Context().(jaeger.SpanContext); ok {
ctx = context.WithValue(ctx, traceKey{}, traceValue{sctx.TraceID().String(), sctx.IsSampled()})
}
return ctx, opentracingSpan
}
func (ts *Opentracing) Inject(ctx context.Context, header http.Header, span Span) {
opentracingSpan, ok := span.(OpentracingSpan)
if !ok {
logger.Error("Failed to cast opentracing span")
}
err := opentracing.GlobalTracer().Inject(
opentracingSpan.span.Context(),
opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(header))
if err != nil {
logger.Error("Failed to inject span context instance", "err", err)
}
}
func (s OpentracingSpan) End() {
s.span.Finish()
}
func (s OpentracingSpan) SetAttributes(key string, value interface{}, kv attribute.KeyValue) {
s.span.SetTag(key, value)
}
func (s OpentracingSpan) SetName(name string) {
s.span.SetOperationName(name)
}
func (s OpentracingSpan) SetStatus(code codes.Code, description string) {
ext.Error.Set(s.span, true)
}
func (s OpentracingSpan) RecordError(err error, options ...trace.EventOption) {
ext.Error.Set(s.span, true)
}
func (s OpentracingSpan) AddEvents(keys []string, values []EventValue) {
fields := []ol.Field{}
for i, v := range values {
if v.Str != "" {
field := ol.String(keys[i], v.Str)
fields = append(fields, field)
}
if v.Num != 0 {
field := ol.Int64(keys[i], v.Num)
fields = append(fields, field)
}
}
s.span.LogFields(fields...)
}
func splitTagSettings(input string) map[string]string {
res := map[string]string{}
tags := strings.Split(input, ",")
for _, v := range tags {
kv := strings.Split(v, ":")
if len(kv) > 1 {
res[kv[0]] = kv[1]
}
}
return res
}
type jaegerLogWrapper struct {
logger log.Logger
}
func (jlw *jaegerLogWrapper) Error(msg string) {
jlw.logger.Error(msg)
}
func (jlw *jaegerLogWrapper) Infof(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
jlw.logger.Info(msg)
}