mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 04:32:15 +01:00
feat(log): add log-std-level-case to control standard output level casing
This commit is contained in:
@@ -49,6 +49,7 @@ func Run() {
|
|||||||
fs.String("log-fields-prefix", "", "Prepend a specified prefix to each log field key")
|
fs.String("log-fields-prefix", "", "Prepend a specified prefix to each log field key")
|
||||||
fs.String("log-std-format", gotenberg.AutoLoggingFormat, "Set the log format for standard output")
|
fs.String("log-std-format", gotenberg.AutoLoggingFormat, "Set the log format for standard output")
|
||||||
fs.Bool("log-std-enable-gcp-fields", false, "Use GCP-compatible field names in log output")
|
fs.Bool("log-std-enable-gcp-fields", false, "Use GCP-compatible field names in log output")
|
||||||
|
fs.String("log-std-level-case", gotenberg.LowerLevelCase, "Set the case of the level field in the standard output, either lower or upper")
|
||||||
|
|
||||||
// Deprecated logging flags.
|
// Deprecated logging flags.
|
||||||
fs.String("log-format", gotenberg.AutoLoggingFormat, "Set the log format")
|
fs.String("log-format", gotenberg.AutoLoggingFormat, "Set the log format")
|
||||||
@@ -123,6 +124,7 @@ func Run() {
|
|||||||
LogFieldsPrefix: parsedFlags.MustString("log-fields-prefix"),
|
LogFieldsPrefix: parsedFlags.MustString("log-fields-prefix"),
|
||||||
LogStdFormat: parsedFlags.MustDeprecatedString("log-format", "log-std-format"),
|
LogStdFormat: parsedFlags.MustDeprecatedString("log-format", "log-std-format"),
|
||||||
LogStdEnableGcpFields: parsedFlags.MustDeprecatedBool("log-enable-gcp-fields", "log-std-enable-gcp-fields"),
|
LogStdEnableGcpFields: parsedFlags.MustDeprecatedBool("log-enable-gcp-fields", "log-std-enable-gcp-fields"),
|
||||||
|
LogStdLevelCase: parsedFlags.MustString("log-std-level-case"),
|
||||||
}
|
}
|
||||||
// LogLevel uses its own flag, not the format flag.
|
// LogLevel uses its own flag, not the format flag.
|
||||||
telemetryCfg.LogLevel = parsedFlags.MustString("log-level")
|
telemetryCfg.LogLevel = parsedFlags.MustString("log-level")
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ func (h traceContextHandler) WithGroup(name string) slog.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewStdHandler returns a [slog.Handler] instance for the standard output.
|
// NewStdHandler returns a [slog.Handler] instance for the standard output.
|
||||||
func NewStdHandler(level slog.Level, format string, fieldsPrefix string, enableGcpFields bool) (slog.Handler, error) {
|
// upperLevelCase is the value of the level-case setting that keeps the level
|
||||||
|
// field uppercase in the standard output. It mirrors gotenberg.UpperLevelCase,
|
||||||
|
// duplicated here because the internal log package cannot import gotenberg.
|
||||||
|
const upperLevelCase = "upper"
|
||||||
|
|
||||||
|
func NewStdHandler(level slog.Level, format string, fieldsPrefix string, enableGcpFields bool, levelCase string) (slog.Handler, error) {
|
||||||
// #nosec: G115
|
// #nosec: G115
|
||||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
|
|
||||||
@@ -82,7 +87,11 @@ func NewStdHandler(level slog.Level, format string, fieldsPrefix string, enableG
|
|||||||
a.Key = "severity"
|
a.Key = "severity"
|
||||||
a.Value = slog.StringValue(gcpSeverity(l))
|
a.Value = slog.StringValue(gcpSeverity(l))
|
||||||
default:
|
default:
|
||||||
a.Value = slog.StringValue(strings.ToLower(l.String()))
|
if levelCase == upperLevelCase {
|
||||||
|
a.Value = slog.StringValue(l.String())
|
||||||
|
} else {
|
||||||
|
a.Value = slog.StringValue(strings.ToLower(l.String()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
53
pkg/gotenberg/internal/log/stdhandler_test.go
Normal file
53
pkg/gotenberg/internal/log/stdhandler_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewStdHandler_LevelCase(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
levelCase string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"lower is the default behavior", "lower", "info"},
|
||||||
|
{"upper keeps slog casing", "upper", "INFO"},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
original := os.Stderr
|
||||||
|
reader, writer, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create pipe: %v", err)
|
||||||
|
}
|
||||||
|
os.Stderr = writer
|
||||||
|
defer func() { os.Stderr = original }()
|
||||||
|
|
||||||
|
handler, err := NewStdHandler(slog.LevelInfo, "json", "", false, tc.levelCase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.New(handler).Info("hello")
|
||||||
|
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatalf("close writer: %v", err)
|
||||||
|
}
|
||||||
|
out, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var record map[string]any
|
||||||
|
if err := json.Unmarshal(out, &record); err != nil {
|
||||||
|
t.Fatalf("parse log line %q: %v", out, err)
|
||||||
|
}
|
||||||
|
if record["level"] != tc.want {
|
||||||
|
t.Errorf("level = %v, want %v", record["level"], tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,11 @@ const (
|
|||||||
DebugLoggingLevel = "debug"
|
DebugLoggingLevel = "debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LowerLevelCase = "lower"
|
||||||
|
UpperLevelCase = "upper"
|
||||||
|
)
|
||||||
|
|
||||||
// TelemetryConfig gathers the configuration data for Gotenberg's telemetry.
|
// TelemetryConfig gathers the configuration data for Gotenberg's telemetry.
|
||||||
type TelemetryConfig struct {
|
type TelemetryConfig struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
@@ -39,6 +44,7 @@ type TelemetryConfig struct {
|
|||||||
LogFieldsPrefix string
|
LogFieldsPrefix string
|
||||||
LogStdFormat string
|
LogStdFormat string
|
||||||
LogStdEnableGcpFields bool
|
LogStdEnableGcpFields bool
|
||||||
|
LogStdLevelCase string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg TelemetryConfig) slogLevel() slog.Level {
|
func (cfg TelemetryConfig) slogLevel() slog.Level {
|
||||||
@@ -86,6 +92,16 @@ func (cfg TelemetryConfig) Validate() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch cfg.LogStdLevelCase {
|
||||||
|
case LowerLevelCase, UpperLevelCase:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
err = errors.Join(
|
||||||
|
err,
|
||||||
|
fmt.Errorf("standard log level case must be either %s or %s", LowerLevelCase, UpperLevelCase),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +109,7 @@ func (cfg TelemetryConfig) Validate() error {
|
|||||||
func StartTelemetry(cfg TelemetryConfig) (shutdown func(context.Context) error, err error) {
|
func StartTelemetry(cfg TelemetryConfig) (shutdown func(context.Context) error, err error) {
|
||||||
var handlers []slog.Handler
|
var handlers []slog.Handler
|
||||||
|
|
||||||
stdHandler, err := log.NewStdHandler(cfg.slogLevel(), cfg.LogStdFormat, cfg.LogFieldsPrefix, cfg.LogStdEnableGcpFields)
|
stdHandler, err := log.NewStdHandler(cfg.slogLevel(), cfg.LogStdFormat, cfg.LogFieldsPrefix, cfg.LogStdEnableGcpFields, cfg.LogStdLevelCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get standard logger handler: %w", err)
|
return nil, fmt.Errorf("get standard logger handler: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user