mirror of https://github.com/coder/coder.git
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
package tracing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"cdr.dev/slog"
|
|
)
|
|
|
|
type SlogSink struct{}
|
|
|
|
var _ slog.Sink = SlogSink{}
|
|
|
|
// LogEntry implements slog.Sink. All entries are added as events to the span
|
|
// in the context. If no span is present, the entry is dropped.
|
|
func (SlogSink) LogEntry(ctx context.Context, e slog.SinkEntry) {
|
|
span := trace.SpanFromContext(ctx)
|
|
if !span.IsRecording() {
|
|
// If the span is a noopSpan or isn't recording, we don't want to
|
|
// compute the attributes (which is expensive) below.
|
|
return
|
|
}
|
|
|
|
attributes := []attribute.KeyValue{
|
|
attribute.String("slog.time", e.Time.Format(time.RFC3339Nano)),
|
|
attribute.String("slog.logger", strings.Join(e.LoggerNames, ".")),
|
|
attribute.String("slog.level", e.Level.String()),
|
|
attribute.String("slog.message", e.Message),
|
|
attribute.String("slog.func", e.Func),
|
|
attribute.String("slog.file", e.File),
|
|
attribute.Int64("slog.line", int64(e.Line)),
|
|
}
|
|
attributes = append(attributes, slogFieldsToAttributes(e.Fields)...)
|
|
|
|
name := fmt.Sprintf("log: %s: %s", e.Level, e.Message)
|
|
span.AddEvent(name, trace.WithAttributes(attributes...))
|
|
}
|
|
|
|
// Sync implements slog.Sink. No-op as syncing is handled externally by otel.
|
|
func (SlogSink) Sync() {}
|
|
|
|
func slogFieldsToAttributes(m slog.Map) []attribute.KeyValue {
|
|
attrs := make([]attribute.KeyValue, 0, len(m))
|
|
for _, f := range m {
|
|
var value attribute.Value
|
|
switch v := f.Value.(type) {
|
|
case bool:
|
|
value = attribute.BoolValue(v)
|
|
case []bool:
|
|
value = attribute.BoolSliceValue(v)
|
|
case float32:
|
|
value = attribute.Float64Value(float64(v))
|
|
// no float32 slice method
|
|
case float64:
|
|
value = attribute.Float64Value(v)
|
|
case []float64:
|
|
value = attribute.Float64SliceValue(v)
|
|
case int:
|
|
value = attribute.Int64Value(int64(v))
|
|
case []int:
|
|
value = attribute.IntSliceValue(v)
|
|
case int8:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no int8 slice method
|
|
case int16:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no int16 slice method
|
|
case int32:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no int32 slice method
|
|
case int64:
|
|
value = attribute.Int64Value(v)
|
|
case []int64:
|
|
value = attribute.Int64SliceValue(v)
|
|
case uint:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no uint slice method
|
|
case uint8:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no uint8 slice method
|
|
case uint16:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no uint16 slice method
|
|
case uint32:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no uint32 slice method
|
|
case uint64:
|
|
value = attribute.Int64Value(int64(v))
|
|
// no uint64 slice method
|
|
case string:
|
|
value = attribute.StringValue(v)
|
|
case []string:
|
|
value = attribute.StringSliceValue(v)
|
|
case time.Duration:
|
|
value = attribute.StringValue(v.String())
|
|
case time.Time:
|
|
value = attribute.StringValue(v.Format(time.RFC3339Nano))
|
|
case fmt.Stringer:
|
|
value = attribute.StringValue(v.String())
|
|
}
|
|
|
|
if value.Type() != attribute.INVALID {
|
|
attrs = append(attrs, attribute.KeyValue{
|
|
Key: attribute.Key(f.Name),
|
|
Value: value,
|
|
})
|
|
}
|
|
}
|
|
|
|
return attrs
|
|
}
|