From c97d6ccfdd782cc9b5ca05f8a3d45f48c407158f Mon Sep 17 00:00:00 2001 From: Malte Poll <1780588+malt3@users.noreply.github.com> Date: Mon, 8 Jan 2024 18:12:48 +0100 Subject: [PATCH] logger: add default logger and subsystem log handler Adds ability to control logging via env vars. --- internal/logger/logger.go | 56 ++++++++++++++++ internal/logger/subsystemlog/handler.go | 87 +++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 internal/logger/logger.go create mode 100644 internal/logger/subsystemlog/handler.go diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000000..080868760f --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,56 @@ +// Package logger provides a slog.Logger that can be configured via environment variables. +// NUNKI_LOG_LEVEL can be used to set the log level. +// NUNKI_LOG_FORMAT can be used to set the log format. +package logger + +import ( + "fmt" + "io" + "log/slog" + "os" + "strconv" + "strings" +) + +// Default returns a logger configured via environment variables. +func Default() (*slog.Logger, error) { + logLevel, err := getLogLevel() + if err != nil { + return nil, err + } + return slog.New(logHandler()(os.Stderr, &slog.HandlerOptions{ + Level: logLevel, + })), nil +} + +func getLogLevel() (slog.Level, error) { + logLevel := os.Getenv("NUNKI_LOG_LEVEL") + switch strings.ToLower(logLevel) { + case "debug": + return slog.LevelDebug, nil + case "", "info": + return slog.LevelInfo, nil + case "warn": + return slog.LevelWarn, nil + case "error": + return slog.LevelError, nil + } + + numericLevel, err := strconv.Atoi(logLevel) + if err != nil { + return slog.Level(0), fmt.Errorf("invalid log level: %q", logLevel) + } + return slog.Level(numericLevel), nil +} + +func logHandler() func(w io.Writer, opts *slog.HandlerOptions) slog.Handler { + switch strings.ToLower(os.Getenv("NUNKI_LOG_FORMAT")) { + case "json": + return func(w io.Writer, opts *slog.HandlerOptions) slog.Handler { + return slog.NewJSONHandler(w, opts) + } + } + return func(w io.Writer, opts *slog.HandlerOptions) slog.Handler { + return slog.NewTextHandler(w, opts) + } +} diff --git a/internal/logger/subsystemlog/handler.go b/internal/logger/subsystemlog/handler.go new file mode 100644 index 0000000000..2ca28c5ec5 --- /dev/null +++ b/internal/logger/subsystemlog/handler.go @@ -0,0 +1,87 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +// Package subsystemlog provides a slog.Handler that can be used to enable +// logging on a per-subsystem basis. +// +// NUNKI_LOG_SUBSYSTEMS can be used to enable logging for specific subsystems. +// If NUNKI_LOG_SUBSYSTEMS has the special value "*", all subsystems are enabled. +// Otherwise, a comma-separated list of subsystem names can be specified. +package subsystemlog + +import ( + "context" + "log/slog" + "os" + "strings" +) + +// Handler is a slog.Handler that can be used to enable logging on a per-subsystem basis. +type Handler struct { + inner slog.Handler + subsystem string + enabled bool +} + +// NewHandler returns a new Handler. +func NewHandler(inner slog.Handler, subsystem string) (*Handler, error) { + if subsystem != "" { + inner = inner.WithGroup(subsystem) + } + + return &Handler{ + inner: inner, + subsystem: subsystem, + enabled: subsystemEnvEnabled(subsystem), + }, nil +} + +// Enabled returns true if the given level is enabled. +func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool { + return h.enabled && h.inner.Enabled(ctx, level) +} + +// Handle handles the given record. +func (h *Handler) Handle(ctx context.Context, record slog.Record) error { + return h.inner.Handle(ctx, record) +} + +// WithAttrs returns a new Handler with the given attributes. +func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &Handler{ + inner: h.inner.WithAttrs(attrs), + subsystem: h.subsystem, + enabled: h.enabled, + } +} + +// WithGroup returns a new Handler with the given group. +func (h *Handler) WithGroup(name string) slog.Handler { + return &Handler{ + inner: h.inner.WithGroup(name), + subsystem: h.subsystem, + enabled: h.enabled, + } +} + +func subsystemEnvEnabled(subsystem string) bool { + allowList := os.Getenv("NUNKI_LOG_SUBSYSTEMS") + + return subsystemAllowListMatch(subsystem, allowList) +} + +func subsystemAllowListMatch(subsystem string, allowList string) bool { + if allowList == "*" { + return true + } + for _, allow := range strings.Split(allowList, ",") { + allow = strings.ToLower(strings.TrimSpace(allow)) + if allow == subsystem { + return true + } + } + return false +}