Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ LOG_LEVEL=info
LOG_FIELDS_PREFIX=
LOG_STD_FORMAT=auto
LOG_STD_ENABLE_GCP_FIELDS=false
LOG_STD_LEVEL_CASE=lower
PDFENGINES_DISABLE_ROUTES=false
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk
Expand Down
2 changes: 2 additions & 0 deletions cmd/gotenberg.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func Run() {
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.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.
fs.String("log-format", gotenberg.AutoLoggingFormat, "Set the log format")
Expand Down Expand Up @@ -123,6 +124,7 @@ func Run() {
LogFieldsPrefix: parsedFlags.MustString("log-fields-prefix"),
LogStdFormat: parsedFlags.MustDeprecatedString("log-format", "log-std-format"),
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.
telemetryCfg.LogLevel = parsedFlags.MustString("log-level")
Expand Down
1 change: 1 addition & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ services:
- "--log-fields-prefix=${LOG_FIELDS_PREFIX}"
- "--log-std-format=${LOG_STD_FORMAT}"
- "--log-std-enable-gcp-fields=${LOG_STD_ENABLE_GCP_FIELDS}"
- "--log-std-level-case=${LOG_STD_LEVEL_CASE}"
- "--pdfengines-merge-engines=${PDFENGINES_MERGE_ENGINES}"
- "--pdfengines-split-engines=${PDFENGINES_SPLIT_ENGINES}"
- "--pdfengines-flatten-engines=${PDFENGINES_FLATTEN_ENGINES}"
Expand Down
13 changes: 11 additions & 2 deletions pkg/gotenberg/internal/log/stdhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ func (h traceContextHandler) WithGroup(name string) slog.Handler {
}

// 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
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))

Expand Down Expand Up @@ -82,7 +87,11 @@ func NewStdHandler(level slog.Level, format string, fieldsPrefix string, enableG
a.Key = "severity"
a.Value = slog.StringValue(gcpSeverity(l))
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()))
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions pkg/gotenberg/internal/log/stdhandler_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
18 changes: 17 additions & 1 deletion pkg/gotenberg/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const (
DebugLoggingLevel = "debug"
)

const (
LowerLevelCase = "lower"
UpperLevelCase = "upper"
)

// TelemetryConfig gathers the configuration data for Gotenberg's telemetry.
type TelemetryConfig struct {
ServiceName string
Expand All @@ -39,6 +44,7 @@ type TelemetryConfig struct {
LogFieldsPrefix string
LogStdFormat string
LogStdEnableGcpFields bool
LogStdLevelCase string
}

func (cfg TelemetryConfig) slogLevel() slog.Level {
Expand Down Expand Up @@ -86,14 +92,24 @@ 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
}

// StartTelemetry starts the telemetry utilities.
func StartTelemetry(cfg TelemetryConfig) (shutdown func(context.Context) error, err error) {
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 {
return nil, fmt.Errorf("get standard logger handler: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions test/integration/features/webhook.feature
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ Feature: Webhook
Given I have a webhook server
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s):
| files | testdata/page_1.pdf | file |
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
| traceparent | 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 | header |
Then the response status code should be 204
When I wait for the asynchronous request to the webhook
Expand Down
Loading