feat(telemetry): record backing-binary versions on spans, captured at build time

This commit is contained in:
Julien Neuhart
2026-06-07 14:50:36 +02:00
parent 2050b4ae6b
commit d4c20c6b39
15 changed files with 606 additions and 242 deletions

View File

@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
@@ -101,6 +102,9 @@ type Chromium struct {
supervisor gotenberg.ProcessSupervisor
engine gotenberg.PdfEngine
version string
versionOnce sync.Once
reqsCounter metric.Int64Counter
errsCounter metric.Int64Counter
conversionDurationCounter metric.Float64Histogram
@@ -717,19 +721,46 @@ func (mod *Chromium) Stop(ctx context.Context) error {
// Debug returns additional debug data.
func (mod *Chromium) Debug() map[string]any {
debug := make(map[string]any)
return map[string]any{"version": mod.detectVersion()}
}
cmd := exec.Command(mod.args.binPath, "--version") //nolint:gosec
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// detectVersion resolves the Chromium version once, preferring the value
// captured at image build time so it never spawns Chromium at runtime. It falls
// back to running chromium --version for local or non-Docker builds.
func (mod *Chromium) detectVersion() string {
mod.versionOnce.Do(func() {
if v, ok := gotenberg.BuildVersion("chromium"); ok {
mod.version = v
return
}
output, err := cmd.Output()
if err != nil {
debug["version"] = err.Error()
return debug
cmd := exec.Command(mod.args.binPath, "--version") //nolint:gosec
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
output, err := cmd.Output()
if err != nil {
mod.version = err.Error()
return
}
mod.version = strings.TrimSpace(string(output))
})
return mod.version
}
// spanAttrs returns the client-span attributes for a Chromium invocation: the
// server address and the Chromium version, plus any extra attributes. The
// version rides on every conversion span so a trace records which Chromium
// rendered the document.
func (mod *Chromium) spanAttrs(extra ...attribute.KeyValue) []attribute.KeyValue {
attrs := make([]attribute.KeyValue, 0, 2+len(extra))
attrs = append(attrs, semconv.ServerAddress(mod.args.binPath))
if v := mod.detectVersion(); v != "" {
attrs = append(attrs, attribute.String("gotenberg.chromium.version", v))
}
debug["version"] = strings.TrimSpace(string(output))
return debug
return append(attrs, extra...)
}
// Metrics returns the metrics.
@@ -828,7 +859,7 @@ func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPa
ctx, span := gotenberg.Tracer().Start(ctx, "chromium.Pdf",
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(semconv.ServerAddress(mod.args.binPath)),
trace.WithAttributes(mod.spanAttrs()...),
)
defer span.End()
@@ -909,7 +940,7 @@ func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPa
func (mod *Chromium) Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
ctx, span := gotenberg.Tracer().Start(ctx, "chromium.Screenshot",
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(semconv.ServerAddress(mod.args.binPath)),
trace.WithAttributes(mod.spanAttrs()...),
)
defer span.End()

View File

@@ -0,0 +1,37 @@
package chromium
import (
"os"
"path/filepath"
"testing"
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
)
func TestChromiumDetectVersion(t *testing.T) {
t.Run("prefers the build-time version without executing Chromium", func(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "chromium"), []byte("Chromium 146.0.7680.80\n"), 0o600); err != nil {
t.Fatalf("write version file: %v", err)
}
t.Setenv(gotenberg.BuildVersionsDirPathEnvVar, dir)
// A bogus binPath would error if executed, so a correct result proves
// the build-time file is used instead of running Chromium.
mod := &Chromium{args: browserArguments{binPath: "/nonexistent/chromium"}}
if got := mod.Debug()["version"]; got != "Chromium 146.0.7680.80" {
t.Errorf("Debug()[version] = %v, want the build-time value", got)
}
})
t.Run("falls back to executing Chromium when no build-time version", func(t *testing.T) {
t.Setenv(gotenberg.BuildVersionsDirPathEnvVar, "")
// With no build-time file and a bogus binPath, the exec fallback runs
// and records its error rather than a build-time value.
mod := &Chromium{args: browserArguments{binPath: "/nonexistent/chromium"}}
if got := mod.Debug()["version"]; got == "Chromium 146.0.7680.80" {
t.Errorf("Debug()[version] = %v, expected the exec fallback", got)
}
})
}