Add buffering exporter to queue OTLP logs until tracing is configured. Support TLS configuration for OpenTelemetry log export with client certificate authentication. Improve logfmt formatting and tracing setup.
98 lines
2.0 KiB
Go
98 lines
2.0 KiB
Go
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log/slog"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type logfmt struct {
|
|
buf *bytes.Buffer
|
|
txt slog.Handler
|
|
next slog.Handler
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// createTextHandlerOptions creates the common slog.HandlerOptions used by all logfmt handlers
|
|
func createTextHandlerOptions() *slog.HandlerOptions {
|
|
return &slog.HandlerOptions{
|
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
|
if a.Key == slog.TimeKey && len(groups) == 0 {
|
|
return slog.Attr{}
|
|
}
|
|
if a.Key == slog.LevelKey && len(groups) == 0 {
|
|
return slog.Attr{}
|
|
}
|
|
return a
|
|
},
|
|
}
|
|
}
|
|
|
|
func newLogFmtHandler(next slog.Handler) slog.Handler {
|
|
buf := bytes.NewBuffer([]byte{})
|
|
|
|
h := &logfmt{
|
|
buf: buf,
|
|
next: next,
|
|
txt: slog.NewTextHandler(buf, createTextHandlerOptions()),
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
func (h *logfmt) Enabled(ctx context.Context, lvl slog.Level) bool {
|
|
return h.next.Enabled(ctx, lvl)
|
|
}
|
|
|
|
func (h *logfmt) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
buf := bytes.NewBuffer([]byte{})
|
|
return &logfmt{
|
|
buf: buf,
|
|
next: h.next.WithAttrs(slices.Clone(attrs)),
|
|
txt: slog.NewTextHandler(buf, createTextHandlerOptions()).WithAttrs(slices.Clone(attrs)),
|
|
}
|
|
}
|
|
|
|
func (h *logfmt) WithGroup(g string) slog.Handler {
|
|
if g == "" {
|
|
return h
|
|
}
|
|
buf := bytes.NewBuffer([]byte{})
|
|
return &logfmt{
|
|
buf: buf,
|
|
next: h.next.WithGroup(g),
|
|
txt: slog.NewTextHandler(buf, createTextHandlerOptions()).WithGroup(g),
|
|
}
|
|
}
|
|
|
|
func (h *logfmt) Handle(ctx context.Context, r slog.Record) error {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
if h.buf.Len() > 0 {
|
|
panic("buffer wasn't empty")
|
|
}
|
|
|
|
// Format using text handler to get the formatted message
|
|
err := h.txt.Handle(ctx, r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
formattedMessage := h.buf.String()
|
|
formattedMessage = strings.TrimSuffix(formattedMessage, "\n")
|
|
h.buf.Reset()
|
|
|
|
// Create a new record with the formatted message
|
|
newRecord := slog.NewRecord(r.Time, r.Level, formattedMessage, r.PC)
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
newRecord.AddAttrs(a)
|
|
return true
|
|
})
|
|
|
|
return h.next.Handle(ctx, newRecord)
|
|
}
|