sigs.k8s.io/cluster-api-provider-azure@v1.14.3/util/tele/span_logger.go (about)

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tele
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/go-logr/logr"
    25  	"go.opentelemetry.io/otel/attribute"
    26  	"go.opentelemetry.io/otel/trace"
    27  	"sigs.k8s.io/controller-runtime/pkg/log"
    28  )
    29  
    30  // spanLogSink is a logr.LogSink implementation that writes log
    31  // data to a span.
    32  type spanLogSink struct {
    33  	trace.Span
    34  	name string
    35  	vals []interface{}
    36  }
    37  
    38  func (*spanLogSink) Init(info logr.RuntimeInfo) {
    39  }
    40  
    41  func (s *spanLogSink) End(opts ...trace.SpanEndOption) {
    42  	s.Span.End(opts...)
    43  }
    44  
    45  func (*spanLogSink) Enabled(v int) bool {
    46  	return true
    47  }
    48  
    49  func (s *spanLogSink) kvsToAttrs(keysAndValues ...interface{}) []attribute.KeyValue {
    50  	var ret []attribute.KeyValue
    51  	for i := 0; i < len(keysAndValues); i += 2 {
    52  		kv1 := fmt.Sprintf("%s", keysAndValues[i])
    53  		kv2 := fmt.Sprintf("%s", keysAndValues[i+1])
    54  		ret = append(ret, attribute.String(kv1, kv2))
    55  	}
    56  	for i := 0; i < len(s.vals); i += 2 {
    57  		kv1 := fmt.Sprintf("%s", s.vals[i])
    58  		kv2 := fmt.Sprintf("%s", s.vals[i+1])
    59  		ret = append(ret, attribute.String(kv1, kv2))
    60  	}
    61  	return ret
    62  }
    63  
    64  func (s *spanLogSink) evtStr(evtType, msg string) string {
    65  	return fmt.Sprintf(
    66  		"[%s | %s] %s",
    67  		evtType,
    68  		s.name,
    69  		msg,
    70  	)
    71  }
    72  
    73  func (s *spanLogSink) Info(level int, msg string, keysAndValues ...interface{}) {
    74  	attrs := s.kvsToAttrs(keysAndValues...)
    75  	s.AddEvent(
    76  		s.evtStr("INFO", msg),
    77  		trace.WithTimestamp(time.Now()),
    78  		trace.WithAttributes(attrs...),
    79  	)
    80  }
    81  
    82  func (s *spanLogSink) Error(err error, msg string, keysAndValues ...interface{}) {
    83  	attrs := s.kvsToAttrs(keysAndValues...)
    84  	s.AddEvent(
    85  		s.evtStr("ERROR", fmt.Sprintf("%s (%s)", msg, err)),
    86  		trace.WithTimestamp(time.Now()),
    87  		trace.WithAttributes(attrs...),
    88  	)
    89  }
    90  
    91  func (s *spanLogSink) WithValues(keysAndValues ...interface{}) logr.LogSink {
    92  	s.vals = append(s.vals, keysAndValues...)
    93  	return s
    94  }
    95  
    96  func (s *spanLogSink) WithName(name string) logr.LogSink {
    97  	s.name = name
    98  	return s
    99  }
   100  
   101  // NewSpanLogSink is the main entry-point to this implementation.
   102  func NewSpanLogSink(span trace.Span) logr.LogSink {
   103  	return &spanLogSink{
   104  		Span: span,
   105  	}
   106  }
   107  
   108  // Config holds optional, arbitrary configuration information
   109  // to be added to logs and telemetry data. Instances of
   110  // Config get passed to StartSpanWithLogger via the KVP function.
   111  type Config struct {
   112  	KVPs map[string]string
   113  }
   114  
   115  func (c Config) teleKeyValues() []attribute.KeyValue {
   116  	ret := make([]attribute.KeyValue, len(c.KVPs))
   117  	i := 0
   118  	for k, v := range c.KVPs {
   119  		ret[i] = attribute.String(k, v)
   120  		i++
   121  	}
   122  	return ret
   123  }
   124  
   125  // Option is the modifier function used to configure
   126  // StartSpanWithLogger. Generally speaking, you should
   127  // not create your own option function. Instead, use
   128  // built-in functions (like KVP) that create them.
   129  type Option func(*Config)
   130  
   131  // KVP returns a new Option function that adds the given
   132  // key-value pair.
   133  func KVP(key, value string) Option {
   134  	return func(cfg *Config) {
   135  		cfg.KVPs[key] = value
   136  	}
   137  }
   138  
   139  // StartSpanWithLogger starts a new span with the global
   140  // tracer returned from Tracer(), then returns a new logger
   141  // implementation that composes both the logger from the
   142  // given ctx and a logger that logs to the newly created span.
   143  //
   144  // Callers should make sure to call the function in the 3rd return
   145  // value to ensure that the span is ended properly. In many cases,
   146  // that can be done with a defer:
   147  //
   148  //	ctx, lggr, done := StartSpanWithLogger(ctx, "my-span")
   149  //	defer done()
   150  func StartSpanWithLogger(
   151  	ctx context.Context,
   152  	spanName string,
   153  	opts ...Option,
   154  ) (context.Context, logr.Logger, func()) {
   155  	cfg := &Config{KVPs: make(map[string]string)}
   156  	for _, opt := range opts {
   157  		opt(cfg)
   158  	}
   159  	ctx, span := Tracer().Start(
   160  		ctx,
   161  		spanName,
   162  		trace.WithAttributes(cfg.teleKeyValues()...),
   163  	)
   164  	endFn := func() {
   165  		span.End()
   166  	}
   167  
   168  	kvs := make([]interface{}, 0, 2*len(cfg.KVPs))
   169  	for k, v := range cfg.KVPs {
   170  		kvs = append(kvs, k, v)
   171  	}
   172  
   173  	lggr := log.FromContext(ctx, kvs...).WithName(spanName)
   174  	return ctx, NewCompositeLogger([]logr.LogSink{
   175  		corrIDLogger(ctx, lggr).GetSink(),
   176  		NewSpanLogSink(span),
   177  	}), endFn
   178  }