feat(logger): add runtime log level control API
Add independent log level control for stderr and OTLP loggers. Both can be configured via environment variables or programmatically at runtime. - Add SetLevel() and SetOTLPLevel() for runtime control - Add ParseLevel() to convert strings to slog.Level - Support LOG_LEVEL and OTLP_LOG_LEVEL env vars - Maintain backward compatibility with DEBUG env var - Add comprehensive test coverage
This commit is contained in:
@@ -18,12 +18,15 @@
|
||||
// - Context propagation for request-scoped logging
|
||||
//
|
||||
// Environment variables:
|
||||
// - DEBUG: Enable debug level logging (configurable prefix via ConfigPrefix)
|
||||
// - LOG_LEVEL: Set stderr log level (DEBUG, INFO, WARN, ERROR) (configurable prefix via ConfigPrefix)
|
||||
// - OTLP_LOG_LEVEL: Set OTLP log level independently (configurable prefix via ConfigPrefix)
|
||||
// - DEBUG: Enable debug level logging for backward compatibility (configurable prefix via ConfigPrefix)
|
||||
// - INVOCATION_ID: Systemd detection for timestamp handling
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
@@ -43,6 +46,16 @@ import (
|
||||
// This enables multiple services to have independent logging configuration.
|
||||
var ConfigPrefix = ""
|
||||
|
||||
var (
|
||||
// Level controls the log level for the default stderr logger.
|
||||
// Can be changed at runtime to adjust logging verbosity.
|
||||
Level = new(slog.LevelVar) // Info by default
|
||||
|
||||
// OTLPLevel controls the log level for OTLP output.
|
||||
// Can be changed independently from the stderr logger level.
|
||||
OTLPLevel = new(slog.LevelVar) // Info by default
|
||||
)
|
||||
|
||||
var (
|
||||
textLogger *slog.Logger
|
||||
otlpLogger *slog.Logger
|
||||
@@ -56,21 +69,64 @@ var (
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
func setupStdErrHandler() slog.Handler {
|
||||
programLevel := new(slog.LevelVar) // Info by default
|
||||
// SetLevel sets the log level for the default stderr logger.
|
||||
// This affects the primary application logger returned by Setup().
|
||||
func SetLevel(level slog.Level) {
|
||||
Level.Set(level)
|
||||
}
|
||||
|
||||
envVar := "DEBUG"
|
||||
// SetOTLPLevel sets the log level for OTLP output.
|
||||
// This affects the logger returned by SetupOLTP() and the OTLP portion of SetupMultiLogger().
|
||||
func SetOTLPLevel(level slog.Level) {
|
||||
OTLPLevel.Set(level)
|
||||
}
|
||||
|
||||
// ParseLevel converts a string log level to slog.Level.
|
||||
// Supported levels: "DEBUG", "INFO", "WARN", "ERROR" (case insensitive).
|
||||
// Returns an error for unrecognized level strings.
|
||||
func ParseLevel(level string) (slog.Level, error) {
|
||||
switch {
|
||||
case level == "":
|
||||
return slog.LevelInfo, nil
|
||||
case level == "DEBUG" || level == "debug":
|
||||
return slog.LevelDebug, nil
|
||||
case level == "INFO" || level == "info":
|
||||
return slog.LevelInfo, nil
|
||||
case level == "WARN" || level == "warn":
|
||||
return slog.LevelWarn, nil
|
||||
case level == "ERROR" || level == "error":
|
||||
return slog.LevelError, nil
|
||||
default:
|
||||
return slog.LevelInfo, fmt.Errorf("unknown log level: %s", level)
|
||||
}
|
||||
}
|
||||
|
||||
func setupStdErrHandler() slog.Handler {
|
||||
// Parse LOG_LEVEL environment variable
|
||||
logLevelVar := "LOG_LEVEL"
|
||||
if len(ConfigPrefix) > 0 {
|
||||
envVar = ConfigPrefix + "_" + envVar
|
||||
logLevelVar = ConfigPrefix + "_" + logLevelVar
|
||||
}
|
||||
|
||||
if opt := os.Getenv(envVar); len(opt) > 0 {
|
||||
if debug, _ := strconv.ParseBool(opt); debug {
|
||||
programLevel.Set(slog.LevelDebug)
|
||||
if levelStr := os.Getenv(logLevelVar); levelStr != "" {
|
||||
if level, err := ParseLevel(levelStr); err == nil {
|
||||
Level.Set(level)
|
||||
}
|
||||
}
|
||||
|
||||
logOptions := &slog.HandlerOptions{Level: programLevel}
|
||||
// Maintain backward compatibility with DEBUG environment variable
|
||||
debugVar := "DEBUG"
|
||||
if len(ConfigPrefix) > 0 {
|
||||
debugVar = ConfigPrefix + "_" + debugVar
|
||||
}
|
||||
|
||||
if opt := os.Getenv(debugVar); len(opt) > 0 {
|
||||
if debug, _ := strconv.ParseBool(opt); debug {
|
||||
Level.Set(slog.LevelDebug)
|
||||
}
|
||||
}
|
||||
|
||||
logOptions := &slog.HandlerOptions{Level: Level}
|
||||
|
||||
if len(os.Getenv("INVOCATION_ID")) > 0 {
|
||||
// don't add timestamps when running under systemd
|
||||
@@ -88,6 +144,18 @@ func setupStdErrHandler() slog.Handler {
|
||||
|
||||
func setupOtlpLogger() *slog.Logger {
|
||||
setupOtlp.Do(func() {
|
||||
// Parse OTLP_LOG_LEVEL environment variable
|
||||
otlpLevelVar := "OTLP_LOG_LEVEL"
|
||||
if len(ConfigPrefix) > 0 {
|
||||
otlpLevelVar = ConfigPrefix + "_" + otlpLevelVar
|
||||
}
|
||||
|
||||
if levelStr := os.Getenv(otlpLevelVar); levelStr != "" {
|
||||
if level, err := ParseLevel(levelStr); err == nil {
|
||||
OTLPLevel.Set(level)
|
||||
}
|
||||
}
|
||||
|
||||
// Create our buffering exporter
|
||||
// It will buffer until tracing is configured
|
||||
bufferingExp := newBufferingExporter()
|
||||
@@ -107,8 +175,9 @@ func setupOtlpLogger() *slog.Logger {
|
||||
// Set global provider
|
||||
global.SetLoggerProvider(provider)
|
||||
|
||||
// Create slog handler
|
||||
handler := newLogFmtHandler(otelslog.NewHandler("common"))
|
||||
// Create slog handler with level control
|
||||
baseHandler := newLogFmtHandler(otelslog.NewHandler("common"))
|
||||
handler := newOTLPLevelHandler(baseHandler)
|
||||
otlpLogger = slog.New(handler)
|
||||
})
|
||||
return otlpLogger
|
||||
|
Reference in New Issue
Block a user