mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-12-10 07:05:20 -05:00
112 lines
2.7 KiB
Go
112 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
|
|
logotelbridge "go.opentelemetry.io/contrib/bridges/otelslog"
|
|
logotelglobal "go.opentelemetry.io/otel/log/global"
|
|
)
|
|
|
|
type SlogAdapter struct {
|
|
*slog.Logger
|
|
}
|
|
|
|
// add Write so SlogAdapter satisfies io.Writer (Echo's logger output)
|
|
func (a *SlogAdapter) Write(p []byte) (int, error) {
|
|
msg := strings.TrimSpace(string(p))
|
|
// Echo middleware writes request lines at INFO; use Info here.
|
|
a.Info(msg)
|
|
return len(p), nil
|
|
}
|
|
|
|
type tee struct {
|
|
a, b slog.Handler
|
|
minA slog.Level
|
|
minB slog.Level
|
|
}
|
|
|
|
// a = stdout
|
|
// b = otel
|
|
// minA = minimum level for stdout
|
|
// minB = minimum level for otel (from OTEL_LOG_LEVEL)
|
|
func NewTee(a, b slog.Handler, minA, minB slog.Level) slog.Handler {
|
|
return &tee{a: a, b: b, minA: minA, minB: minB}
|
|
}
|
|
|
|
func (t *tee) Enabled(ctx context.Context, level slog.Level) bool {
|
|
if (t.minA == 0 || level >= t.minA) && t.a.Enabled(ctx, level) {
|
|
return true
|
|
}
|
|
if (t.minB == 0 || level >= t.minB) && t.b.Enabled(ctx, level) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (t *tee) Handle(ctx context.Context, r slog.Record) error {
|
|
if t.minA == 0 || r.Level >= t.minA {
|
|
if err := t.a.Handle(ctx, r); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if t.minB == 0 || r.Level >= t.minB {
|
|
return t.b.Handle(ctx, r)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tee) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
return NewTee(t.a.WithAttrs(attrs), t.b.WithAttrs(attrs), t.minA, t.minB)
|
|
}
|
|
|
|
func (t *tee) WithGroup(name string) slog.Handler {
|
|
return NewTee(t.a.WithGroup(name), t.b.WithGroup(name), t.minA, t.minB)
|
|
}
|
|
|
|
func SetupLogger(ctx context.Context) (*SlogAdapter, func(context.Context) error, error) {
|
|
stdout := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
|
// drop the default time attribute so text output has no timestamp
|
|
if a.Key == "time" {
|
|
return slog.Attr{}
|
|
}
|
|
return a
|
|
},
|
|
})
|
|
otelHandler := logotelbridge.NewHandler("slog", logotelbridge.WithLoggerProvider(logotelglobal.GetLoggerProvider()))
|
|
|
|
minStdout := parseLogLevel(os.Getenv("STDOUT_LOG_LEVEL"))
|
|
minOtel := parseLogLevel(os.Getenv("OTEL_LOG_LEVEL"))
|
|
|
|
handler := NewTee(stdout, otelHandler, minStdout, minOtel)
|
|
|
|
logger := slog.New(handler)
|
|
adapter := &SlogAdapter{logger}
|
|
shutdown := func(ctx context.Context) error { return nil }
|
|
|
|
slog.SetDefault(adapter.Logger)
|
|
return adapter, shutdown, nil
|
|
}
|
|
|
|
func parseLogLevel(v string) slog.Level {
|
|
v = strings.ToUpper(strings.TrimSpace(v))
|
|
if v == "" {
|
|
return slog.LevelInfo
|
|
}
|
|
m := map[string]slog.Level{
|
|
"TRACE": slog.LevelDebug,
|
|
"DEBUG": slog.LevelDebug,
|
|
"INFO": slog.LevelInfo,
|
|
"WARN": slog.LevelWarn,
|
|
"WARNING": slog.LevelWarn,
|
|
"ERROR": slog.LevelError,
|
|
}
|
|
if lv, ok := m[v]; ok {
|
|
return lv
|
|
}
|
|
return slog.LevelInfo
|
|
}
|