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  }