get.porter.sh/porter@v1.3.0/pkg/tracing/traceLogger.go (about) 1 package tracing 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "runtime/debug" 8 "strings" 9 10 "go.opentelemetry.io/otel/attribute" 11 "go.opentelemetry.io/otel/codes" 12 semconv "go.opentelemetry.io/otel/semconv/v1.4.0" 13 "go.opentelemetry.io/otel/trace" 14 "go.uber.org/zap" 15 "go.uber.org/zap/zapcore" 16 ) 17 18 // traceSensitiveAttributes is a build flag that is off for release builds of Porter 19 // A Porter developer may decide that they need sensitive data included in traces, and can create a custom build of Porter with this flag enabled. 20 // When it is off, calls to SetSensitiveAttributes does nothing. 21 // You can enable it with `go build 22 var traceSensitiveAttributes = false 23 24 // IsTraceSensitiveAttributesEnabled returns if sensitive data traced with TraceLogger.SetSensitiveAttributes 25 // will trace the data. Defaults to false, which means that function is a noop and does nothing. 26 func IsTraceSensitiveAttributesEnabled() bool { 27 return traceSensitiveAttributes 28 } 29 30 // TraceLogger how porter emits traces and logs to any configured listeners. 31 type TraceLogger interface { 32 // StartSpan retrieves a logger from the current context and starts a new span 33 // named after the current function. 34 StartSpan(attrs ...attribute.KeyValue) (context.Context, TraceLogger) 35 36 // StartSpanWithName retrieves a logger from the current context and starts a span with 37 // the specified name. 38 StartSpanWithName(ops string, attrs ...attribute.KeyValue) (context.Context, TraceLogger) 39 40 // SetAttributes applies additional key/value pairs to the current trace span. 41 SetAttributes(attrs ...attribute.KeyValue) 42 43 // SetSensitiveAttributes applies attributes that contain SENSITIVE DATA. It is only enabled on debug builds of Porter with the traceSensitiveAttributes build tag set. 44 SetSensitiveAttributes(attrs ...attribute.KeyValue) 45 46 // EndSpan finishes the span and submits it to the otel endpoint. 47 EndSpan(opts ...trace.SpanEndOption) 48 49 // Debug logs a message at the debug level. 50 Debug(msg string, attrs ...attribute.KeyValue) 51 52 // Debugf formats a message and logs it at the debug level. 53 Debugf(format string, args ...interface{}) 54 55 // Info logs a message at the info level. 56 Info(msg string, attrs ...attribute.KeyValue) 57 58 // Infof formats a message and logs it at the info level. 59 Infof(format string, args ...interface{}) 60 61 // Warn logs a message at the warning level. 62 Warn(msg string, attrs ...attribute.KeyValue) 63 64 // Warnf formats a message and prints it at the warning level. 65 Warnf(format string, args ...interface{}) 66 67 // Error logs a message at the error level, when the specified error is not nil, 68 // and marks the current span as failed. 69 // Example: return log.Error(err) 70 // Only log it in the function that generated the error, not when bubbling 71 // it up the call stack. 72 Error(err error, attrs ...attribute.KeyValue) error 73 74 // Errorf logs a message at the error level and marks the current span as failed. 75 Errorf(format string, arg ...interface{}) error 76 77 // ShouldLog returns if the current log level includes the specified level. 78 ShouldLog(level zapcore.Level) bool 79 80 // IsTracingEnabled returns if the current logger is configured to send trace data. 81 IsTracingEnabled() bool 82 } 83 84 type RootTraceLogger interface { 85 TraceLogger 86 87 // Close the tracer and send data to the telemetry collector 88 Close() error 89 } 90 91 var _ TraceLogger = traceLogger{} 92 93 // traceLogger writes tracing data to an open telemetry collector, 94 // and sends logs to both the tracer and a standard logger. 95 // This results in open telemetry collecting logs and traces, 96 // and also having all logs available in a file and send to the console. 97 type traceLogger struct { 98 ctx context.Context 99 span trace.Span 100 logger *zap.Logger 101 tracer Tracer 102 } 103 104 // Close the root span and send the telemetry data to the collector. 105 func (l traceLogger) Close() error { 106 l.span.End() 107 108 if err := l.tracer.Close(context.Background()); err != nil { 109 return l.Errorf("error closing the Tracer: %w", err) 110 } 111 return nil 112 } 113 114 // ShouldLog returns if the current log level includes the specified level. 115 func (l traceLogger) ShouldLog(level zapcore.Level) bool { 116 return l.logger.Core().Enabled(level) 117 } 118 119 func (l traceLogger) IsTracingEnabled() bool { 120 return !l.tracer.IsNoOp 121 } 122 123 // NewRootLogger creates a new TraceLogger and stores in on the context 124 func NewRootLogger(ctx context.Context, span trace.Span, logger *zap.Logger, tracer Tracer) (context.Context, RootTraceLogger) { 125 childCtx := context.WithValue(ctx, contextKeyTraceLogger, traceLoggerContext{logger, tracer}) 126 return childCtx, newTraceLogger(childCtx, span, logger, tracer) 127 } 128 129 // UpdateRootLogger swaps the logger on the context. 130 func UpdateRootLogger(ctx context.Context, logger *zap.Logger) context.Context { 131 if tl, ok := ctx.Value(contextKeyTraceLogger).(traceLoggerContext); ok { 132 tl.logger = logger 133 return context.WithValue(ctx, contextKeyTraceLogger, tl) 134 } 135 136 return ctx 137 } 138 139 func newTraceLogger(ctx context.Context, span trace.Span, logger *zap.Logger, tracer Tracer) traceLogger { 140 l := traceLogger{ 141 ctx: ctx, 142 span: span, 143 logger: logger, 144 tracer: tracer, 145 } 146 return l 147 } 148 149 // EndSpan finishes the span and submits it to the otel endpoint. 150 func (l traceLogger) EndSpan(opts ...trace.SpanEndOption) { 151 defer l.span.End(opts...) 152 153 // If there was a panic, mark the span and include the stack trace 154 if panicErr := recover(); panicErr != nil { 155 panicErr = l.Error(fmt.Errorf("%s", panicErr), 156 attribute.Bool("panic", true), 157 attribute.String("stackTrace", string(debug.Stack()))) 158 panic(panicErr) // rethrow 159 } 160 } 161 162 // StartSpan retrieves a logger from the current context and starts a new span 163 // named after the current function. 164 func (l traceLogger) StartSpan(attrs ...attribute.KeyValue) (context.Context, TraceLogger) { 165 return l.StartSpanWithName(callerFunc(), attrs...) 166 } 167 168 // StartSpanWithName retrieves a logger from the current context and starts a span with 169 // the specified name. 170 func (l traceLogger) StartSpanWithName(op string, attrs ...attribute.KeyValue) (context.Context, TraceLogger) { 171 childCtx, childSpan := l.tracer.Start(l.ctx, op) 172 childSpan.SetAttributes(attrs...) 173 return childCtx, newTraceLogger(childCtx, childSpan, l.logger, l.tracer) 174 } 175 176 // SetAttributes applies additional key/value pairs to the current trace span. 177 func (l traceLogger) SetAttributes(attrs ...attribute.KeyValue) { 178 l.span.SetAttributes(attrs...) 179 } 180 181 // SetSensitiveAttributes applies attributes that contain SENSITIVE DATA. 182 // It is only enabled on debug builds of Porter with the traceSensitiveAttributes build flag set. 183 func (l traceLogger) SetSensitiveAttributes(attrs ...attribute.KeyValue) { 184 if traceSensitiveAttributes { 185 l.SetAttributes(attrs...) 186 } 187 } 188 189 // Debug logs a message at the debug level. 190 func (l traceLogger) Debug(msg string, attrs ...attribute.KeyValue) { 191 l.logger.Debug(msg, convertAttributesToFields(attrs)...) 192 193 addLogToSpan(l.span, msg, "debug", attrs...) 194 } 195 196 // Debugf formats a message and logs it at the debug level. 197 func (l traceLogger) Debugf(format string, args ...interface{}) { 198 l.Debug(fmt.Sprintf(format, args...)) 199 } 200 201 // Info logs a message at the info level. 202 func (l traceLogger) Info(msg string, attrs ...attribute.KeyValue) { 203 l.logger.Info(msg, convertAttributesToFields(attrs)...) 204 205 addLogToSpan(l.span, msg, "info", attrs...) 206 } 207 208 // Infof formats a message and logs it at the info level. 209 func (l traceLogger) Infof(format string, args ...interface{}) { 210 l.Info(fmt.Sprintf(format, args...)) 211 } 212 213 // Warn logs a message at the warning level. 214 func (l traceLogger) Warn(msg string, attrs ...attribute.KeyValue) { 215 l.logger.Warn(msg, convertAttributesToFields(attrs)...) 216 217 addLogToSpan(l.span, msg, "warn", attrs...) 218 } 219 220 // Warnf formats a message and prints it at the warning level. 221 func (l traceLogger) Warnf(format string, args ...interface{}) { 222 l.Warn(fmt.Sprintf(format, args...)) 223 } 224 225 func (l traceLogger) Errorf(format string, args ...interface{}) error { 226 return l.Error(fmt.Errorf(format, args...)) 227 } 228 229 // Error logs a message at the error level, when the specified error is not nil. 230 func (l traceLogger) Error(err error, attrs ...attribute.KeyValue) error { 231 if err == nil { 232 return err 233 } 234 235 msg := err.Error() 236 l.logger.Error(msg, convertAttributesToFields(attrs)...) 237 238 attrs = append(attrs, attribute.String("level", "error")) 239 240 // Try to include the stack trace 241 // I'm not using trace.WithStackTrace because it records the stack trace from _here_ 242 // and not the one attached to the error... 243 errOpts := []trace.EventOption{ 244 trace.WithAttributes(attrs...), 245 } 246 247 errOpts = append(errOpts, trace.WithAttributes( 248 semconv.ExceptionStacktraceKey.String(fmt.Sprintf("%+v", err)), 249 )) 250 251 l.span.RecordError(err, errOpts...) 252 l.span.SetStatus(codes.Error, err.Error()) 253 254 return err 255 } 256 257 // appends logs to a otel span as events 258 func addLogToSpan(span trace.Span, msg string, level string, attrs ...attribute.KeyValue) { 259 attrs = append(attrs, 260 attribute.String("level", level)) 261 span.AddEvent(msg, trace.WithAttributes(attrs...)) 262 } 263 264 func callerFunc() string { 265 callerUnknown := "unknown" 266 267 // Depending on how this is called, the real function that we are targeting 268 // may be a variable number of frames up the stack 269 // Only look 10 frames up the stack 270 for i := 0; i < 10; i++ { 271 var pc [1]uintptr 272 if runtime.Callers(i+2, pc[:]) != 1 { 273 return callerUnknown 274 } 275 // translate the PC into function information 276 frame, _ := runtime.CallersFrames(pc[:]).Next() 277 if frame.Function == "" { 278 return callerUnknown 279 } 280 281 // Locate the function that first called into the tracing package 282 if strings.HasPrefix(frame.Function, "get.porter.sh/porter/pkg/tracing.") { 283 continue 284 } 285 286 return strings.TrimPrefix(frame.Function, "get.porter.sh/porter/") 287 } 288 289 return callerUnknown 290 }