diff --git a/pkg/slogd/disabledHandler.go b/pkg/slogd/disabledHandler.go new file mode 100644 index 0000000..9e62c8e --- /dev/null +++ b/pkg/slogd/disabledHandler.go @@ -0,0 +1,32 @@ +package slogd + +import ( + "context" + "log/slog" +) + +func newDisabledHandler() slog.Handler { + return &disabledHandler{} +} + +func registerDisabledHandler(activate bool) { + RegisterSink(handlerDisabled, newDisabledHandler(), activate) +} + +type disabledHandler struct{} + +func (h *disabledHandler) Handle(ctx context.Context, r slog.Record) error { + return nil +} + +func (h *disabledHandler) Enabled(ctx context.Context, level slog.Level) bool { + return false +} + +func (h *disabledHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return h +} + +func (h *disabledHandler) WithGroup(group string) slog.Handler { + return h +} diff --git a/pkg/slogd/jsonHandler.go b/pkg/slogd/jsonHandler.go new file mode 100644 index 0000000..0688cc3 --- /dev/null +++ b/pkg/slogd/jsonHandler.go @@ -0,0 +1,10 @@ +package slogd + +import ( + "io" + "log/slog" +) + +func RegisterJSONHandler(w io.Writer, activate bool) { + RegisterSink(HandlerJSON, slog.NewJSONHandler(w, HandlerOptions()), activate) +} diff --git a/pkg/slogd/level.go b/pkg/slogd/level.go new file mode 100644 index 0000000..ac1352e --- /dev/null +++ b/pkg/slogd/level.go @@ -0,0 +1,56 @@ +package slogd + +import ( + "log/slog" + "strings" +) + +const ( + LevelTrace = slog.Level(-8) + LevelDebug = slog.LevelDebug + LevelInfo = slog.LevelInfo + LevelNotice = slog.Level(2) + LevelWarn = slog.LevelWarn + LevelError = slog.LevelError + LevelFatal = slog.Level(12) + LevelDefault = LevelInfo +) + +var levelNames = map[slog.Leveler]string{ + LevelTrace: "TRACE", + LevelDebug: "DEBUG", + LevelInfo: "INFO", + LevelNotice: "NOTICE", + LevelWarn: "WARN", + LevelError: "ERROR", + LevelFatal: "FATAL", +} + +func ReplaceAttrs(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.LevelKey { + a.Value = slog.StringValue(LevelName(a.Value.Any().(slog.Level))) + } + return a +} + +func Level(l string) slog.Level { + mux.Lock() + defer mux.Unlock() + for k, v := range levelNames { + if strings.ToUpper(l) == v { + return k.Level() + } + } + return LevelDefault +} + +func LevelName(l slog.Level) string { + mux.Lock() + defer mux.Unlock() + for k, v := range levelNames { + if k == l { + return v + } + } + return levelNames[LevelDefault] +} diff --git a/pkg/slogd/slogd.go b/pkg/slogd/slogd.go new file mode 100644 index 0000000..27aabea --- /dev/null +++ b/pkg/slogd/slogd.go @@ -0,0 +1,140 @@ +package slogd + +import ( + "context" + "log/slog" + "sync" + + slogformatter "github.com/samber/slog-formatter" + slogmulti "github.com/samber/slog-multi" +) + +const ( + HandlerText string = "text" + HandlerJSON string = "json" + handlerDisabled string = "disabled" +) + +const ( + FlowFanOut Flow = iota + FlowPipeline + FlowRouting + FlowFailOver + FlowLoadBalancing +) + +type Flow int + +var ( + ctxKey = contextKey{} +) +var ( + handlers = make(map[string]slog.Handler) + activeHandler string + level = new(slog.LevelVar) + formatters []slogformatter.Formatter + middlewares []slogmulti.Middleware + source bool + logger *slog.Logger + mux = &sync.Mutex{} +) + +func ActiveHandler() string { + mux.Lock() + defer mux.Unlock() + return activeHandler +} + +func Key() contextKey { + return ctxKey +} + +func Disable() { + UseHandler(handlerDisabled) +} + +func FromContext(ctx context.Context) *slog.Logger { + if l, ok := ctx.Value(Key()).(*slog.Logger); ok { + return l + } + return Logger() +} + +func HandlerOptions() *slog.HandlerOptions { + return &slog.HandlerOptions{ + AddSource: source, + Level: level, + ReplaceAttr: ReplaceAttrs, + } +} + +func Init(l slog.Level, addSource bool) { + level.Set(l) + source = addSource +} + +func Logger() *slog.Logger { + mux.Lock() + defer mux.Unlock() + + if logger == nil { + logger = slog.New(handlers[handlerDisabled]) + } + return logger +} + +func SetLevel(l slog.Level) { + mux.Lock() + defer mux.Unlock() + level.Set(l) +} + +func RegisterFormatter(f slogformatter.Formatter) { + mux.Lock() + defer mux.Unlock() + formatters = append(formatters, f) +} + +func RegisterMiddleware(h slogmulti.Middleware) { + mux.Lock() + defer mux.Unlock() + middlewares = append(middlewares, h) +} + +func RegisterSink(name string, h slog.Handler, activate bool) { + mux.Lock() + handlers[name] = h + mux.Unlock() + + if activate { + UseHandler(name) + } +} + +func UseHandler(name string) { + mux.Lock() + defer mux.Unlock() + if _, ok := handlers[name]; !ok { + Logger().LogAttrs(context.Background(), LevelError, "could not find handler", slog.String("name", name)) + return + } + + formatterPipe := slogformatter.NewFormatterMiddleware(formatters...) + pipe := slogmulti.Pipe(middlewares...).Pipe(formatterPipe) + handler := slogmulti.Fanout(handlers[name]) + + logger = slog.New(pipe.Handler(handler)) + activeHandler = name +} + +func WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, Key(), Logger()) +} + +func init() { + // RegisterFormatter(LevelFormatter()) + // RegisterMiddleware(NewLevelMiddleware()) + registerDisabledHandler(true) +} + +type contextKey struct{} diff --git a/pkg/slogd/textHandler.go b/pkg/slogd/textHandler.go new file mode 100644 index 0000000..0ce0631 --- /dev/null +++ b/pkg/slogd/textHandler.go @@ -0,0 +1,10 @@ +package slogd + +import ( + "io" + "log/slog" +) + +func RegisterTextHandler(w io.Writer, activate bool) { + RegisterSink(HandlerText, slog.NewTextHandler(w, HandlerOptions()), activate) +}