Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379package log
import ( "context" "errors" "fmt" "io" "log/slog" "os"
"github.com/charmbracelet/log" "go.opentelemetry.io/contrib/bridges/otelslog" otellog "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/trace")
type handlerOperation struct { attrs []slog.Attr group string}
type namedHandler struct { slog.Handler name string format string writer io.Writer hasTraceID bool hasSpanID bool operations []handlerOperation}
func (h *namedHandler) WithAttrs(attrs []slog.Attr) slog.Handler { hasTraceID := h.hasTraceID hasSpanID := h.hasSpanID for _, attr := range attrs { switch attr.Key { case "trace_id": hasTraceID = true case "span_id": hasSpanID = true } } operations := append(h.operations[:len(h.operations):len(h.operations)], handlerOperation{ attrs: append([]slog.Attr(nil), attrs...), }) return &namedHandler{ Handler: h.Handler.WithAttrs(attrs), name: h.name, format: h.format, writer: h.writer, hasTraceID: hasTraceID, hasSpanID: hasSpanID, operations: operations, }}
func (h *namedHandler) WithGroup(name string) slog.Handler { operations := append(h.operations[:len(h.operations):len(h.operations)], handlerOperation{group: name}) return &namedHandler{ Handler: h.Handler.WithGroup(name), name: h.name, format: h.format, writer: h.writer, hasTraceID: h.hasTraceID, hasSpanID: h.hasSpanID, operations: operations, }}
func (h *namedHandler) WithName(name string) slog.Handler { next, err := newHandler(h.writer, name, h.format) if err != nil { return h } for _, operation := range h.operations { if operation.group != "" { next = next.WithGroup(operation.group) } else { next = next.WithAttrs(operation.attrs) } } return next}
func (h *namedHandler) Handle(ctx context.Context, r slog.Record) error { spanContext := trace.SpanContextFromContext(ctx) if spanContext.IsValid() && spanContext.IsSampled() { hasTraceID := h.hasTraceID hasSpanID := h.hasSpanID if !hasTraceID || !hasSpanID { r.Attrs(func(a slog.Attr) bool { if a.Key == "trace_id" { hasTraceID = true } if a.Key == "span_id" { hasSpanID = true } return !hasTraceID || !hasSpanID }) } var attrs []slog.Attr if !hasTraceID { attrs = append(attrs, slog.String("trace_id", spanContext.TraceID().String())) } if !hasSpanID { attrs = append(attrs, slog.String("span_id", spanContext.SpanID().String())) } if len(attrs) > 0 { r.AddAttrs(attrs...) } } return h.Handler.Handle(ctx, r)}
type fanoutHandler struct { primary slog.Handler others []slog.Handler}
func (h *fanoutHandler) Enabled(ctx context.Context, level slog.Level) bool { if h.primary.Enabled(ctx, level) { return true } for _, other := range h.others { if other.Enabled(ctx, level) { return true } } return false}
func (h *fanoutHandler) Handle(ctx context.Context, r slog.Record) error { var errs []error if h.primary.Enabled(ctx, r.Level) { if err := h.primary.Handle(ctx, r); err != nil { errs = append(errs, err) } } for _, other := range h.others { if other.Enabled(ctx, r.Level) { if err := other.Handle(ctx, r); err != nil { errs = append(errs, err) } } } if len(errs) > 0 { return errors.Join(errs...) } return nil}
func (h *fanoutHandler) WithAttrs(attrs []slog.Attr) slog.Handler { others := make([]slog.Handler, len(h.others)) for i, other := range h.others { others[i] = other.WithAttrs(attrs) } return &fanoutHandler{ primary: h.primary.WithAttrs(attrs), others: others, }}
func (h *fanoutHandler) WithGroup(name string) slog.Handler { others := make([]slog.Handler, len(h.others)) for i, other := range h.others { others[i] = other.WithGroup(name) } return &fanoutHandler{ primary: h.primary.WithGroup(name), others: others, }}
func (h *fanoutHandler) WithName(name string) slog.Handler { newPrimary := h.primary if nh, ok := h.primary.(interface{ WithName(string) slog.Handler }); ok { newPrimary = nh.WithName(name) } others := make([]slog.Handler, len(h.others)) for i, other := range h.others { if nh, ok := other.(interface{ WithName(string) slog.Handler }); ok { others[i] = nh.WithName(name) } else { others[i] = other } } return &fanoutHandler{ primary: newPrimary, others: others, }}
func NewFanoutHandler(primary slog.Handler, others ...slog.Handler) slog.Handler { var validOthers []slog.Handler for _, other := range others { if other != nil { validOthers = append(validOthers, other) } } if len(validOthers) == 0 { return primary } return &fanoutHandler{ primary: primary, others: validOthers, }}
type otelHandler struct { slog.Handler provider otellog.LoggerProvider name string operations []handlerOperation}
func (h *otelHandler) WithAttrs(attrs []slog.Attr) slog.Handler { operations := append(h.operations[:len(h.operations):len(h.operations)], handlerOperation{ attrs: append([]slog.Attr(nil), attrs...), }) return &otelHandler{ Handler: h.Handler.WithAttrs(attrs), provider: h.provider, name: h.name, operations: operations, }}
func (h *otelHandler) WithGroup(name string) slog.Handler { operations := append(h.operations[:len(h.operations):len(h.operations)], handlerOperation{group: name}) return &otelHandler{ Handler: h.Handler.WithGroup(name), provider: h.provider, name: h.name, operations: operations, }}
func (h *otelHandler) WithName(newName string) slog.Handler { var opts []otelslog.Option if h.provider != nil { opts = append(opts, otelslog.WithLoggerProvider(h.provider)) } var handler slog.Handler = otelslog.NewHandler(newName, opts...) for _, operation := range h.operations { if operation.group != "" { handler = handler.WithGroup(operation.group) } else { handler = handler.WithAttrs(operation.attrs) } } return &otelHandler{ Handler: handler, provider: h.provider, name: newName, operations: h.operations, }}
func NewOtelHandler(provider otellog.LoggerProvider, name string) slog.Handler { var opts []otelslog.Option if provider != nil { opts = append(opts, otelslog.WithLoggerProvider(provider)) } h := otelslog.NewHandler(name, opts...) return &otelHandler{ Handler: h, provider: provider, name: name, }}
func newHandler(w io.Writer, name, format string) (slog.Handler, error) { var handler slog.Handler switch format { case "", "text": handler = log.NewWithOptions(w, log.Options{ ReportTimestamp: true, Prefix: name, Level: log.DebugLevel, }) format = "text" case "json": handler = slog.NewJSONHandler(w, &slog.HandlerOptions{ Level: slog.LevelDebug, }).WithAttrs([]slog.Attr{slog.String("logger", name)}) default: return nil, fmt.Errorf("unknown log format %q", format) } return &namedHandler{ Handler: handler, name: name, format: format, writer: w, }, nil}
func NewHandler(name string) slog.Handler { handler, _ := newHandler(os.Stderr, name, "text") return handler}
func New(name string) *slog.Logger { return slog.New(NewHandler(name))}
func NewWithFormat(name, format string) (*slog.Logger, error) { return newWithFormatWriter(os.Stderr, name, format)}
func newWithFormatWriter(w io.Writer, name, format string) (*slog.Logger, error) { handler, err := newHandler(w, name, format) if err != nil { return nil, err } return slog.New(handler), nil}
func NewContext(ctx context.Context, name string) context.Context { return IntoContext(ctx, New(name))}
type ctxKey struct{}
// IntoContext adds a logger to a context. Use FromContext to// pull the logger out.func IntoContext(ctx context.Context, l *slog.Logger) context.Context { return context.WithValue(ctx, ctxKey{}, l)}
// FromContext returns a logger from a context.Context;// if the passed context is nil, we return the default slog// logger.func FromContext(ctx context.Context) *slog.Logger { if ctx != nil { v := ctx.Value(ctxKey{}) if v == nil { return slog.Default() } return v.(*slog.Logger) }
return slog.Default()}
// sublogger derives a new logger from an existing one by appending a suffix to its prefix.func SubLogger(base *slog.Logger, suffix string) *slog.Logger { if base == nil { return New(suffix) } if handler, ok := base.Handler().(*namedHandler); ok { name := suffix if handler.name != "" { name = handler.name + "/" + suffix } return slog.New(handler.WithName(name)) } if fh, ok := base.Handler().(*fanoutHandler); ok { var newName string if ph, ok := fh.primary.(*namedHandler); ok { if ph.name != "" { newName = ph.name + "/" + suffix } else { newName = suffix } } else { newName = suffix } return slog.New(fh.WithName(newName)) } if handler, ok := base.Handler().(*log.Logger); ok { name := suffix if handler.GetPrefix() != "" { name = handler.GetPrefix() + "/" + suffix } return New(name) } return base.With("component", suffix)}