feat(otel): add OpenTelemetry support

This commit is contained in:
Julien Neuhart
2026-03-27 16:28:45 +01:00
parent 08088c15f4
commit 4e9f63004d
86 changed files with 4396 additions and 1283 deletions

View File

@@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"time"
@@ -13,16 +14,15 @@ import (
"github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page"
"github.com/chromedp/chromedp"
"go.uber.org/zap"
)
func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOptions) chromedp.ActionFunc {
func printToPdfActionFunc(logger *slog.Logger, outputPath string, options PdfOptions) chromedp.ActionFunc {
return func(ctx context.Context) error {
paperHeight := options.PaperHeight
pageRanges := options.PageRanges
if options.SinglePage {
logger.Debug("single page PDF")
logger.DebugContext(ctx, "single page PDF")
_, _, _, _, _, cssContentSize, err := page.GetLayoutMetrics().Do(ctx)
if err != nil {
@@ -56,11 +56,11 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
options.FooterTemplate != DefaultPdfOptions().FooterTemplate
if !hasCustomHeaderFooter {
logger.Debug("no custom header nor footer")
logger.DebugContext(ctx, "no custom header nor footer")
printToPdf = printToPdf.WithDisplayHeaderFooter(false)
} else {
logger.Debug("with custom header and/or footer")
logger.DebugContext(ctx, "with custom header and/or footer")
printToPdf = printToPdf.
WithDisplayHeaderFooter(true).
@@ -68,7 +68,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
WithFooterTemplate(options.FooterTemplate)
}
logger.Debug(fmt.Sprintf("print to PDF with: %+v", printToPdf))
logger.DebugContext(ctx, fmt.Sprintf("print to PDF with: %+v", printToPdf))
_, stream, err := printToPdf.Do(ctx)
if err != nil {
@@ -86,7 +86,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
defer func() {
err = reader.Close()
if err != nil {
logger.Error(fmt.Sprintf("close reader: %s", err))
logger.ErrorContext(ctx, fmt.Sprintf("close reader: %s", err))
}
}()
@@ -98,7 +98,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
defer func() {
err = file.Close()
if err != nil {
logger.Error(fmt.Sprintf("close output path: %s", err))
logger.ErrorContext(ctx, fmt.Sprintf("close output path: %s", err))
}
}()
@@ -113,7 +113,7 @@ func printToPdfActionFunc(logger *zap.Logger, outputPath string, options PdfOpti
}
}
func captureScreenshotActionFunc(logger *zap.Logger, outputPath string, options ScreenshotOptions) chromedp.ActionFunc {
func captureScreenshotActionFunc(logger *slog.Logger, outputPath string, options ScreenshotOptions) chromedp.ActionFunc {
return func(ctx context.Context) error {
captureScreenshot := page.CaptureScreenshot().
WithCaptureBeyondViewport(true).
@@ -134,7 +134,7 @@ func captureScreenshotActionFunc(logger *zap.Logger, outputPath string, options
WithQuality(int64(options.Quality))
}
logger.Debug(fmt.Sprintf("capture screenshot with: %+v", captureScreenshot))
logger.DebugContext(ctx, fmt.Sprintf("capture screenshot with: %+v", captureScreenshot))
buffer, err := captureScreenshot.Do(ctx)
if err != nil {
@@ -149,7 +149,7 @@ func captureScreenshotActionFunc(logger *zap.Logger, outputPath string, options
defer func() {
err = file.Close()
if err != nil {
logger.Error(fmt.Sprintf("close output path: %s", err))
logger.ErrorContext(ctx, fmt.Sprintf("close output path: %s", err))
}
}()
@@ -162,9 +162,9 @@ func captureScreenshotActionFunc(logger *zap.Logger, outputPath string, options
}
}
func setDeviceMetricsOverride(logger *zap.Logger, width, height int) chromedp.ActionFunc {
func setDeviceMetricsOverride(logger *slog.Logger, width, height int) chromedp.ActionFunc {
return func(ctx context.Context) error {
logger.Debug("set device metrics override")
logger.DebugContext(ctx, "set device metrics override")
err := emulation.SetDeviceMetricsOverride(int64(width), int64(height), 1.0, false).Do(ctx)
if err == nil {
@@ -175,15 +175,15 @@ func setDeviceMetricsOverride(logger *zap.Logger, width, height int) chromedp.Ac
}
}
func clearCacheActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc {
func clearCacheActionFunc(logger *slog.Logger, clear bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
// See https://github.com/gotenberg/gotenberg/issues/753.
if !clear {
logger.Debug("cache not cleared")
logger.DebugContext(ctx, "cache not cleared")
return nil
}
logger.Debug("clear cache")
logger.DebugContext(ctx, "clear cache")
err := network.ClearBrowserCache().Do(ctx)
if err == nil {
@@ -194,15 +194,15 @@ func clearCacheActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc {
}
}
func clearCookiesActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc {
func clearCookiesActionFunc(logger *slog.Logger, clear bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
// See https://github.com/gotenberg/gotenberg/issues/753.
if !clear {
logger.Debug("cookies not cleared")
logger.DebugContext(ctx, "cookies not cleared")
return nil
}
logger.Debug("clear cookies")
logger.DebugContext(ctx, "clear cookies")
err := network.ClearBrowserCookies().Do(ctx)
if err == nil {
@@ -213,15 +213,15 @@ func clearCookiesActionFunc(logger *zap.Logger, clear bool) chromedp.ActionFunc
}
}
func disableJavaScriptActionFunc(logger *zap.Logger, disable bool) chromedp.ActionFunc {
func disableJavaScriptActionFunc(logger *slog.Logger, disable bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
// See https://github.com/gotenberg/gotenberg/issues/175.
if !disable {
logger.Debug("JavaScript not disabled")
logger.DebugContext(ctx, "JavaScript not disabled")
return nil
}
logger.Debug("disable JavaScript")
logger.DebugContext(ctx, "disable JavaScript")
err := emulation.SetScriptExecutionDisabled(true).Do(ctx)
if err == nil {
@@ -232,10 +232,10 @@ func disableJavaScriptActionFunc(logger *zap.Logger, disable bool) chromedp.Acti
}
}
func setCookiesActionFunc(logger *zap.Logger, cookies []Cookie) chromedp.ActionFunc {
func setCookiesActionFunc(logger *slog.Logger, cookies []Cookie) chromedp.ActionFunc {
return func(ctx context.Context) error {
if len(cookies) == 0 {
logger.Debug("no cookies to set")
logger.DebugContext(ctx, "no cookies to set")
return nil
}
@@ -274,21 +274,21 @@ func setCookiesActionFunc(logger *zap.Logger, cookies []Cookie) chromedp.ActionF
return fmt.Errorf("set cookie %s: %w", cookiePretty(cookieParams), err)
}
logger.Debug(fmt.Sprintf("set cookie %s", cookiePretty(cookieParams)))
logger.DebugContext(ctx, fmt.Sprintf("set cookie %s", cookiePretty(cookieParams)))
}
return nil
}
}
func userAgentOverride(logger *zap.Logger, userAgent string) chromedp.ActionFunc {
func userAgentOverride(logger *slog.Logger, userAgent string) chromedp.ActionFunc {
return func(ctx context.Context) error {
if len(userAgent) == 0 {
logger.Debug("no user agent override")
logger.DebugContext(ctx, "no user agent override")
return nil
}
logger.Debug(fmt.Sprintf("user agent override: %s", userAgent))
logger.DebugContext(ctx, fmt.Sprintf("user agent override: %s", userAgent))
err := emulation.SetUserAgentOverride(userAgent).Do(ctx)
if err == nil {
return nil
@@ -303,32 +303,32 @@ func userAgentOverride(logger *zap.Logger, userAgent string) chromedp.ActionFunc
// network.SetExtraHTTPHeaders set the headers for ALL requests from the page.
// See https://github.com/gotenberg/gotenberg/issues/1011.
//
//func extraHttpHeadersActionFunc(logger *zap.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
// return func(ctx context.Context) error {
// if len(extraHttpHeaders) == 0 {
// logger.Debug("no extra HTTP headers")
// return nil
// }
// func extraHttpHeadersActionFunc(logger *slog.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
// return func(ctx context.Context) error {
// if len(extraHttpHeaders) == 0 {
// logger.DebugContext(ctx,"no extra HTTP headers")
// return nil
// }
//
// logger.Debug(fmt.Sprintf("extra HTTP headers: %+v", extraHttpHeaders))
// logger.DebugContext(ctx,fmt.Sprintf("extra HTTP headers: %+v", extraHttpHeaders))
//
// headers := make(network.Headers, len(extraHttpHeaders))
// for key, value := range extraHttpHeaders {
// headers[key] = value
// }
// headers := make(network.Headers, len(extraHttpHeaders))
// for key, value := range extraHttpHeaders {
// headers[key] = value
// }
//
// err := network.SetExtraHTTPHeaders(headers).Do(ctx)
// if err == nil {
// return nil
// }
// err := network.SetExtraHTTPHeaders(headers).Do(ctx)
// if err == nil {
// return nil
// }
//
// return fmt.Errorf("set extra HTTP headers: %w", err)
// }
//}
// return fmt.Errorf("set extra HTTP headers: %w", err)
// }
// }
func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent, skipNetworkAlmostIdleEvent bool) chromedp.ActionFunc {
func navigateActionFunc(logger *slog.Logger, url string, skipNetworkIdleEvent, skipNetworkAlmostIdleEvent bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
logger.Debug(fmt.Sprintf("navigate to '%s'", url))
logger.DebugContext(ctx, fmt.Sprintf("navigate to '%s'", url))
_, _, _, _, err := page.Navigate(url).Do(ctx)
if err != nil {
@@ -344,13 +344,13 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent, sk
if !skipNetworkIdleEvent {
waitFunc = append(waitFunc, waitForEventNetworkIdle(ctx, logger))
} else {
logger.Debug("skipping network idle event")
logger.DebugContext(ctx, "skipping network idle event")
}
if !skipNetworkAlmostIdleEvent {
waitFunc = append(waitFunc, waitForEventNetworkAlmostIdle(ctx, logger))
} else {
logger.Debug("skipping network almost idle event")
logger.DebugContext(ctx, "skipping network almost idle event")
}
err = runBatch(
@@ -366,11 +366,11 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent, sk
}
}
func hideDefaultWhiteBackgroundActionFunc(logger *zap.Logger, omitBackground, printBackground bool) chromedp.ActionFunc {
func hideDefaultWhiteBackgroundActionFunc(logger *slog.Logger, omitBackground, printBackground bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
// See https://github.com/gotenberg/gotenberg/issues/226.
if !omitBackground {
logger.Debug("default white background not hidden")
logger.DebugContext(ctx, "default white background not hidden")
return nil
}
@@ -379,7 +379,7 @@ func hideDefaultWhiteBackgroundActionFunc(logger *zap.Logger, omitBackground, pr
return fmt.Errorf("validate omit background: %w", ErrOmitBackgroundWithoutPrintBackground)
}
logger.Debug("hide default white background")
logger.DebugContext(ctx, "hide default white background")
err := emulation.SetDefaultBackgroundColorOverride().WithColor(
&cdp.RGBA{
@@ -397,7 +397,7 @@ func hideDefaultWhiteBackgroundActionFunc(logger *zap.Logger, omitBackground, pr
}
}
func forceExactColorsActionFunc(logger *zap.Logger, printBackground bool) chromedp.ActionFunc {
func forceExactColorsActionFunc(logger *slog.Logger, printBackground bool) chromedp.ActionFunc {
return func(ctx context.Context) error {
css := "html { -webkit-print-color-adjust: exact !important; }"
if !printBackground {
@@ -405,7 +405,7 @@ func forceExactColorsActionFunc(logger *zap.Logger, printBackground bool) chrome
// print of the background, whatever the printToPDF args.
// See https://github.com/gotenberg/gotenberg/issues/1154.
additionalCss := "html, body { background: none !important; }"
logger.Debug(fmt.Sprintf("inject %s as printBackground is %t", additionalCss, printBackground))
logger.DebugContext(ctx, fmt.Sprintf("inject %s as printBackground is %t", additionalCss, printBackground))
css += additionalCss
}
@@ -429,10 +429,10 @@ func forceExactColorsActionFunc(logger *zap.Logger, printBackground bool) chrome
}
}
func emulateMediaTypeActionFunc(logger *zap.Logger, mediaType string, mediaFeatures []EmulatedMediaFeature) chromedp.ActionFunc {
func emulateMediaTypeActionFunc(logger *slog.Logger, mediaType string, mediaFeatures []EmulatedMediaFeature) chromedp.ActionFunc {
return func(ctx context.Context) error {
if mediaType == "" && len(mediaFeatures) == 0 {
logger.Debug("no emulated media type or features")
logger.DebugContext(ctx, "no emulated media type or features")
return nil
}
@@ -443,12 +443,12 @@ func emulateMediaTypeActionFunc(logger *zap.Logger, mediaType string, mediaFeatu
emulatedMedia := emulation.SetEmulatedMedia()
if mediaType != "" {
logger.Debug(fmt.Sprintf("emulate media type '%s'", mediaType))
logger.DebugContext(ctx, fmt.Sprintf("emulate media type '%s'", mediaType))
emulatedMedia = emulatedMedia.WithMedia(mediaType)
}
if len(mediaFeatures) > 0 {
logger.Debug(fmt.Sprintf("emulate media features %+v", mediaFeatures))
logger.DebugContext(ctx, fmt.Sprintf("emulate media features %+v", mediaFeatures))
features := make([]*emulation.MediaFeature, len(mediaFeatures))
for i, f := range mediaFeatures {
@@ -470,21 +470,21 @@ func emulateMediaTypeActionFunc(logger *zap.Logger, mediaType string, mediaFeatu
}
}
func waitDelayBeforePrintActionFunc(logger *zap.Logger, disableJavaScript bool, delay time.Duration) chromedp.ActionFunc {
func waitDelayBeforePrintActionFunc(logger *slog.Logger, disableJavaScript bool, delay time.Duration) chromedp.ActionFunc {
return func(ctx context.Context) error {
if disableJavaScript {
logger.Debug("JavaScript disabled, skipping wait delay")
logger.DebugContext(ctx, "JavaScript disabled, skipping wait delay")
return nil
}
if delay <= 0 {
logger.Debug("no wait delay")
logger.DebugContext(ctx, "no wait delay")
return nil
}
// We wait for a given amount of time so that JavaScript
// scripts have a chance to finish before printing the page.
logger.Debug(fmt.Sprintf("wait '%s' before print", delay))
logger.DebugContext(ctx, fmt.Sprintf("wait '%s' before print", delay))
select {
case <-ctx.Done():
@@ -495,21 +495,21 @@ func waitDelayBeforePrintActionFunc(logger *zap.Logger, disableJavaScript bool,
}
}
func waitForExpressionBeforePrintActionFunc(logger *zap.Logger, disableJavaScript bool, expression string) chromedp.ActionFunc {
func waitForExpressionBeforePrintActionFunc(logger *slog.Logger, disableJavaScript bool, expression string) chromedp.ActionFunc {
return func(ctx context.Context) error {
if disableJavaScript {
logger.Debug("JavaScript disabled, skipping wait expression")
logger.DebugContext(ctx, "JavaScript disabled, skipping wait expression")
return nil
}
if expression == "" {
logger.Debug("no wait expression")
logger.DebugContext(ctx, "no wait expression")
return nil
}
// We wait until the evaluation of the expression is true or
// until the context is done.
logger.Debug(fmt.Sprintf("wait until '%s' is true before print", expression))
logger.DebugContext(ctx, fmt.Sprintf("wait until '%s' is true before print", expression))
ticker := time.NewTicker(time.Duration(100) * time.Millisecond)
for {
@@ -537,14 +537,14 @@ func waitForExpressionBeforePrintActionFunc(logger *zap.Logger, disableJavaScrip
}
}
func waitForSelectorVisibleBeforePrintActionFunc(logger *zap.Logger, selector string) chromedp.ActionFunc {
func waitForSelectorVisibleBeforePrintActionFunc(logger *slog.Logger, selector string) chromedp.ActionFunc {
return func(ctx context.Context) error {
if selector == "" {
logger.Debug("no wait selector")
logger.DebugContext(ctx, "no wait selector")
return nil
}
logger.Debug(fmt.Sprintf("wait until '%s' is visible before print", selector))
logger.DebugContext(ctx, fmt.Sprintf("wait until '%s' is visible before print", selector))
err := chromedp.WaitVisible(selector, chromedp.ByQuery, chromedp.RetryInterval(time.Duration(100)*time.Millisecond)).Do(ctx)
if err != nil {
return fmt.Errorf("wait visible: %v: %w", err, ErrInvalidSelectorQuery)