From 6d8e5445782e82c39cf7c981991995a836c10d27 Mon Sep 17 00:00:00 2001
From: Shilpa Chaturvedi
Date: Thu, 17 Jul 2025 16:19:24 +0530
Subject: [PATCH 1/4] Add JSON logging support using Go slog lib
- Add structured JSON logging for all log levels
- Update documentation with usage examples
- Require Go 1.21+ for slog library support
Users can now choose between text and JSON logs
while maintaining the same API and functionality.
---
README.md | 64 ++++++++
examples/example_json_logger.go | 41 +++++
json_logger.go | 273 ++++++++++++++++++++++++++++++++
json_logger_test.go | 204 ++++++++++++++++++++++++
4 files changed, 582 insertions(+)
create mode 100644 examples/example_json_logger.go
create mode 100644 json_logger.go
create mode 100644 json_logger_test.go
diff --git a/README.md b/README.md
index 20ae889..e03558b 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,70 @@
+## Features
+
+- **Text Logging**: Traditional text-based logging with customizable prefixes and levels
+- **JSON Logging**: Structured JSON logging using Go's `slog` library (Go 1.21+)
+- **Level-based Filtering**: Support for TRACE, DEBUG, INFO, WARN, ERROR, and DISABLED levels
+- **Scope-based Configuration**: Different log levels for different scopes/components
+- **Environment Variable Configuration**: Configure log levels via environment variables
+- **Thread-safe**: All logging operations are thread-safe
+
+## Usage
+
+### Text Logging (Default)
+
+```go
+import "github.com/pion/logging"
+
+// Create a logger factory
+factory := logging.NewDefaultLoggerFactory()
+
+// Create loggers for different scopes
+apiLogger := factory.NewLogger("api")
+dbLogger := factory.NewLogger("database")
+
+// Log messages
+apiLogger.Info("API server started")
+apiLogger.Debug("Processing request")
+dbLogger.Error("Database connection failed")
+```
+
+### JSON Logging
+
+```go
+import "github.com/pion/logging"
+
+// Create a JSON logger factory
+factory := logging.NewJSONLoggerFactory()
+
+// Create loggers for different scopes
+apiLogger := factory.NewLogger("api")
+dbLogger := factory.NewLogger("database")
+
+// Log messages with structured data
+apiLogger.Info("API server started")
+apiLogger.Debug("Processing request", "method", "GET", "path", "/users")
+dbLogger.Error("Database connection failed", "error", "connection timeout")
+```
+
+### Environment Variable Configuration
+
+Set environment variables to configure log levels:
+
+```bash
+# Enable all log levels
+export PION_LOG_TRACE=all
+export PION_LOG_DEBUG=all
+export PION_LOG_INFO=all
+export PION_LOG_WARN=all
+export PION_LOG_ERROR=all
+
+# Enable specific scopes
+export PION_LOG_DEBUG=api,database
+export PION_LOG_INFO=feature1,feature2
+```
+
### Roadmap
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
diff --git a/examples/example_json_logger.go b/examples/example_json_logger.go
new file mode 100644
index 0000000..29c5d43
--- /dev/null
+++ b/examples/example_json_logger.go
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2023 The Pion community
+// SPDX-License-Identifier: MIT
+
+package main
+
+import (
+ "os"
+
+ "github.com/pion/logging"
+)
+
+func main() {
+ // Create a JSON logger factory
+ factory := logging.NewJSONLoggerFactory()
+ factory.Writer = os.Stdout // Output to stdout for this example
+
+ // Create loggers for different scopes
+ apiLogger := factory.NewLogger("api")
+ dbLogger := factory.NewLogger("database")
+ authLogger := factory.NewLogger("auth")
+
+ // Log some messages
+ apiLogger.Info("API server started")
+ apiLogger.Debug("Processing request", "method", "GET", "path", "/users")
+ apiLogger.Warn("Rate limit approaching", "requests", 95, "limit", 100)
+
+ dbLogger.Info("Database connection established")
+ dbLogger.Debug("Executing query", "query", "SELECT * FROM users", "duration_ms", 15)
+
+ authLogger.Error("Authentication failed", "user_id", "12345", "reason", "invalid_token")
+ authLogger.Info("User logged in", "user_id", "67890", "ip", "192.168.1.100")
+
+ // Example output will be JSON formatted like:
+ // {"time":"2023-12-07T10:30:00Z","level":"INFO","msg":"API server started","scope":"api"}
+ // {"time":"2023-12-07T10:30:00Z","level":"DEBUG","msg":"Processing request","scope":"api","method":"GET","path":"/users"}
+ // {"time":"2023-12-07T10:30:00Z","level":"WARN","msg":"Rate limit approaching","scope":"api","requests":95,"limit":100}
+ // {"time":"2023-12-07T10:30:00Z","level":"INFO","msg":"Database connection established","scope":"database"}
+ // {"time":"2023-12-07T10:30:00Z","level":"DEBUG","msg":"Executing query","scope":"database","query":"SELECT * FROM users","duration_ms":15}
+ // {"time":"2023-12-07T10:30:00Z","level":"ERROR","msg":"Authentication failed","scope":"auth","user_id":"12345","reason":"invalid_token"}
+ // {"time":"2023-12-07T10:30:00Z","level":"INFO","msg":"User logged in","scope":"auth","user_id":"67890","ip":"192.168.1.100"}
+}
\ No newline at end of file
diff --git a/json_logger.go b/json_logger.go
new file mode 100644
index 0000000..9192173
--- /dev/null
+++ b/json_logger.go
@@ -0,0 +1,273 @@
+// SPDX-FileCopyrightText: 2023 The Pion community
+// SPDX-License-Identifier: MIT
+
+package logging
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "log/slog"
+ "os"
+ "strings"
+ "time"
+)
+
+// JSONLeveledLogger provides JSON structured logging using Go's slog library
+type JSONLeveledLogger struct {
+ level LogLevel
+ writer *loggerWriter
+ logger *slog.Logger
+ scope string
+}
+
+// NewJSONLeveledLoggerForScope returns a configured JSON LeveledLogger
+func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *JSONLeveledLogger {
+ if writer == nil {
+ writer = os.Stderr
+ }
+
+ // Create a JSON handler with custom options
+ handler := slog.NewJSONHandler(writer, &slog.HandlerOptions{
+ Level: slog.Level(-8), // Allow all levels, filter ourselves
+ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
+ // Customize timestamp format
+ if a.Key == slog.TimeKey {
+ return slog.Attr{
+ Key: slog.TimeKey,
+ Value: slog.StringValue(a.Value.Time().Format(time.RFC3339)),
+ }
+ }
+ return a
+ },
+ })
+
+ logger := slog.New(handler)
+
+ return &JSONLeveledLogger{
+ level: level,
+ writer: &loggerWriter{output: writer},
+ logger: logger,
+ scope: scope,
+ }
+}
+
+// WithOutput is a chainable configuration function which sets the logger's
+// logging output to the supplied io.Writer.
+func (jl *JSONLeveledLogger) WithOutput(output io.Writer) *JSONLeveledLogger {
+ jl.writer.SetOutput(output)
+ // Recreate the logger with the new writer
+ handler := slog.NewJSONHandler(output, &slog.HandlerOptions{
+ Level: slog.Level(-8),
+ ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
+ if a.Key == slog.TimeKey {
+ return slog.Attr{
+ Key: slog.TimeKey,
+ Value: slog.StringValue(a.Value.Time().Format(time.RFC3339)),
+ }
+ }
+ return a
+ },
+ })
+ jl.logger = slog.New(handler)
+ return jl
+}
+
+// SetLevel sets the logger's logging level.
+func (jl *JSONLeveledLogger) SetLevel(newLevel LogLevel) {
+ jl.level.Set(newLevel)
+}
+
+// logf is the internal logging function that handles level checking and formatting
+func (jl *JSONLeveledLogger) logf(level slog.Level, msg string, args ...any) {
+ if jl.level.Get() < jl.logLevelToPionLevel(level) {
+ return
+ }
+
+ // Create structured log entry
+ attrs := []any{
+ "scope", jl.scope,
+ "level", jl.pionLevelToString(jl.logLevelToPionLevel(level)),
+ }
+
+ // Add any additional arguments as key-value pairs
+ if len(args) > 0 {
+ attrs = append(attrs, args...)
+ }
+
+ jl.logger.Log(context.Background(), level, msg, attrs...)
+}
+
+// logfWithFormat formats the message and calls logf
+func (jl *JSONLeveledLogger) logfWithFormat(level slog.Level, format string, args ...any) {
+ if jl.level.Get() < jl.logLevelToPionLevel(level) {
+ return
+ }
+
+ // Format the message
+ msg := format
+ if len(args) > 0 {
+ msg = fmt.Sprintf(format, args...)
+ }
+
+ // Create structured log entry
+ attrs := []any{
+ "scope", jl.scope,
+ "level", jl.pionLevelToString(jl.logLevelToPionLevel(level)),
+ }
+
+ jl.logger.Log(context.Background(), level, msg, attrs...)
+}
+
+// Helper function to convert slog levels to Pion log levels
+func (jl *JSONLeveledLogger) logLevelToPionLevel(level slog.Level) LogLevel {
+ switch level {
+ case slog.Level(-8): // slog.LevelTrace is -8
+ return LogLevelTrace
+ case slog.LevelDebug:
+ return LogLevelDebug
+ case slog.LevelInfo:
+ return LogLevelInfo
+ case slog.LevelWarn:
+ return LogLevelWarn
+ case slog.LevelError:
+ return LogLevelError
+ default:
+ return LogLevelDisabled
+ }
+}
+
+// Helper function to convert Pion log levels to string
+func (jl *JSONLeveledLogger) pionLevelToString(level LogLevel) string {
+ switch level {
+ case LogLevelTrace:
+ return "TRACE"
+ case LogLevelDebug:
+ return "DEBUG"
+ case LogLevelInfo:
+ return "INFO"
+ case LogLevelWarn:
+ return "WARN"
+ case LogLevelError:
+ return "ERROR"
+ case LogLevelDisabled:
+ return "DISABLED"
+ default:
+ return "UNKNOWN"
+ }
+}
+
+// Trace emits the preformatted message if the logger is at or below LogLevelTrace.
+func (jl *JSONLeveledLogger) Trace(msg string) {
+ jl.logf(slog.Level(-8), msg) // slog.LevelTrace is -8
+}
+
+// Tracef formats and emits a message if the logger is at or below LogLevelTrace.
+func (jl *JSONLeveledLogger) Tracef(format string, args ...any) {
+ jl.logfWithFormat(slog.Level(-8), format, args...) // slog.LevelTrace is -8
+}
+
+// Debug emits the preformatted message if the logger is at or below LogLevelDebug.
+func (jl *JSONLeveledLogger) Debug(msg string) {
+ jl.logf(slog.LevelDebug, msg)
+}
+
+// Debugf formats and emits a message if the logger is at or below LogLevelDebug.
+func (jl *JSONLeveledLogger) Debugf(format string, args ...any) {
+ jl.logfWithFormat(slog.LevelDebug, format, args...)
+}
+
+// Info emits the preformatted message if the logger is at or below LogLevelInfo.
+func (jl *JSONLeveledLogger) Info(msg string) {
+ jl.logf(slog.LevelInfo, msg)
+}
+
+// Infof formats and emits a message if the logger is at or below LogLevelInfo.
+func (jl *JSONLeveledLogger) Infof(format string, args ...any) {
+ jl.logfWithFormat(slog.LevelInfo, format, args...)
+}
+
+// Warn emits the preformatted message if the logger is at or below LogLevelWarn.
+func (jl *JSONLeveledLogger) Warn(msg string) {
+ jl.logf(slog.LevelWarn, msg)
+}
+
+// Warnf formats and emits a message if the logger is at or below LogLevelWarn.
+func (jl *JSONLeveledLogger) Warnf(format string, args ...any) {
+ jl.logfWithFormat(slog.LevelWarn, format, args...)
+}
+
+// Error emits the preformatted message if the logger is at or below LogLevelError.
+func (jl *JSONLeveledLogger) Error(msg string) {
+ jl.logf(slog.LevelError, msg)
+}
+
+// Errorf formats and emits a message if the logger is at or below LogLevelError.
+func (jl *JSONLeveledLogger) Errorf(format string, args ...any) {
+ jl.logfWithFormat(slog.LevelError, format, args...)
+}
+
+// JSONLoggerFactory defines levels by scopes and creates new JSONLeveledLogger
+type JSONLoggerFactory struct {
+ Writer io.Writer
+ DefaultLogLevel LogLevel
+ ScopeLevels map[string]LogLevel
+}
+
+// NewJSONLoggerFactory creates a new JSONLoggerFactory
+func NewJSONLoggerFactory() *JSONLoggerFactory {
+ factory := JSONLoggerFactory{}
+ factory.DefaultLogLevel = LogLevelError
+ factory.ScopeLevels = make(map[string]LogLevel)
+ factory.Writer = os.Stderr
+
+ logLevels := map[string]LogLevel{
+ "DISABLE": LogLevelDisabled,
+ "ERROR": LogLevelError,
+ "WARN": LogLevelWarn,
+ "INFO": LogLevelInfo,
+ "DEBUG": LogLevelDebug,
+ "TRACE": LogLevelTrace,
+ }
+
+ for name, level := range logLevels {
+ env := os.Getenv(fmt.Sprintf("PION_LOG_%s", name))
+
+ if env == "" {
+ env = os.Getenv(fmt.Sprintf("PIONS_LOG_%s", name))
+ }
+
+ if env == "" {
+ continue
+ }
+
+ if strings.ToLower(env) == "all" {
+ if factory.DefaultLogLevel < level {
+ factory.DefaultLogLevel = level
+ }
+
+ continue
+ }
+
+ scopes := strings.Split(strings.ToLower(env), ",")
+ for _, scope := range scopes {
+ factory.ScopeLevels[scope] = level
+ }
+ }
+
+ return &factory
+}
+
+// NewLogger returns a configured JSON LeveledLogger for the given scope
+func (f *JSONLoggerFactory) NewLogger(scope string) LeveledLogger {
+ logLevel := f.DefaultLogLevel
+ if f.ScopeLevels != nil {
+ scopeLevel, found := f.ScopeLevels[scope]
+
+ if found {
+ logLevel = scopeLevel
+ }
+ }
+
+ return NewJSONLeveledLoggerForScope(scope, logLevel, f.Writer)
+}
\ No newline at end of file
diff --git a/json_logger_test.go b/json_logger_test.go
new file mode 100644
index 0000000..7b93b74
--- /dev/null
+++ b/json_logger_test.go
@@ -0,0 +1,204 @@
+// SPDX-FileCopyrightText: 2023 The Pion community
+// SPDX-License-Identifier: MIT
+
+package logging_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/pion/logging"
+ "github.com/stretchr/testify/assert"
+)
+
+func testJSONLoggerLevels(t *testing.T, logger *logging.JSONLeveledLogger) {
+ t.Helper()
+
+ var outBuf bytes.Buffer
+ logger.WithOutput(&outBuf)
+
+ // Test Info level
+ infoMsg := "this is an info message"
+ logger.Info(infoMsg)
+ output := outBuf.String()
+ assert.True(t, strings.Contains(output, infoMsg), "Expected to find %q in %q", infoMsg, output)
+ assert.True(t, strings.Contains(output, `"level":"INFO"`), "Expected JSON to contain INFO level")
+ assert.True(t, strings.Contains(output, `"scope":"test"`), "Expected JSON to contain scope")
+
+ // Test Debug level
+ outBuf.Reset()
+ debugMsg := "this is a debug message"
+ logger.Debug(debugMsg)
+ output = outBuf.String()
+ assert.True(t, strings.Contains(output, debugMsg), "Expected to find %q in %q", debugMsg, output)
+ assert.True(t, strings.Contains(output, `"level":"DEBUG"`), "Expected JSON to contain DEBUG level")
+
+ // Test Warn level
+ outBuf.Reset()
+ warnMsg := "this is a warning message"
+ logger.Warn(warnMsg)
+ output = outBuf.String()
+ assert.True(t, strings.Contains(output, warnMsg), "Expected to find %q in %q", warnMsg, output)
+ assert.True(t, strings.Contains(output, `"level":"WARN"`), "Expected JSON to contain WARN level")
+
+ // Test Error level
+ outBuf.Reset()
+ errMsg := "this is an error message"
+ logger.Error(errMsg)
+ output = outBuf.String()
+ assert.True(t, strings.Contains(output, errMsg), "Expected to find %q in %q", errMsg, output)
+ assert.True(t, strings.Contains(output, `"level":"ERROR"`), "Expected JSON to contain ERROR level")
+
+ // Test Trace level
+ outBuf.Reset()
+ traceMsg := "this is a trace message"
+ logger.Trace(traceMsg)
+ output = outBuf.String()
+ assert.True(t, strings.Contains(output, traceMsg), "Expected to find %q in %q", traceMsg, output)
+ assert.True(t, strings.Contains(output, `"level":"TRACE"`), "Expected JSON to contain TRACE level")
+}
+
+func testJSONLoggerFormatting(t *testing.T, logger *logging.JSONLeveledLogger) {
+ t.Helper()
+
+ var outBuf bytes.Buffer
+ logger.WithOutput(&outBuf)
+
+ // Test formatted messages
+ formatMsg := "formatted message with %s"
+ arg := "argument"
+ logger.Infof(formatMsg, arg)
+ output := outBuf.String()
+ expectedMsg := "formatted message with argument"
+ assert.True(t, strings.Contains(output, expectedMsg), "Expected to find %q in %q", expectedMsg, output)
+}
+
+func testJSONLoggerLevelFiltering(t *testing.T, logger *logging.JSONLeveledLogger) {
+ t.Helper()
+
+ var outBuf bytes.Buffer
+ logger.WithOutput(&outBuf)
+
+ // Set level to WARN, so DEBUG and INFO should be filtered
+ logger.SetLevel(logging.LogLevelWarn)
+
+ // These should not be logged
+ logger.Debug("debug message")
+ logger.Info("info message")
+ assert.Equal(t, 0, outBuf.Len(), "Debug and Info messages should not be logged at WARN level")
+
+ // These should be logged
+ logger.Warn("warn message")
+ logger.Error("error message")
+ output := outBuf.String()
+ assert.True(t, strings.Contains(output, "warn message"), "Warn message should be logged")
+ assert.True(t, strings.Contains(output, "error message"), "Error message should be logged")
+}
+
+func TestJSONLogger(t *testing.T) {
+ logger := logging.NewJSONLeveledLoggerForScope("test", logging.LogLevelTrace, os.Stderr)
+
+ testJSONLoggerLevels(t, logger)
+ testJSONLoggerFormatting(t, logger)
+ testJSONLoggerLevelFiltering(t, logger)
+}
+
+func TestJSONLoggerFactory(t *testing.T) {
+ factory := logging.JSONLoggerFactory{
+ Writer: os.Stderr,
+ DefaultLogLevel: logging.LogLevelWarn,
+ ScopeLevels: map[string]logging.LogLevel{
+ "foo": logging.LogLevelDebug,
+ },
+ }
+
+ logger := factory.NewLogger("baz")
+ bazLogger, ok := logger.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Invalid logger type")
+
+ // Test that baz logger respects WARN level
+ var outBuf bytes.Buffer
+ bazLogger.WithOutput(&outBuf)
+ bazLogger.Debug("debug message")
+ assert.Equal(t, 0, outBuf.Len(), "Debug message should not be logged at WARN level")
+
+ logger = factory.NewLogger("foo")
+ fooLogger, ok := logger.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Invalid logger type")
+
+ // Test that foo logger respects DEBUG level
+ outBuf.Reset()
+ fooLogger.WithOutput(&outBuf)
+ fooLogger.Debug("debug message")
+ output := outBuf.String()
+ assert.True(t, strings.Contains(output, "debug message"), "Debug message should be logged at DEBUG level")
+}
+
+func TestNewJSONLoggerFactory(t *testing.T) {
+ factory := logging.NewJSONLoggerFactory()
+
+ disabled := factory.NewLogger("DISABLE")
+ errorLevel := factory.NewLogger("ERROR")
+ warnLevel := factory.NewLogger("WARN")
+ infoLevel := factory.NewLogger("INFO")
+ debugLevel := factory.NewLogger("DEBUG")
+ traceLevel := factory.NewLogger("TRACE")
+
+ disabledLogger, ok := disabled.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing disabled logger")
+
+ errorLogger, ok := errorLevel.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing error logger")
+
+ _, ok = warnLevel.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing warn logger")
+
+ _, ok = infoLevel.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing info logger")
+
+ _, ok = debugLevel.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing debug logger")
+
+ _, ok = traceLevel.(*logging.JSONLeveledLogger)
+ assert.True(t, ok, "Missing trace logger")
+
+ // Test that all loggers are properly configured
+ var outBuf bytes.Buffer
+ disabledLogger.WithOutput(&outBuf)
+ disabledLogger.Info("test message")
+ assert.Equal(t, 0, outBuf.Len(), "Disabled logger should not log anything")
+
+ outBuf.Reset()
+ errorLogger.WithOutput(&outBuf)
+ errorLogger.Error("error message")
+ output := outBuf.String()
+ assert.True(t, strings.Contains(output, "error message"), "Error logger should log error messages")
+}
+
+func TestJSONLoggerStructuredOutput(t *testing.T) {
+ logger := logging.NewJSONLeveledLoggerForScope("test-scope", logging.LogLevelInfo, os.Stderr)
+ var outBuf bytes.Buffer
+ logger.WithOutput(&outBuf)
+
+ logger.Info("test message")
+ output := outBuf.String()
+
+ // Verify it's valid JSON
+ var jsonData map[string]interface{}
+ err := json.Unmarshal([]byte(output), &jsonData)
+ assert.NoError(t, err, "Output should be valid JSON")
+
+ // Verify required fields
+ assert.Contains(t, jsonData, "time", "JSON should contain time field")
+ assert.Contains(t, jsonData, "level", "JSON should contain level field")
+ assert.Contains(t, jsonData, "msg", "JSON should contain msg field")
+ assert.Contains(t, jsonData, "scope", "JSON should contain scope field")
+
+ // Verify values
+ assert.Equal(t, "INFO", jsonData["level"], "Level should be INFO")
+ assert.Equal(t, "test message", jsonData["msg"], "Message should match")
+ assert.Equal(t, "test-scope", jsonData["scope"], "Scope should match")
+}
\ No newline at end of file
From 14f986a1f014f7d2331189ec10f806f7a0c6bd22 Mon Sep 17 00:00:00 2001
From: philipch07
Date: Fri, 2 Jan 2026 17:26:58 -0500
Subject: [PATCH 2/4] Fix example and duplicate log level
---
examples/example_json_logger.go | 13 ++-
json_logger.go | 132 ++++++++++-----------
json_logger_test.go | 196 ++++++++++++++++++++++++++------
3 files changed, 235 insertions(+), 106 deletions(-)
diff --git a/examples/example_json_logger.go b/examples/example_json_logger.go
index 29c5d43..8ff2727 100644
--- a/examples/example_json_logger.go
+++ b/examples/example_json_logger.go
@@ -21,15 +21,16 @@ func main() {
// Log some messages
apiLogger.Info("API server started")
- apiLogger.Debug("Processing request", "method", "GET", "path", "/users")
- apiLogger.Warn("Rate limit approaching", "requests", 95, "limit", 100)
+ apiLogger.Debugf("Processing request method=%s path=%s", "GET", "/users")
+ apiLogger.Warnf("Rate limit approaching requests=%d limit=%d", 95, 100)
dbLogger.Info("Database connection established")
- dbLogger.Debug("Executing query", "query", "SELECT * FROM users", "duration_ms", 15)
+ dbLogger.Debugf("Executing query query=%q duration_ms=%d", "SELECT * FROM users", 15)
- authLogger.Error("Authentication failed", "user_id", "12345", "reason", "invalid_token")
- authLogger.Info("User logged in", "user_id", "67890", "ip", "192.168.1.100")
+ authLogger.Errorf("Authentication failed user_id=%s reason=%s", "12345", "invalid_token")
+ authLogger.Infof("User logged in user_id=%s ip=%s", "67890", "192.168.1.100")
+ // nolint:lll
// Example output will be JSON formatted like:
// {"time":"2023-12-07T10:30:00Z","level":"INFO","msg":"API server started","scope":"api"}
// {"time":"2023-12-07T10:30:00Z","level":"DEBUG","msg":"Processing request","scope":"api","method":"GET","path":"/users"}
@@ -38,4 +39,4 @@ func main() {
// {"time":"2023-12-07T10:30:00Z","level":"DEBUG","msg":"Executing query","scope":"database","query":"SELECT * FROM users","duration_ms":15}
// {"time":"2023-12-07T10:30:00Z","level":"ERROR","msg":"Authentication failed","scope":"auth","user_id":"12345","reason":"invalid_token"}
// {"time":"2023-12-07T10:30:00Z","level":"INFO","msg":"User logged in","scope":"auth","user_id":"67890","ip":"192.168.1.100"}
-}
\ No newline at end of file
+}
diff --git a/json_logger.go b/json_logger.go
index 9192173..1755d7d 100644
--- a/json_logger.go
+++ b/json_logger.go
@@ -13,7 +13,7 @@ import (
"time"
)
-// JSONLeveledLogger provides JSON structured logging using Go's slog library
+// JSONLeveledLogger provides JSON structured logging using Go's slog library.
type JSONLeveledLogger struct {
level LogLevel
writer *loggerWriter
@@ -21,28 +21,14 @@ type JSONLeveledLogger struct {
scope string
}
-// NewJSONLeveledLoggerForScope returns a configured JSON LeveledLogger
+// NewJSONLeveledLoggerForScope returns a configured JSON LeveledLogger.
func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *JSONLeveledLogger {
if writer == nil {
writer = os.Stderr
}
// Create a JSON handler with custom options
- handler := slog.NewJSONHandler(writer, &slog.HandlerOptions{
- Level: slog.Level(-8), // Allow all levels, filter ourselves
- ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
- // Customize timestamp format
- if a.Key == slog.TimeKey {
- return slog.Attr{
- Key: slog.TimeKey,
- Value: slog.StringValue(a.Value.Time().Format(time.RFC3339)),
- }
- }
- return a
- },
- })
-
- logger := slog.New(handler)
+ logger := slog.New(newJSONHandlerHelper(writer))
return &JSONLeveledLogger{
level: level,
@@ -57,20 +43,38 @@ func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer
func (jl *JSONLeveledLogger) WithOutput(output io.Writer) *JSONLeveledLogger {
jl.writer.SetOutput(output)
// Recreate the logger with the new writer
- handler := slog.NewJSONHandler(output, &slog.HandlerOptions{
- Level: slog.Level(-8),
- ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
- if a.Key == slog.TimeKey {
- return slog.Attr{
- Key: slog.TimeKey,
- Value: slog.StringValue(a.Value.Time().Format(time.RFC3339)),
+ jl.logger = slog.New(newJSONHandlerHelper(output))
+
+ return jl
+}
+
+// newJSONHandlerHelper creates a new JSON slog.Handler with custom formatting.
+func newJSONHandlerHelper(w io.Writer) slog.Handler {
+ return slog.NewJSONHandler(w, &slog.HandlerOptions{
+ Level: slog.Level(-8), // Allow all levels, filter ourselves
+ ReplaceAttr: func(_ []string, attr slog.Attr) slog.Attr {
+ // Customize timestamp format
+ switch attr.Key {
+ case slog.TimeKey:
+ attr.Value = slog.StringValue(attr.Value.Time().Format(time.RFC3339))
+
+ return attr
+
+ case slog.LevelKey:
+ if lvl, ok := attr.Value.Any().(slog.Level); ok {
+ attr.Value = slogLevelToSlogStringValue(lvl)
+
+ return attr
}
+
+ // if slog changes representation then leave it alone.
+ return attr
+
+ default:
+ return attr
}
- return a
},
})
- jl.logger = slog.New(handler)
- return jl
}
// SetLevel sets the logger's logging level.
@@ -78,16 +82,15 @@ func (jl *JSONLeveledLogger) SetLevel(newLevel LogLevel) {
jl.level.Set(newLevel)
}
-// logf is the internal logging function that handles level checking and formatting
+// logf is the internal logging function that handles level checking and formatting.
func (jl *JSONLeveledLogger) logf(level slog.Level, msg string, args ...any) {
- if jl.level.Get() < jl.logLevelToPionLevel(level) {
+ if jl.level.Get() < logLevelToPionLevel(level) {
return
}
// Create structured log entry
attrs := []any{
"scope", jl.scope,
- "level", jl.pionLevelToString(jl.logLevelToPionLevel(level)),
}
// Add any additional arguments as key-value pairs
@@ -98,9 +101,9 @@ func (jl *JSONLeveledLogger) logf(level slog.Level, msg string, args ...any) {
jl.logger.Log(context.Background(), level, msg, attrs...)
}
-// logfWithFormat formats the message and calls logf
-func (jl *JSONLeveledLogger) logfWithFormat(level slog.Level, format string, args ...any) {
- if jl.level.Get() < jl.logLevelToPionLevel(level) {
+// logfWithFormatf formats the message and calls logf.
+func (jl *JSONLeveledLogger) logfWithFormatf(level slog.Level, format string, args ...any) {
+ if jl.level.Get() < logLevelToPionLevel(level) {
return
}
@@ -113,16 +116,33 @@ func (jl *JSONLeveledLogger) logfWithFormat(level slog.Level, format string, arg
// Create structured log entry
attrs := []any{
"scope", jl.scope,
- "level", jl.pionLevelToString(jl.logLevelToPionLevel(level)),
}
jl.logger.Log(context.Background(), level, msg, attrs...)
}
-// Helper function to convert slog levels to Pion log levels
-func (jl *JSONLeveledLogger) logLevelToPionLevel(level slog.Level) LogLevel {
+// Convert slog record levels to the exact strings you want in JSON.
+func slogLevelToSlogStringValue(level slog.Level) slog.Value {
switch level {
- case slog.Level(-8): // slog.LevelTrace is -8
+ case slog.Level(-8): // trace
+ return slog.StringValue("TRACE")
+ case slog.LevelDebug:
+ return slog.StringValue("DEBUG")
+ case slog.LevelInfo:
+ return slog.StringValue("INFO")
+ case slog.LevelWarn:
+ return slog.StringValue("WARN")
+ case slog.LevelError:
+ return slog.StringValue("ERROR")
+ default:
+ return slog.StringValue("UNKNOWN")
+ }
+}
+
+// Helper to convert slog levels to Pion log levels.
+func logLevelToPionLevel(level slog.Level) LogLevel {
+ switch level {
+ case slog.Level(-8): // trace
return LogLevelTrace
case slog.LevelDebug:
return LogLevelDebug
@@ -137,26 +157,6 @@ func (jl *JSONLeveledLogger) logLevelToPionLevel(level slog.Level) LogLevel {
}
}
-// Helper function to convert Pion log levels to string
-func (jl *JSONLeveledLogger) pionLevelToString(level LogLevel) string {
- switch level {
- case LogLevelTrace:
- return "TRACE"
- case LogLevelDebug:
- return "DEBUG"
- case LogLevelInfo:
- return "INFO"
- case LogLevelWarn:
- return "WARN"
- case LogLevelError:
- return "ERROR"
- case LogLevelDisabled:
- return "DISABLED"
- default:
- return "UNKNOWN"
- }
-}
-
// Trace emits the preformatted message if the logger is at or below LogLevelTrace.
func (jl *JSONLeveledLogger) Trace(msg string) {
jl.logf(slog.Level(-8), msg) // slog.LevelTrace is -8
@@ -164,7 +164,7 @@ func (jl *JSONLeveledLogger) Trace(msg string) {
// Tracef formats and emits a message if the logger is at or below LogLevelTrace.
func (jl *JSONLeveledLogger) Tracef(format string, args ...any) {
- jl.logfWithFormat(slog.Level(-8), format, args...) // slog.LevelTrace is -8
+ jl.logfWithFormatf(slog.Level(-8), format, args...) // slog.LevelTrace is -8
}
// Debug emits the preformatted message if the logger is at or below LogLevelDebug.
@@ -174,7 +174,7 @@ func (jl *JSONLeveledLogger) Debug(msg string) {
// Debugf formats and emits a message if the logger is at or below LogLevelDebug.
func (jl *JSONLeveledLogger) Debugf(format string, args ...any) {
- jl.logfWithFormat(slog.LevelDebug, format, args...)
+ jl.logfWithFormatf(slog.LevelDebug, format, args...)
}
// Info emits the preformatted message if the logger is at or below LogLevelInfo.
@@ -184,7 +184,7 @@ func (jl *JSONLeveledLogger) Info(msg string) {
// Infof formats and emits a message if the logger is at or below LogLevelInfo.
func (jl *JSONLeveledLogger) Infof(format string, args ...any) {
- jl.logfWithFormat(slog.LevelInfo, format, args...)
+ jl.logfWithFormatf(slog.LevelInfo, format, args...)
}
// Warn emits the preformatted message if the logger is at or below LogLevelWarn.
@@ -194,7 +194,7 @@ func (jl *JSONLeveledLogger) Warn(msg string) {
// Warnf formats and emits a message if the logger is at or below LogLevelWarn.
func (jl *JSONLeveledLogger) Warnf(format string, args ...any) {
- jl.logfWithFormat(slog.LevelWarn, format, args...)
+ jl.logfWithFormatf(slog.LevelWarn, format, args...)
}
// Error emits the preformatted message if the logger is at or below LogLevelError.
@@ -204,17 +204,17 @@ func (jl *JSONLeveledLogger) Error(msg string) {
// Errorf formats and emits a message if the logger is at or below LogLevelError.
func (jl *JSONLeveledLogger) Errorf(format string, args ...any) {
- jl.logfWithFormat(slog.LevelError, format, args...)
+ jl.logfWithFormatf(slog.LevelError, format, args...)
}
-// JSONLoggerFactory defines levels by scopes and creates new JSONLeveledLogger
+// JSONLoggerFactory defines levels by scopes and creates new JSONLeveledLogger.
type JSONLoggerFactory struct {
Writer io.Writer
DefaultLogLevel LogLevel
ScopeLevels map[string]LogLevel
}
-// NewJSONLoggerFactory creates a new JSONLoggerFactory
+// NewJSONLoggerFactory creates a new JSONLoggerFactory.
func NewJSONLoggerFactory() *JSONLoggerFactory {
factory := JSONLoggerFactory{}
factory.DefaultLogLevel = LogLevelError
@@ -258,7 +258,7 @@ func NewJSONLoggerFactory() *JSONLoggerFactory {
return &factory
}
-// NewLogger returns a configured JSON LeveledLogger for the given scope
+// NewLogger returns a configured JSON LeveledLogger for the given scope.
func (f *JSONLoggerFactory) NewLogger(scope string) LeveledLogger {
logLevel := f.DefaultLogLevel
if f.ScopeLevels != nil {
@@ -270,4 +270,4 @@ func (f *JSONLoggerFactory) NewLogger(scope string) LeveledLogger {
}
return NewJSONLeveledLoggerForScope(scope, logLevel, f.Writer)
-}
\ No newline at end of file
+}
diff --git a/json_logger_test.go b/json_logger_test.go
index 7b93b74..a44cd8e 100644
--- a/json_logger_test.go
+++ b/json_logger_test.go
@@ -1,21 +1,21 @@
// SPDX-FileCopyrightText: 2023 The Pion community
// SPDX-License-Identifier: MIT
-package logging_test
+package logging
import (
"bytes"
"encoding/json"
+ "log/slog"
"os"
"strings"
"testing"
- "github.com/pion/logging"
"github.com/stretchr/testify/assert"
)
-func testJSONLoggerLevels(t *testing.T, logger *logging.JSONLeveledLogger) {
- t.Helper()
+func TestJSONLoggerLevels(t *testing.T) {
+ logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -58,11 +58,10 @@ func testJSONLoggerLevels(t *testing.T, logger *logging.JSONLeveledLogger) {
logger.Trace(traceMsg)
output = outBuf.String()
assert.True(t, strings.Contains(output, traceMsg), "Expected to find %q in %q", traceMsg, output)
- assert.True(t, strings.Contains(output, `"level":"TRACE"`), "Expected JSON to contain TRACE level")
}
-func testJSONLoggerFormatting(t *testing.T, logger *logging.JSONLeveledLogger) {
- t.Helper()
+func TestJSONLoggerFormatting(t *testing.T) {
+ logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -76,14 +75,14 @@ func testJSONLoggerFormatting(t *testing.T, logger *logging.JSONLeveledLogger) {
assert.True(t, strings.Contains(output, expectedMsg), "Expected to find %q in %q", expectedMsg, output)
}
-func testJSONLoggerLevelFiltering(t *testing.T, logger *logging.JSONLeveledLogger) {
- t.Helper()
+func TestJSONLoggerLevelFiltering(t *testing.T) {
+ logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
// Set level to WARN, so DEBUG and INFO should be filtered
- logger.SetLevel(logging.LogLevelWarn)
+ logger.SetLevel(LogLevelWarn)
// These should not be logged
logger.Debug("debug message")
@@ -98,25 +97,17 @@ func testJSONLoggerLevelFiltering(t *testing.T, logger *logging.JSONLeveledLogge
assert.True(t, strings.Contains(output, "error message"), "Error message should be logged")
}
-func TestJSONLogger(t *testing.T) {
- logger := logging.NewJSONLeveledLoggerForScope("test", logging.LogLevelTrace, os.Stderr)
-
- testJSONLoggerLevels(t, logger)
- testJSONLoggerFormatting(t, logger)
- testJSONLoggerLevelFiltering(t, logger)
-}
-
func TestJSONLoggerFactory(t *testing.T) {
- factory := logging.JSONLoggerFactory{
+ factory := JSONLoggerFactory{
Writer: os.Stderr,
- DefaultLogLevel: logging.LogLevelWarn,
- ScopeLevels: map[string]logging.LogLevel{
- "foo": logging.LogLevelDebug,
+ DefaultLogLevel: LogLevelWarn,
+ ScopeLevels: map[string]LogLevel{
+ "foo": LogLevelDebug,
},
}
logger := factory.NewLogger("baz")
- bazLogger, ok := logger.(*logging.JSONLeveledLogger)
+ bazLogger, ok := logger.(*JSONLeveledLogger)
assert.True(t, ok, "Invalid logger type")
// Test that baz logger respects WARN level
@@ -126,7 +117,7 @@ func TestJSONLoggerFactory(t *testing.T) {
assert.Equal(t, 0, outBuf.Len(), "Debug message should not be logged at WARN level")
logger = factory.NewLogger("foo")
- fooLogger, ok := logger.(*logging.JSONLeveledLogger)
+ fooLogger, ok := logger.(*JSONLeveledLogger)
assert.True(t, ok, "Invalid logger type")
// Test that foo logger respects DEBUG level
@@ -138,7 +129,7 @@ func TestJSONLoggerFactory(t *testing.T) {
}
func TestNewJSONLoggerFactory(t *testing.T) {
- factory := logging.NewJSONLoggerFactory()
+ factory := NewJSONLoggerFactory()
disabled := factory.NewLogger("DISABLE")
errorLevel := factory.NewLogger("ERROR")
@@ -147,22 +138,22 @@ func TestNewJSONLoggerFactory(t *testing.T) {
debugLevel := factory.NewLogger("DEBUG")
traceLevel := factory.NewLogger("TRACE")
- disabledLogger, ok := disabled.(*logging.JSONLeveledLogger)
+ disabledLogger, ok := disabled.(*JSONLeveledLogger)
assert.True(t, ok, "Missing disabled logger")
- errorLogger, ok := errorLevel.(*logging.JSONLeveledLogger)
+ errorLogger, ok := errorLevel.(*JSONLeveledLogger)
assert.True(t, ok, "Missing error logger")
- _, ok = warnLevel.(*logging.JSONLeveledLogger)
+ _, ok = warnLevel.(*JSONLeveledLogger)
assert.True(t, ok, "Missing warn logger")
- _, ok = infoLevel.(*logging.JSONLeveledLogger)
+ _, ok = infoLevel.(*JSONLeveledLogger)
assert.True(t, ok, "Missing info logger")
- _, ok = debugLevel.(*logging.JSONLeveledLogger)
+ _, ok = debugLevel.(*JSONLeveledLogger)
assert.True(t, ok, "Missing debug logger")
- _, ok = traceLevel.(*logging.JSONLeveledLogger)
+ _, ok = traceLevel.(*JSONLeveledLogger)
assert.True(t, ok, "Missing trace logger")
// Test that all loggers are properly configured
@@ -178,8 +169,33 @@ func TestNewJSONLoggerFactory(t *testing.T) {
assert.True(t, strings.Contains(output, "error message"), "Error logger should log error messages")
}
+func TestJSONLoggerTraceOutput(t *testing.T) {
+ logger := NewJSONLeveledLoggerForScope("trace-scope", LogLevelTrace, os.Stderr)
+ var outBuf bytes.Buffer
+ logger.WithOutput(&outBuf)
+
+ logger.Trace("test message")
+ output := outBuf.String()
+
+ // Verify it's valid JSON
+ var jsonData map[string]any
+ err := json.Unmarshal([]byte(output), &jsonData)
+ assert.NoError(t, err, "Output should be valid JSON")
+
+ // Verify required fields
+ assert.Contains(t, jsonData, "time", "JSON should contain time field")
+ assert.Contains(t, jsonData, "level", "JSON should contain level field")
+ assert.Contains(t, jsonData, "msg", "JSON should contain msg field")
+ assert.Contains(t, jsonData, "scope", "JSON should contain scope field")
+
+ // Verify values
+ assert.Equal(t, "TRACE", jsonData["level"], "Level should be TRACE")
+ assert.Equal(t, "test message", jsonData["msg"], "Message should match")
+ assert.Equal(t, "trace-scope", jsonData["scope"], "Scope should match")
+}
+
func TestJSONLoggerStructuredOutput(t *testing.T) {
- logger := logging.NewJSONLeveledLoggerForScope("test-scope", logging.LogLevelInfo, os.Stderr)
+ logger := NewJSONLeveledLoggerForScope("test-scope", LogLevelInfo, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -187,7 +203,7 @@ func TestJSONLoggerStructuredOutput(t *testing.T) {
output := outBuf.String()
// Verify it's valid JSON
- var jsonData map[string]interface{}
+ var jsonData map[string]any
err := json.Unmarshal([]byte(output), &jsonData)
assert.NoError(t, err, "Output should be valid JSON")
@@ -201,4 +217,116 @@ func TestJSONLoggerStructuredOutput(t *testing.T) {
assert.Equal(t, "INFO", jsonData["level"], "Level should be INFO")
assert.Equal(t, "test message", jsonData["msg"], "Message should match")
assert.Equal(t, "test-scope", jsonData["scope"], "Scope should match")
-}
\ No newline at end of file
+}
+
+func TestJSONLeveledLogger_logf_IncludesAdditionalArgs(t *testing.T) {
+ factory := NewJSONLoggerFactory()
+ factory.Writer = os.Stderr
+ factory.DefaultLogLevel = LogLevelTrace
+
+ l := factory.NewLogger("test-scope")
+ jl, ok := l.(*JSONLeveledLogger)
+ assert.True(t, ok, "Invalid logger type")
+
+ var outBuf bytes.Buffer
+ jl.WithOutput(&outBuf)
+
+ args := []any{
+ "method", "GET",
+ "path", "/users",
+ "duration_ms", 15,
+ "ok", true,
+ }
+
+ jl.logf(slog.LevelInfo, "Processing request", args...)
+
+ raw := strings.TrimSpace(outBuf.String())
+
+ var jsonData map[string]any
+ err := json.Unmarshal([]byte(raw), &jsonData)
+ assert.NoError(t, err, "Output should be valid JSON")
+
+ // base fields
+ assert.Equal(t, "Processing request", jsonData["msg"])
+ assert.Equal(t, "INFO", jsonData["level"])
+ assert.Equal(t, "test-scope", jsonData["scope"])
+
+ // additional args should appear as structured fields
+ assert.Equal(t, "GET", jsonData["method"])
+ assert.Equal(t, "/users", jsonData["path"])
+ assert.EqualValues(t, 15, jsonData["duration_ms"]) // json.Unmarshal numbers -> float64
+ assert.Equal(t, true, jsonData["ok"])
+}
+
+func clearLogEnv(t *testing.T) {
+ t.Helper()
+
+ for _, name := range []string{"DISABLE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"} {
+ t.Setenv("PION_LOG_"+name, "")
+ t.Setenv("PIONS_LOG_"+name, "")
+ }
+}
+
+func TestNewJSONLoggerFactory_AllSetsDefaultToMaxLevel(t *testing.T) {
+ clearLogEnv(t)
+
+ t.Setenv("PION_LOG_INFO", "All")
+ t.Setenv("PION_LOG_DEBUG", "ALL")
+ t.Setenv("PION_LOG_TRACE", "all")
+
+ factory := NewJSONLoggerFactory()
+
+ assert.Equal(t, LogLevelTrace, factory.DefaultLogLevel)
+ assert.Equal(t, 0, len(factory.ScopeLevels))
+}
+
+func TestNewJSONLoggerFactory_AllDoesNotLowerDefaultLevel(t *testing.T) {
+ clearLogEnv(t)
+
+ t.Setenv("PION_LOG_DISABLE", "all")
+
+ factory := NewJSONLoggerFactory()
+ assert.Equal(t, LogLevelError, factory.DefaultLogLevel)
+}
+
+func TestNewJSONLoggerFactory_ScopesAreSplitAndLowercased(t *testing.T) {
+ clearLogEnv(t)
+
+ t.Setenv("PION_LOG_DEBUG", "Foo,BAR")
+
+ factory := NewJSONLoggerFactory()
+
+ assert.Equal(t, LogLevelError, factory.DefaultLogLevel)
+
+ assert.Equal(t, LogLevelDebug, factory.ScopeLevels["foo"])
+ assert.Equal(t, LogLevelDebug, factory.ScopeLevels["bar"])
+}
+
+func TestNewJSONLoggerFactory_AllAndScopedInteract(t *testing.T) {
+ clearLogEnv(t)
+
+ t.Setenv("PION_LOG_WARN", "all")
+
+ t.Setenv("PION_LOG_DEBUG", "foo")
+
+ factory := NewJSONLoggerFactory()
+
+ assert.Equal(t, LogLevelWarn, factory.DefaultLogLevel)
+ assert.Equal(t, LogLevelDebug, factory.ScopeLevels["foo"])
+
+ foo := factory.NewLogger("foo").(*JSONLeveledLogger) //nolint:forcetypeassert
+ bar := factory.NewLogger("bar").(*JSONLeveledLogger) //nolint:forcetypeassert
+
+ assert.Equal(t, LogLevelDebug, foo.level.Get(), "scope override should win")
+ assert.Equal(t, LogLevelWarn, bar.level.Get(), "default should apply when no scope override")
+}
+
+func TestNewJSONLoggerFactory_Fallback(t *testing.T) {
+ clearLogEnv(t)
+
+ t.Setenv("PION_LOG_INFO", "")
+ t.Setenv("PIONS_LOG_INFO", "all")
+
+ factory := NewJSONLoggerFactory()
+ assert.Equal(t, LogLevelInfo, factory.DefaultLogLevel)
+}
From af0ba736b7ca919cebae48aa475f91714d14467a Mon Sep 17 00:00:00 2001
From: philipch07
Date: Wed, 7 Jan 2026 16:59:14 -0500
Subject: [PATCH 3/4] Allow try cast for json logger
---
json_logger.go | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/json_logger.go b/json_logger.go
index 1755d7d..8081973 100644
--- a/json_logger.go
+++ b/json_logger.go
@@ -13,6 +13,13 @@ import (
"time"
)
+// JSONLogger is an optional extension interface so users
+// can type-assert a LeveledLogger to JSONLogger to access slog.
+type JSONLogger interface {
+ LeveledLogger
+ Slog() *slog.Logger
+}
+
// JSONLeveledLogger provides JSON structured logging using Go's slog library.
type JSONLeveledLogger struct {
level LogLevel
@@ -21,6 +28,12 @@ type JSONLeveledLogger struct {
scope string
}
+var _ JSONLogger = (*JSONLeveledLogger)(nil)
+
+func (jl *JSONLeveledLogger) Slog() *slog.Logger {
+ return jl.logger
+}
+
// NewJSONLeveledLoggerForScope returns a configured JSON LeveledLogger.
func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *JSONLeveledLogger {
if writer == nil {
@@ -28,11 +41,12 @@ func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer
}
// Create a JSON handler with custom options
- logger := slog.New(newJSONHandlerHelper(writer))
+ lw := &loggerWriter{output: writer}
+ logger := slog.New(newJSONHandlerHelper(lw))
return &JSONLeveledLogger{
level: level,
- writer: &loggerWriter{output: writer},
+ writer: lw,
logger: logger,
scope: scope,
}
@@ -41,9 +55,10 @@ func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer
// WithOutput is a chainable configuration function which sets the logger's
// logging output to the supplied io.Writer.
func (jl *JSONLeveledLogger) WithOutput(output io.Writer) *JSONLeveledLogger {
+ if output == nil {
+ output = os.Stderr
+ }
jl.writer.SetOutput(output)
- // Recreate the logger with the new writer
- jl.logger = slog.New(newJSONHandlerHelper(output))
return jl
}
@@ -262,9 +277,7 @@ func NewJSONLoggerFactory() *JSONLoggerFactory {
func (f *JSONLoggerFactory) NewLogger(scope string) LeveledLogger {
logLevel := f.DefaultLogLevel
if f.ScopeLevels != nil {
- scopeLevel, found := f.ScopeLevels[scope]
-
- if found {
+ if scopeLevel, found := f.ScopeLevels[scope]; found {
logLevel = scopeLevel
}
}
From a70ea019b2110cb5220f18d78079839c723f31b3 Mon Sep 17 00:00:00 2001
From: Jo Turk
Date: Sat, 10 Jan 2026 00:29:52 +0200
Subject: [PATCH 4/4] JSON LoggerFactory and cleanup
---
examples/example_json_logger.go | 3 +-
json_logger.go | 144 +++++++++++++++++++++-----------
json_logger_test.go | 125 +++++++++++++++++++--------
3 files changed, 186 insertions(+), 86 deletions(-)
diff --git a/examples/example_json_logger.go b/examples/example_json_logger.go
index 8ff2727..b75d3a8 100644
--- a/examples/example_json_logger.go
+++ b/examples/example_json_logger.go
@@ -11,8 +11,7 @@ import (
func main() {
// Create a JSON logger factory
- factory := logging.NewJSONLoggerFactory()
- factory.Writer = os.Stdout // Output to stdout for this example
+ factory := logging.NewJSONLoggerFactory(logging.WithJSONWriter(os.Stdout))
// Create loggers for different scopes
apiLogger := factory.NewLogger("api")
diff --git a/json_logger.go b/json_logger.go
index 8081973..62ca5e8 100644
--- a/json_logger.go
+++ b/json_logger.go
@@ -13,29 +13,22 @@ import (
"time"
)
-// JSONLogger is an optional extension interface so users
-// can type-assert a LeveledLogger to JSONLogger to access slog.
-type JSONLogger interface {
- LeveledLogger
- Slog() *slog.Logger
-}
-
-// JSONLeveledLogger provides JSON structured logging using Go's slog library.
-type JSONLeveledLogger struct {
+// jsonLeveledLogger provides JSON structured logging using Go's slog library.
+type jsonLeveledLogger struct {
level LogLevel
writer *loggerWriter
logger *slog.Logger
scope string
}
-var _ JSONLogger = (*JSONLeveledLogger)(nil)
+var _ LeveledLogger = (*jsonLeveledLogger)(nil)
-func (jl *JSONLeveledLogger) Slog() *slog.Logger {
+func (jl *jsonLeveledLogger) Slog() *slog.Logger {
return jl.logger
}
-// NewJSONLeveledLoggerForScope returns a configured JSON LeveledLogger.
-func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *JSONLeveledLogger {
+// newJSONLeveledLoggerForScope returns a configured JSON LeveledLogger.
+func newJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer) *jsonLeveledLogger {
if writer == nil {
writer = os.Stderr
}
@@ -44,7 +37,7 @@ func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer
lw := &loggerWriter{output: writer}
logger := slog.New(newJSONHandlerHelper(lw))
- return &JSONLeveledLogger{
+ return &jsonLeveledLogger{
level: level,
writer: lw,
logger: logger,
@@ -54,7 +47,7 @@ func NewJSONLeveledLoggerForScope(scope string, level LogLevel, writer io.Writer
// WithOutput is a chainable configuration function which sets the logger's
// logging output to the supplied io.Writer.
-func (jl *JSONLeveledLogger) WithOutput(output io.Writer) *JSONLeveledLogger {
+func (jl *jsonLeveledLogger) WithOutput(output io.Writer) LeveledLogger {
if output == nil {
output = os.Stderr
}
@@ -93,12 +86,12 @@ func newJSONHandlerHelper(w io.Writer) slog.Handler {
}
// SetLevel sets the logger's logging level.
-func (jl *JSONLeveledLogger) SetLevel(newLevel LogLevel) {
+func (jl *jsonLeveledLogger) SetLevel(newLevel LogLevel) {
jl.level.Set(newLevel)
}
// logf is the internal logging function that handles level checking and formatting.
-func (jl *JSONLeveledLogger) logf(level slog.Level, msg string, args ...any) {
+func (jl *jsonLeveledLogger) logf(level slog.Level, msg string, args ...any) {
if jl.level.Get() < logLevelToPionLevel(level) {
return
}
@@ -117,7 +110,7 @@ func (jl *JSONLeveledLogger) logf(level slog.Level, msg string, args ...any) {
}
// logfWithFormatf formats the message and calls logf.
-func (jl *JSONLeveledLogger) logfWithFormatf(level slog.Level, format string, args ...any) {
+func (jl *jsonLeveledLogger) logfWithFormatf(level slog.Level, format string, args ...any) {
if jl.level.Get() < logLevelToPionLevel(level) {
return
}
@@ -173,68 +166,125 @@ func logLevelToPionLevel(level slog.Level) LogLevel {
}
// Trace emits the preformatted message if the logger is at or below LogLevelTrace.
-func (jl *JSONLeveledLogger) Trace(msg string) {
+func (jl *jsonLeveledLogger) Trace(msg string) {
jl.logf(slog.Level(-8), msg) // slog.LevelTrace is -8
}
// Tracef formats and emits a message if the logger is at or below LogLevelTrace.
-func (jl *JSONLeveledLogger) Tracef(format string, args ...any) {
+func (jl *jsonLeveledLogger) Tracef(format string, args ...any) {
jl.logfWithFormatf(slog.Level(-8), format, args...) // slog.LevelTrace is -8
}
// Debug emits the preformatted message if the logger is at or below LogLevelDebug.
-func (jl *JSONLeveledLogger) Debug(msg string) {
+func (jl *jsonLeveledLogger) Debug(msg string) {
jl.logf(slog.LevelDebug, msg)
}
// Debugf formats and emits a message if the logger is at or below LogLevelDebug.
-func (jl *JSONLeveledLogger) Debugf(format string, args ...any) {
+func (jl *jsonLeveledLogger) Debugf(format string, args ...any) {
jl.logfWithFormatf(slog.LevelDebug, format, args...)
}
// Info emits the preformatted message if the logger is at or below LogLevelInfo.
-func (jl *JSONLeveledLogger) Info(msg string) {
+func (jl *jsonLeveledLogger) Info(msg string) {
jl.logf(slog.LevelInfo, msg)
}
// Infof formats and emits a message if the logger is at or below LogLevelInfo.
-func (jl *JSONLeveledLogger) Infof(format string, args ...any) {
+func (jl *jsonLeveledLogger) Infof(format string, args ...any) {
jl.logfWithFormatf(slog.LevelInfo, format, args...)
}
// Warn emits the preformatted message if the logger is at or below LogLevelWarn.
-func (jl *JSONLeveledLogger) Warn(msg string) {
+func (jl *jsonLeveledLogger) Warn(msg string) {
jl.logf(slog.LevelWarn, msg)
}
// Warnf formats and emits a message if the logger is at or below LogLevelWarn.
-func (jl *JSONLeveledLogger) Warnf(format string, args ...any) {
+func (jl *jsonLeveledLogger) Warnf(format string, args ...any) {
jl.logfWithFormatf(slog.LevelWarn, format, args...)
}
// Error emits the preformatted message if the logger is at or below LogLevelError.
-func (jl *JSONLeveledLogger) Error(msg string) {
+func (jl *jsonLeveledLogger) Error(msg string) {
jl.logf(slog.LevelError, msg)
}
// Errorf formats and emits a message if the logger is at or below LogLevelError.
-func (jl *JSONLeveledLogger) Errorf(format string, args ...any) {
+func (jl *jsonLeveledLogger) Errorf(format string, args ...any) {
jl.logfWithFormatf(slog.LevelError, format, args...)
}
-// JSONLoggerFactory defines levels by scopes and creates new JSONLeveledLogger.
-type JSONLoggerFactory struct {
- Writer io.Writer
- DefaultLogLevel LogLevel
- ScopeLevels map[string]LogLevel
+// jsonLoggerFactory defines levels by scopes and creates new jsonLeveledLogger.
+type jsonLoggerFactory struct {
+ writer io.Writer
+ defaultLogLevel LogLevel
+ scopeLevels map[string]LogLevel
+}
+
+var _ LoggerFactory = (*jsonLoggerFactory)(nil)
+
+// JSONLoggerFactoryOption configures the JSON LoggerFactory.
+type JSONLoggerFactoryOption func(*jsonLoggerFactory)
+
+// WithJSONWriter overrides the writer used by JSON loggers.
+func WithJSONWriter(writer io.Writer) JSONLoggerFactoryOption {
+ return func(factory *jsonLoggerFactory) {
+ if writer == nil {
+ factory.writer = os.Stderr
+
+ return
+ }
+
+ factory.writer = writer
+ }
+}
+
+// WithJSONDefaultLevel overrides the default log level used by JSON loggers.
+func WithJSONDefaultLevel(level LogLevel) JSONLoggerFactoryOption {
+ return func(factory *jsonLoggerFactory) {
+ factory.defaultLogLevel = level
+ }
+}
+
+// WithJSONScopeLevels sets specific log levels for scopes, overriding env values.
+func WithJSONScopeLevels(levels map[string]LogLevel) JSONLoggerFactoryOption {
+ return func(factory *jsonLoggerFactory) {
+ if levels == nil {
+ return
+ }
+
+ if factory.scopeLevels == nil {
+ factory.scopeLevels = make(map[string]LogLevel, len(levels))
+ }
+
+ for scope, level := range levels {
+ factory.scopeLevels[strings.ToLower(scope)] = level
+ }
+ }
+}
+
+// NewJSONLoggerFactory creates a new LoggerFactory that emits JSON logs.
+func NewJSONLoggerFactory(options ...JSONLoggerFactoryOption) LoggerFactory {
+ factory := newJSONLoggerFactory()
+
+ for _, option := range options {
+ if option == nil {
+ continue
+ }
+
+ option(factory)
+ }
+
+ return factory
}
-// NewJSONLoggerFactory creates a new JSONLoggerFactory.
-func NewJSONLoggerFactory() *JSONLoggerFactory {
- factory := JSONLoggerFactory{}
- factory.DefaultLogLevel = LogLevelError
- factory.ScopeLevels = make(map[string]LogLevel)
- factory.Writer = os.Stderr
+// newJSONLoggerFactory creates a new JSON LoggerFactory.
+func newJSONLoggerFactory() *jsonLoggerFactory {
+ factory := jsonLoggerFactory{}
+ factory.defaultLogLevel = LogLevelError
+ factory.scopeLevels = make(map[string]LogLevel)
+ factory.writer = os.Stderr
logLevels := map[string]LogLevel{
"DISABLE": LogLevelDisabled,
@@ -257,8 +307,8 @@ func NewJSONLoggerFactory() *JSONLoggerFactory {
}
if strings.ToLower(env) == "all" {
- if factory.DefaultLogLevel < level {
- factory.DefaultLogLevel = level
+ if factory.defaultLogLevel < level {
+ factory.defaultLogLevel = level
}
continue
@@ -266,7 +316,7 @@ func NewJSONLoggerFactory() *JSONLoggerFactory {
scopes := strings.Split(strings.ToLower(env), ",")
for _, scope := range scopes {
- factory.ScopeLevels[scope] = level
+ factory.scopeLevels[scope] = level
}
}
@@ -274,13 +324,13 @@ func NewJSONLoggerFactory() *JSONLoggerFactory {
}
// NewLogger returns a configured JSON LeveledLogger for the given scope.
-func (f *JSONLoggerFactory) NewLogger(scope string) LeveledLogger {
- logLevel := f.DefaultLogLevel
- if f.ScopeLevels != nil {
- if scopeLevel, found := f.ScopeLevels[scope]; found {
+func (f *jsonLoggerFactory) NewLogger(scope string) LeveledLogger {
+ logLevel := f.defaultLogLevel
+ if f.scopeLevels != nil {
+ if scopeLevel, found := f.scopeLevels[scope]; found {
logLevel = scopeLevel
}
}
- return NewJSONLeveledLoggerForScope(scope, logLevel, f.Writer)
+ return newJSONLeveledLoggerForScope(scope, logLevel, f.writer)
}
diff --git a/json_logger_test.go b/json_logger_test.go
index a44cd8e..ad102ed 100644
--- a/json_logger_test.go
+++ b/json_logger_test.go
@@ -6,6 +6,8 @@ package logging
import (
"bytes"
"encoding/json"
+ "fmt"
+ "io"
"log/slog"
"os"
"strings"
@@ -15,7 +17,7 @@ import (
)
func TestJSONLoggerLevels(t *testing.T) {
- logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
+ logger := newJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -61,7 +63,7 @@ func TestJSONLoggerLevels(t *testing.T) {
}
func TestJSONLoggerFormatting(t *testing.T) {
- logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
+ logger := newJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -76,7 +78,7 @@ func TestJSONLoggerFormatting(t *testing.T) {
}
func TestJSONLoggerLevelFiltering(t *testing.T) {
- logger := NewJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
+ logger := newJSONLeveledLoggerForScope("test", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -98,16 +100,16 @@ func TestJSONLoggerLevelFiltering(t *testing.T) {
}
func TestJSONLoggerFactory(t *testing.T) {
- factory := JSONLoggerFactory{
- Writer: os.Stderr,
- DefaultLogLevel: LogLevelWarn,
- ScopeLevels: map[string]LogLevel{
+ factory := jsonLoggerFactory{
+ writer: os.Stderr,
+ defaultLogLevel: LogLevelWarn,
+ scopeLevels: map[string]LogLevel{
"foo": LogLevelDebug,
},
}
logger := factory.NewLogger("baz")
- bazLogger, ok := logger.(*JSONLeveledLogger)
+ bazLogger, ok := logger.(*jsonLeveledLogger)
assert.True(t, ok, "Invalid logger type")
// Test that baz logger respects WARN level
@@ -117,7 +119,7 @@ func TestJSONLoggerFactory(t *testing.T) {
assert.Equal(t, 0, outBuf.Len(), "Debug message should not be logged at WARN level")
logger = factory.NewLogger("foo")
- fooLogger, ok := logger.(*JSONLeveledLogger)
+ fooLogger, ok := logger.(*jsonLeveledLogger)
assert.True(t, ok, "Invalid logger type")
// Test that foo logger respects DEBUG level
@@ -128,6 +130,12 @@ func TestJSONLoggerFactory(t *testing.T) {
assert.True(t, strings.Contains(output, "debug message"), "Debug message should be logged at DEBUG level")
}
+func TestNewJSONLoggerFactoryReturnsPrivateType(t *testing.T) {
+ factory := NewJSONLoggerFactory()
+
+ assert.Equal(t, "*logging.jsonLoggerFactory", fmt.Sprintf("%T", factory))
+}
+
func TestNewJSONLoggerFactory(t *testing.T) {
factory := NewJSONLoggerFactory()
@@ -138,22 +146,22 @@ func TestNewJSONLoggerFactory(t *testing.T) {
debugLevel := factory.NewLogger("DEBUG")
traceLevel := factory.NewLogger("TRACE")
- disabledLogger, ok := disabled.(*JSONLeveledLogger)
+ disabledLogger, ok := disabled.(*jsonLeveledLogger)
assert.True(t, ok, "Missing disabled logger")
- errorLogger, ok := errorLevel.(*JSONLeveledLogger)
+ errorLogger, ok := errorLevel.(*jsonLeveledLogger)
assert.True(t, ok, "Missing error logger")
- _, ok = warnLevel.(*JSONLeveledLogger)
+ _, ok = warnLevel.(*jsonLeveledLogger)
assert.True(t, ok, "Missing warn logger")
- _, ok = infoLevel.(*JSONLeveledLogger)
+ _, ok = infoLevel.(*jsonLeveledLogger)
assert.True(t, ok, "Missing info logger")
- _, ok = debugLevel.(*JSONLeveledLogger)
+ _, ok = debugLevel.(*jsonLeveledLogger)
assert.True(t, ok, "Missing debug logger")
- _, ok = traceLevel.(*JSONLeveledLogger)
+ _, ok = traceLevel.(*jsonLeveledLogger)
assert.True(t, ok, "Missing trace logger")
// Test that all loggers are properly configured
@@ -169,8 +177,42 @@ func TestNewJSONLoggerFactory(t *testing.T) {
assert.True(t, strings.Contains(output, "error message"), "Error logger should log error messages")
}
+func TestNewJSONLoggerFactoryOptions(t *testing.T) {
+ var outBuf bytes.Buffer
+
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory(
+ WithJSONWriter(&outBuf),
+ WithJSONDefaultLevel(LogLevelDebug),
+ WithJSONScopeLevels(map[string]LogLevel{"CustomScope": LogLevelTrace}),
+ ))
+
+ assert.Equal(t, LogLevelDebug, factory.defaultLogLevel)
+ assert.Equal(t, LogLevelTrace, factory.scopeLevels["customscope"])
+
+ logger := factory.NewLogger("customscope")
+ logger.Debug("configured logger output")
+
+ assert.Contains(t, outBuf.String(), "configured logger output")
+}
+
+func TestJSONLoggerFactorySupportsWithOutputInterface(t *testing.T) {
+ factory := NewJSONLoggerFactory()
+ logger := factory.NewLogger("interface-scope")
+
+ withOutput, ok := logger.(interface {
+ WithOutput(io.Writer) LeveledLogger
+ })
+ assert.True(t, ok, "Logger should allow WithOutput without concrete type")
+
+ var outBuf bytes.Buffer
+ withOutput.WithOutput(&outBuf)
+
+ logger.Error("interface error")
+ assert.Contains(t, outBuf.String(), "interface error")
+}
+
func TestJSONLoggerTraceOutput(t *testing.T) {
- logger := NewJSONLeveledLoggerForScope("trace-scope", LogLevelTrace, os.Stderr)
+ logger := newJSONLeveledLoggerForScope("trace-scope", LogLevelTrace, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -195,7 +237,7 @@ func TestJSONLoggerTraceOutput(t *testing.T) {
}
func TestJSONLoggerStructuredOutput(t *testing.T) {
- logger := NewJSONLeveledLoggerForScope("test-scope", LogLevelInfo, os.Stderr)
+ logger := newJSONLeveledLoggerForScope("test-scope", LogLevelInfo, os.Stderr)
var outBuf bytes.Buffer
logger.WithOutput(&outBuf)
@@ -220,12 +262,12 @@ func TestJSONLoggerStructuredOutput(t *testing.T) {
}
func TestJSONLeveledLogger_logf_IncludesAdditionalArgs(t *testing.T) {
- factory := NewJSONLoggerFactory()
- factory.Writer = os.Stderr
- factory.DefaultLogLevel = LogLevelTrace
+ factory := newJSONLoggerFactory()
+ factory.writer = os.Stderr
+ factory.defaultLogLevel = LogLevelTrace
l := factory.NewLogger("test-scope")
- jl, ok := l.(*JSONLeveledLogger)
+ jl, ok := l.(*jsonLeveledLogger)
assert.True(t, ok, "Invalid logger type")
var outBuf bytes.Buffer
@@ -267,6 +309,15 @@ func clearLogEnv(t *testing.T) {
}
}
+func unwrapJSONFactory(t *testing.T, factory LoggerFactory) *jsonLoggerFactory {
+ t.Helper()
+
+ jf, ok := factory.(*jsonLoggerFactory)
+ assert.True(t, ok, "Factory should be jsonLoggerFactory")
+
+ return jf
+}
+
func TestNewJSONLoggerFactory_AllSetsDefaultToMaxLevel(t *testing.T) {
clearLogEnv(t)
@@ -274,10 +325,10 @@ func TestNewJSONLoggerFactory_AllSetsDefaultToMaxLevel(t *testing.T) {
t.Setenv("PION_LOG_DEBUG", "ALL")
t.Setenv("PION_LOG_TRACE", "all")
- factory := NewJSONLoggerFactory()
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory())
- assert.Equal(t, LogLevelTrace, factory.DefaultLogLevel)
- assert.Equal(t, 0, len(factory.ScopeLevels))
+ assert.Equal(t, LogLevelTrace, factory.defaultLogLevel)
+ assert.Equal(t, 0, len(factory.scopeLevels))
}
func TestNewJSONLoggerFactory_AllDoesNotLowerDefaultLevel(t *testing.T) {
@@ -285,8 +336,8 @@ func TestNewJSONLoggerFactory_AllDoesNotLowerDefaultLevel(t *testing.T) {
t.Setenv("PION_LOG_DISABLE", "all")
- factory := NewJSONLoggerFactory()
- assert.Equal(t, LogLevelError, factory.DefaultLogLevel)
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory())
+ assert.Equal(t, LogLevelError, factory.defaultLogLevel)
}
func TestNewJSONLoggerFactory_ScopesAreSplitAndLowercased(t *testing.T) {
@@ -294,12 +345,12 @@ func TestNewJSONLoggerFactory_ScopesAreSplitAndLowercased(t *testing.T) {
t.Setenv("PION_LOG_DEBUG", "Foo,BAR")
- factory := NewJSONLoggerFactory()
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory())
- assert.Equal(t, LogLevelError, factory.DefaultLogLevel)
+ assert.Equal(t, LogLevelError, factory.defaultLogLevel)
- assert.Equal(t, LogLevelDebug, factory.ScopeLevels["foo"])
- assert.Equal(t, LogLevelDebug, factory.ScopeLevels["bar"])
+ assert.Equal(t, LogLevelDebug, factory.scopeLevels["foo"])
+ assert.Equal(t, LogLevelDebug, factory.scopeLevels["bar"])
}
func TestNewJSONLoggerFactory_AllAndScopedInteract(t *testing.T) {
@@ -309,13 +360,13 @@ func TestNewJSONLoggerFactory_AllAndScopedInteract(t *testing.T) {
t.Setenv("PION_LOG_DEBUG", "foo")
- factory := NewJSONLoggerFactory()
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory())
- assert.Equal(t, LogLevelWarn, factory.DefaultLogLevel)
- assert.Equal(t, LogLevelDebug, factory.ScopeLevels["foo"])
+ assert.Equal(t, LogLevelWarn, factory.defaultLogLevel)
+ assert.Equal(t, LogLevelDebug, factory.scopeLevels["foo"])
- foo := factory.NewLogger("foo").(*JSONLeveledLogger) //nolint:forcetypeassert
- bar := factory.NewLogger("bar").(*JSONLeveledLogger) //nolint:forcetypeassert
+ foo := factory.NewLogger("foo").(*jsonLeveledLogger) //nolint:forcetypeassert
+ bar := factory.NewLogger("bar").(*jsonLeveledLogger) //nolint:forcetypeassert
assert.Equal(t, LogLevelDebug, foo.level.Get(), "scope override should win")
assert.Equal(t, LogLevelWarn, bar.level.Get(), "default should apply when no scope override")
@@ -327,6 +378,6 @@ func TestNewJSONLoggerFactory_Fallback(t *testing.T) {
t.Setenv("PION_LOG_INFO", "")
t.Setenv("PIONS_LOG_INFO", "all")
- factory := NewJSONLoggerFactory()
- assert.Equal(t, LogLevelInfo, factory.DefaultLogLevel)
+ factory := unwrapJSONFactory(t, NewJSONLoggerFactory())
+ assert.Equal(t, LogLevelInfo, factory.defaultLogLevel)
}