From 525102b991213f1f0c64afd26c0f986fe146ec24 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Tue, 2 Jun 2026 19:07:26 +0200 Subject: [PATCH] refactor(libreoffice): classify Pdf errors and set span error.type --- pkg/modules/libreoffice/api/api.go | 32 +++++++++++------- pkg/modules/libreoffice/api/errortype_test.go | 33 +++++++++++++++++++ 2 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 pkg/modules/libreoffice/api/errortype_test.go diff --git a/pkg/modules/libreoffice/api/api.go b/pkg/modules/libreoffice/api/api.go index 5b0f7765..427805fc 100644 --- a/pkg/modules/libreoffice/api/api.go +++ b/pkg/modules/libreoffice/api/api.go @@ -635,20 +635,11 @@ func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPat reason := "" if err != nil { - switch { - case errors.Is(err, context.DeadlineExceeded): + status = "error" + if errors.Is(err, context.DeadlineExceeded) { status = "timeout" - reason = "timeout" - case errors.Is(err, context.Canceled): - status = "error" - reason = "context_cancelled" - case errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded) || errors.Is(err, gotenberg.ErrProcessAlreadyRestarting): - status = "error" - reason = "libreoffice_unavailable" - default: - status = "error" - reason = "unknown" } + reason = libreofficeErrorType(err) } // Record metrics. @@ -657,6 +648,7 @@ func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPat if reason != "" { a.errsCounter.Add(ctx, 1, metric.WithAttributes(attribute.String("reason", reason))) + gotenberg.SpanErrorType(span, reason) } if !conversionStart.IsZero() { @@ -688,6 +680,22 @@ func (a *Api) Pdf(ctx context.Context, logger *slog.Logger, inputPath, outputPat return fmt.Errorf("supervisor run task: %w", err) } +// libreofficeErrorType maps a conversion error to LibreOffice's bounded reason +// value, reused as the span error.type. Generic failures fall back to +// [gotenberg.ClassifyError]. +func libreofficeErrorType(err error) string { + switch { + case errors.Is(err, ErrInvalidPdfFormats): + return gotenberg.ErrorTypeInvalidInput + case errors.Is(err, ErrUnoException), errors.Is(err, ErrRuntimeException): + return "libreoffice_exception" + case errors.Is(err, gotenberg.ErrMaximumQueueSizeExceeded), errors.Is(err, gotenberg.ErrProcessAlreadyRestarting): + return "libreoffice_unavailable" + default: + return gotenberg.ClassifyError(err) + } +} + // Extensions returns the file extensions available for conversions. // FIXME: don't care, take all on the route level? func (a *Api) Extensions() []string { diff --git a/pkg/modules/libreoffice/api/errortype_test.go b/pkg/modules/libreoffice/api/errortype_test.go new file mode 100644 index 00000000..79a0fe60 --- /dev/null +++ b/pkg/modules/libreoffice/api/errortype_test.go @@ -0,0 +1,33 @@ +package api + +import ( + "context" + "errors" + "testing" + + "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" +) + +func TestLibreofficeErrorType(t *testing.T) { + for _, tc := range []struct { + name string + err error + want string + }{ + {"deadline", context.DeadlineExceeded, "timeout"}, + {"canceled", context.Canceled, "context_cancelled"}, + {"invalid pdf formats", ErrInvalidPdfFormats, "invalid_input"}, + {"uno exception", ErrUnoException, "libreoffice_exception"}, + {"runtime exception", ErrRuntimeException, "libreoffice_exception"}, + {"queue size exceeded", gotenberg.ErrMaximumQueueSizeExceeded, "libreoffice_unavailable"}, + {"process restarting", gotenberg.ErrProcessAlreadyRestarting, "libreoffice_unavailable"}, + {"core dumped", ErrCoreDumped, "unknown"}, + {"unknown", errors.New("boom"), "unknown"}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := libreofficeErrorType(tc.err); got != tc.want { + t.Errorf("libreofficeErrorType(%v) = %q, want %q", tc.err, got, tc.want) + } + }) + } +}