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 }