mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-15 20:02:15 +01:00
refactor(pdfengines): embeds => attachments
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -18,15 +19,18 @@ import (
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/shirou/gopsutil/v4/process"
|
||||
"go.uber.org/zap"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.39.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
gotenberg.Process
|
||||
pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
||||
screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error
|
||||
screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
}
|
||||
|
||||
type browserArguments struct {
|
||||
@@ -72,12 +76,15 @@ func newChromiumBrowser(arguments browserArguments) browser {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) Start(logger *zap.Logger) error {
|
||||
func (b *chromiumBrowser) Start(logger *slog.Logger) error {
|
||||
if b.isStarted.Load() {
|
||||
return errors.New("browser is already started")
|
||||
}
|
||||
|
||||
debug := &debugLogger{logger: logger}
|
||||
debug := &debugLogger{
|
||||
ctx: b.initialCtx,
|
||||
logger: logger,
|
||||
}
|
||||
b.userProfileDirPath = b.fs.NewDirPath()
|
||||
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1293.
|
||||
@@ -164,7 +171,7 @@ func (b *chromiumBrowser) Start(logger *zap.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
func (b *chromiumBrowser) Stop(logger *slog.Logger) error {
|
||||
if !b.isStarted.Load() {
|
||||
// No big deal? Like calling cancel twice.
|
||||
return nil
|
||||
@@ -181,7 +188,7 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
// Clean up stuck processes.
|
||||
ps, err := process.Processes()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("list processes: %v", err))
|
||||
logger.ErrorContext(context.Background(), fmt.Sprintf("list processes: %v", err))
|
||||
} else {
|
||||
for _, p := range ps {
|
||||
func() {
|
||||
@@ -199,9 +206,9 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
|
||||
err = p.KillWithContext(killCtx)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("kill process: %v", err))
|
||||
logger.ErrorContext(killCtx, fmt.Sprintf("kill process: %v", err))
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("Chromium process %d killed", p.Pid))
|
||||
logger.DebugContext(killCtx, fmt.Sprintf("Chromium process %d killed", p.Pid))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -215,15 +222,15 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
|
||||
err = os.RemoveAll(userProfileDirPath)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("remove Chromium's user profile directory: %s", err))
|
||||
logger.ErrorContext(context.Background(), fmt.Sprintf("remove Chromium's user profile directory: %s", err))
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
|
||||
logger.DebugContext(context.Background(), fmt.Sprintf("'%s' Chromium's user profile directory removed", userProfileDirPath))
|
||||
}
|
||||
|
||||
// Also, remove Chromium-specific files in the temporary directory.
|
||||
err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{".org.chromium.Chromium", ".com.google.Chrome"}, expirationTime)
|
||||
err = gotenberg.GarbageCollect(context.Background(), logger, os.TempDir(), []string{".org.chromium.Chromium", ".com.google.Chrome"}, expirationTime)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
logger.ErrorContext(context.Background(), err.Error())
|
||||
}
|
||||
}()
|
||||
}(copyUserProfileDirPath, expirationTime)
|
||||
@@ -239,7 +246,7 @@ func (b *chromiumBrowser) Stop(logger *zap.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) Healthy(logger *zap.Logger) bool {
|
||||
func (b *chromiumBrowser) Healthy(logger *slog.Logger) bool {
|
||||
// Good to know: the supervisor does not call this method if no first start
|
||||
// or if the process is restarting.
|
||||
|
||||
@@ -266,14 +273,14 @@ func (b *chromiumBrowser) Healthy(logger *zap.Logger) bool {
|
||||
return err
|
||||
}))
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("browser health check failed: %s", err))
|
||||
logger.ErrorContext(ctx, fmt.Sprintf("browser health check failed: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
func (b *chromiumBrowser) pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
||||
// Note: no error wrapping because it leaks on errors we want to display to
|
||||
// the end user.
|
||||
return b.do(ctx, logger, url, options.Options, chromedp.Tasks{
|
||||
@@ -299,7 +306,7 @@ func (b *chromiumBrowser) pdf(ctx context.Context, logger *zap.Logger, url, outp
|
||||
})
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
func (b *chromiumBrowser) screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
// Note: no error wrapping because it leaks on errors we want to display to
|
||||
// the end user.
|
||||
return b.do(ctx, logger, url, options.Options, chromedp.Tasks{
|
||||
@@ -326,7 +333,7 @@ func (b *chromiumBrowser) screenshot(ctx context.Context, logger *zap.Logger, ur
|
||||
})
|
||||
}
|
||||
|
||||
func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string, options Options, tasks chromedp.Tasks) error {
|
||||
func (b *chromiumBrowser) do(ctx context.Context, logger *slog.Logger, url string, options Options, tasks chromedp.Tasks) error {
|
||||
if !b.isStarted.Load() {
|
||||
return errors.New("browser not started, cannot handle tasks")
|
||||
}
|
||||
@@ -412,7 +419,26 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
|
||||
resourceLoadingFailedMu: &resourceLoadingFailedMu,
|
||||
})
|
||||
|
||||
err = chromedp.Run(taskCtx, tasks...)
|
||||
clientCtx, clientSpan := gotenberg.Tracer().Start(taskCtx, "cdp.execute",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(
|
||||
semconv.ServerAddress("127.0.0.1"),
|
||||
semconv.ServicePeerName("chromium"),
|
||||
semconv.RPCSystemNameKey.String("cdp"),
|
||||
// Legacy attribute for older APMs (Datadog, Jaeger) to draw the
|
||||
// dependency graph.
|
||||
attribute.String("peer.service", "chromium"),
|
||||
),
|
||||
)
|
||||
|
||||
err = chromedp.Run(clientCtx, tasks...)
|
||||
if err != nil {
|
||||
clientSpan.RecordError(err)
|
||||
clientSpan.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
|
||||
clientSpan.End()
|
||||
|
||||
if err != nil {
|
||||
errMessage := err.Error()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -14,7 +15,8 @@ import (
|
||||
"github.com/chromedp/cdproto/network"
|
||||
"github.com/dlclark/regexp2"
|
||||
flag "github.com/spf13/pflag"
|
||||
"go.uber.org/zap"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||
@@ -91,7 +93,7 @@ type Chromium struct {
|
||||
maxConcurrency int64
|
||||
args browserArguments
|
||||
|
||||
logger *zap.Logger
|
||||
logger *slog.Logger
|
||||
browser browser
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
engine gotenberg.PdfEngine
|
||||
@@ -268,7 +270,7 @@ type PdfOptions struct {
|
||||
PreferCssPageSize bool
|
||||
|
||||
// GenerateDocumentOutline defines whether the document outline should be
|
||||
// embedded into the PDF.
|
||||
// attached into the PDF.
|
||||
GenerateDocumentOutline bool
|
||||
|
||||
// GenerateTaggedPdf defines whether to generate tagged (accessible)
|
||||
@@ -389,8 +391,8 @@ type ExtraHttpHeader struct {
|
||||
|
||||
// Api helps to interact with Chromium for converting HTML documents to PDF.
|
||||
type Api interface {
|
||||
Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
||||
Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
Pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error
|
||||
Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
}
|
||||
|
||||
// Provider is a module interface that exposes a method for creating an [Api]
|
||||
@@ -428,13 +430,6 @@ func (mod *Chromium) Descriptor() gotenberg.ModuleDescriptor {
|
||||
fs.Bool("chromium-disable-javascript", false, "Disable JavaScript")
|
||||
fs.Bool("chromium-disable-routes", false, "Disable the routes")
|
||||
|
||||
// Deprecated flags.
|
||||
fs.Bool("chromium-incognito", false, "Start Chromium with incognito mode")
|
||||
err := fs.MarkDeprecated("chromium-incognito", "this flag is ignored as it provides no benefits")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
New: func() gotenberg.Module { return new(Chromium) },
|
||||
@@ -477,15 +472,7 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
||||
}
|
||||
|
||||
// Logger.
|
||||
loggerProvider, err := ctx.Module(new(gotenberg.LoggerProvider))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get logger provider: %w", err)
|
||||
}
|
||||
logger, err := loggerProvider.(gotenberg.LoggerProvider).Logger(mod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get logger: %w", err)
|
||||
}
|
||||
mod.logger = logger.Named("browser")
|
||||
mod.logger = gotenberg.Logger(mod).With(slog.String("logger", "browser"))
|
||||
|
||||
// Process.
|
||||
mod.browser = newChromiumBrowser(mod.args)
|
||||
@@ -502,6 +489,36 @@ func (mod *Chromium) Provision(ctx *gotenberg.Context) error {
|
||||
}
|
||||
mod.engine = engine
|
||||
|
||||
// OpenTelemetry.
|
||||
meter := gotenberg.Meter()
|
||||
_, err = meter.Int64ObservableCounter(
|
||||
"chromium.process.restarts.total",
|
||||
metric.WithDescription("Current number of Chromium restarts."),
|
||||
metric.WithUnit("{restart}"),
|
||||
metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error {
|
||||
val := mod.supervisor.RestartsCount()
|
||||
o.Observe(val)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create process restarts observable counter: %w", err)
|
||||
}
|
||||
|
||||
_, err = meter.Int64ObservableGauge(
|
||||
"chromium.requests.queue_size",
|
||||
metric.WithDescription("Current number of Chromium conversion requests waiting to be treated."),
|
||||
metric.WithUnit("{request}"),
|
||||
metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error {
|
||||
val := mod.supervisor.ReqQueueSize()
|
||||
o.Observe(val)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create requests queue size observable gauge: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -552,7 +569,7 @@ func (mod *Chromium) StartupMessage() string {
|
||||
func (mod *Chromium) Stop(ctx context.Context) error {
|
||||
// Block until the context is done so that another module may gracefully
|
||||
// stop before we do a shutdown.
|
||||
mod.logger.Debug("wait for the end of grace duration")
|
||||
mod.logger.DebugContext(ctx, "wait for the end of grace duration")
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
@@ -581,26 +598,6 @@ func (mod *Chromium) Debug() map[string]any {
|
||||
return debug
|
||||
}
|
||||
|
||||
// Metrics returns the metrics.
|
||||
func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) {
|
||||
return []gotenberg.Metric{
|
||||
{
|
||||
Name: "chromium_requests_queue_size",
|
||||
Description: "Current number of Chromium conversion requests waiting to be treated.",
|
||||
Read: func() float64 {
|
||||
return float64(mod.supervisor.ReqQueueSize())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "chromium_restarts_count",
|
||||
Description: "Current number of Chromium restarts.",
|
||||
Read: func() float64 {
|
||||
return float64(mod.supervisor.RestartsCount())
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Checks adds a health check that verifies if Chromium is healthy.
|
||||
func (mod *Chromium) Checks() ([]health.CheckerOption, error) {
|
||||
return []health.CheckerOption{
|
||||
@@ -668,32 +665,49 @@ func (mod *Chromium) Routes() ([]api.Route, error) {
|
||||
}
|
||||
|
||||
// Pdf converts a URL to PDF.
|
||||
func (mod *Chromium) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
func (mod *Chromium) Pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
||||
// Note: no error wrapping because it leaks on errors we want to display to
|
||||
// the end user.
|
||||
return mod.supervisor.Run(ctx, logger, func() error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "Chromium.Pdf")
|
||||
defer span.End()
|
||||
|
||||
err := mod.supervisor.Run(ctx, logger, func() error {
|
||||
return mod.browser.pdf(ctx, logger, url, outputPath, options)
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mod *Chromium) Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
func (mod *Chromium) Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
// Note: no error wrapping because it leaks on errors we want to display to
|
||||
// the end user.
|
||||
return mod.supervisor.Run(ctx, logger, func() error {
|
||||
ctx, span := gotenberg.Tracer().Start(ctx, "Chromium.Screenshot")
|
||||
defer span.End()
|
||||
|
||||
err := mod.supervisor.Run(ctx, logger, func() error {
|
||||
return mod.browser.screenshot(ctx, logger, url, outputPath, options)
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ gotenberg.Module = (*Chromium)(nil)
|
||||
_ gotenberg.Provisioner = (*Chromium)(nil)
|
||||
_ gotenberg.Validator = (*Chromium)(nil)
|
||||
_ gotenberg.App = (*Chromium)(nil)
|
||||
_ gotenberg.Debuggable = (*Chromium)(nil)
|
||||
_ gotenberg.MetricsProvider = (*Chromium)(nil)
|
||||
_ api.HealthChecker = (*Chromium)(nil)
|
||||
_ api.Router = (*Chromium)(nil)
|
||||
_ Api = (*Chromium)(nil)
|
||||
_ Provider = (*Chromium)(nil)
|
||||
_ gotenberg.Module = (*Chromium)(nil)
|
||||
_ gotenberg.Provisioner = (*Chromium)(nil)
|
||||
_ gotenberg.Validator = (*Chromium)(nil)
|
||||
_ gotenberg.App = (*Chromium)(nil)
|
||||
_ gotenberg.Debuggable = (*Chromium)(nil)
|
||||
_ api.HealthChecker = (*Chromium)(nil)
|
||||
_ api.Router = (*Chromium)(nil)
|
||||
_ Api = (*Chromium)(nil)
|
||||
_ Provider = (*Chromium)(nil)
|
||||
)
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// debugLogger is wrapper around a [zap.Logger] which is used for debugging
|
||||
// debugLogger is wrapper around a [slog.Logger] which is used for debugging
|
||||
// Chromium.
|
||||
type debugLogger struct {
|
||||
logger *zap.Logger
|
||||
ctx context.Context
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// Write logs the bytes in a debug message.
|
||||
func (debug *debugLogger) Write(p []byte) (n int, err error) {
|
||||
debug.logger.Debug(string(p))
|
||||
debug.logger.DebugContext(debug.ctx, string(p))
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Printf logs a debug message.
|
||||
func (debug *debugLogger) Printf(format string, v ...any) {
|
||||
debug.logger.Debug(fmt.Sprintf(format, v...))
|
||||
debug.logger.DebugContext(debug.ctx, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
|
||||
@@ -3,6 +3,7 @@ package chromium
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"github.com/chromedp/chromedp"
|
||||
"github.com/dlclark/regexp2"
|
||||
"go.uber.org/multierr"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
@@ -32,29 +32,29 @@ type eventRequestPausedOptions struct {
|
||||
// allowed or not. It also set the extra HTTP headers, if any.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1011.
|
||||
// TODO: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setBlockedURLs (experimental for now).
|
||||
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, options eventRequestPausedOptions) {
|
||||
func listenForEventRequestPaused(ctx context.Context, logger *slog.Logger, options eventRequestPausedOptions) {
|
||||
if len(options.extraHttpHeaders) == 0 {
|
||||
logger.Debug("no extra HTTP headers")
|
||||
logger.DebugContext(ctx, "no extra HTTP headers")
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("extra HTTP headers: %+v", options.extraHttpHeaders))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("extra HTTP headers: %+v", options.extraHttpHeaders))
|
||||
}
|
||||
|
||||
chromedp.ListenTarget(ctx, func(ev any) {
|
||||
switch e := ev.(type) {
|
||||
case *fetch.EventRequestPaused:
|
||||
go func() {
|
||||
logger.Debug(fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("event EventRequestPaused fired for '%s'", e.Request.URL))
|
||||
allow := true
|
||||
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
logger.Error("context has no deadline, cannot filter URL")
|
||||
logger.ErrorContext(ctx, "context has no deadline, cannot filter URL")
|
||||
return
|
||||
}
|
||||
|
||||
err := gotenberg.FilterDeadline(options.allowList, options.denyList, e.Request.URL, deadline)
|
||||
if err != nil {
|
||||
logger.Warn(err.Error())
|
||||
logger.WarnContext(ctx, err.Error())
|
||||
allow = false
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option
|
||||
req := fetch.FailRequest(e.RequestID, network.ErrorReasonAccessDenied)
|
||||
err = req.Do(executorCtx)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("fail request: %s", err))
|
||||
logger.ErrorContext(ctx, fmt.Sprintf("fail request: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -81,25 +81,25 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option
|
||||
for _, header := range options.extraHttpHeaders {
|
||||
if header.Scope == nil {
|
||||
// Non-scoped header.
|
||||
logger.Debug(fmt.Sprintf("extra HTTP header '%s' will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("extra HTTP header '%s' will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
extraHttpHeadersToSet = append(extraHttpHeadersToSet, header)
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := header.Scope.MatchString(e.Request.URL)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("fail to match extra HTTP header '%s' scope with URL '%s': %s", header.Name, e.Request.URL, err))
|
||||
logger.ErrorContext(ctx, fmt.Sprintf("fail to match extra HTTP header '%s' scope with URL '%s': %s", header.Name, e.Request.URL, err))
|
||||
} else if ok {
|
||||
logger.Debug(fmt.Sprintf("extra HTTP header '%s' (scoped) will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("extra HTTP header '%s' (scoped) will be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
extraHttpHeadersToSet = append(extraHttpHeadersToSet, header)
|
||||
} else {
|
||||
logger.Debug(fmt.Sprintf("scoped extra HTTP header '%s' (scoped) will not be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("scoped extra HTTP header '%s' (scoped) will not be set for request URL '%s'", header.Name, e.Request.URL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(extraHttpHeadersToSet) > 0 {
|
||||
logger.Debug(fmt.Sprintf("setting extra HTTP headers for request URL '%s': %+v", e.Request.URL, extraHttpHeadersToSet))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("setting extra HTTP headers for request URL '%s': %+v", e.Request.URL, extraHttpHeadersToSet))
|
||||
|
||||
originalHeaders := e.Request.Headers
|
||||
headers := make(map[string]string)
|
||||
@@ -109,7 +109,7 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option
|
||||
if ok {
|
||||
headers[key] = strValue
|
||||
} else {
|
||||
logger.Error(fmt.Sprintf("ignoring header '%s' for URL '%s' since it cannot be cast to a string", key, e.Request.URL))
|
||||
logger.ErrorContext(ctx, fmt.Sprintf("ignoring header '%s' for URL '%s' since it cannot be cast to a string", key, e.Request.URL))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, option
|
||||
|
||||
err = req.Do(executorCtx)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("continue request: %s", err))
|
||||
logger.ErrorContext(ctx, fmt.Sprintf("continue request: %s", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -157,7 +157,7 @@ type eventResponseReceivedOptions struct {
|
||||
// https://github.com/gotenberg/gotenberg/issues/1021.
|
||||
func listenForEventResponseReceived(
|
||||
ctx context.Context,
|
||||
logger *zap.Logger,
|
||||
logger *slog.Logger,
|
||||
options eventResponseReceivedOptions,
|
||||
) {
|
||||
normalizedIgnoreDomains := normalizeDomains(options.ignoreResourceHttpStatusDomains)
|
||||
@@ -180,7 +180,7 @@ func listenForEventResponseReceived(
|
||||
switch ev := ev.(type) {
|
||||
case *network.EventResponseReceived:
|
||||
if ev.Response.URL == options.mainPageUrl {
|
||||
logger.Debug(fmt.Sprintf("event EventResponseReceived fired for main page: %+v", ev.Response))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("event EventResponseReceived fired for main page: %+v", ev.Response))
|
||||
|
||||
if slices.Contains(options.failOnHttpStatusCodes, ev.Response.Status) {
|
||||
options.invalidHttpStatusCodeMu.Lock()
|
||||
@@ -192,11 +192,11 @@ func listenForEventResponseReceived(
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf("event EventResponseReceived fired for a resource: %+v", ev.Response))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("event EventResponseReceived fired for a resource: %+v", ev.Response))
|
||||
|
||||
if slices.Contains(options.failOnResourceOnHttpStatusCode, ev.Response.Status) {
|
||||
if !shouldCheckResourceHttpStatusCode(ev.Response.URL, normalizedIgnoreDomains) {
|
||||
logger.Debug(fmt.Sprintf("skip resource HTTP status code check for '%s' due to domain filtering", ev.Response.URL))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("skip resource HTTP status code check for '%s' due to domain filtering", ev.Response.URL))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -298,11 +298,11 @@ type eventLoadingFailedOptions struct {
|
||||
// https://github.com/gotenberg/gotenberg/issues/913.
|
||||
// https://github.com/gotenberg/gotenberg/issues/959.
|
||||
// https://github.com/gotenberg/gotenberg/issues/1021.
|
||||
func listenForEventLoadingFailed(ctx context.Context, logger *zap.Logger, options eventLoadingFailedOptions) {
|
||||
func listenForEventLoadingFailed(ctx context.Context, logger *slog.Logger, options eventLoadingFailedOptions) {
|
||||
chromedp.ListenTarget(ctx, func(ev any) {
|
||||
switch ev := ev.(type) {
|
||||
case *network.EventLoadingFailed:
|
||||
logger.Debug(fmt.Sprintf("event EventLoadingFailed fired: %+v", ev.ErrorText))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("event EventLoadingFailed fired: %+v", ev.ErrorText))
|
||||
|
||||
// We are looking for common errors.
|
||||
// TODO: sufficient?
|
||||
@@ -321,14 +321,14 @@ func listenForEventLoadingFailed(ctx context.Context, logger *zap.Logger, option
|
||||
"net::ERR_HTTP2_PROTOCOL_ERROR",
|
||||
}
|
||||
if !slices.Contains(errors, ev.ErrorText) {
|
||||
logger.Debug(fmt.Sprintf("skip EventLoadingFailed: '%s' is not part of %+v", ev.ErrorText, errors))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("skip EventLoadingFailed: '%s' is not part of %+v", ev.ErrorText, errors))
|
||||
return
|
||||
}
|
||||
|
||||
if ev.Type == network.ResourceTypeDocument {
|
||||
// Supposition: except iframe, an event loading failed with a
|
||||
// resource type Document is about the main page.
|
||||
logger.Debug("event EventLoadingFailed fired for main page")
|
||||
logger.DebugContext(ctx, "event EventLoadingFailed fired for main page")
|
||||
|
||||
options.loadingFailedMu.Lock()
|
||||
defer options.loadingFailedMu.Unlock()
|
||||
@@ -338,7 +338,7 @@ func listenForEventLoadingFailed(ctx context.Context, logger *zap.Logger, option
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("event EventLoadingFailed fired for a resource")
|
||||
logger.DebugContext(ctx, "event EventLoadingFailed fired for a resource")
|
||||
|
||||
options.resourceLoadingFailedMu.Lock()
|
||||
defer options.resourceLoadingFailedMu.Unlock()
|
||||
@@ -354,11 +354,11 @@ func listenForEventLoadingFailed(ctx context.Context, logger *zap.Logger, option
|
||||
// listenForEventExceptionThrown listens for exceptions in the console and
|
||||
// appends those exceptions to the given error pointer.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/262.
|
||||
func listenForEventExceptionThrown(ctx context.Context, logger *zap.Logger, consoleExceptions *error, consoleExceptionsMu *sync.RWMutex) {
|
||||
func listenForEventExceptionThrown(ctx context.Context, logger *slog.Logger, consoleExceptions *error, consoleExceptionsMu *sync.RWMutex) {
|
||||
chromedp.ListenTarget(ctx, func(ev any) {
|
||||
switch ev := ev.(type) {
|
||||
case *runtime.EventExceptionThrown:
|
||||
logger.Debug(fmt.Sprintf("event EventExceptionThrown fired: %+v", ev.ExceptionDetails))
|
||||
logger.DebugContext(ctx, fmt.Sprintf("event EventExceptionThrown fired: %+v", ev.ExceptionDetails))
|
||||
|
||||
consoleExceptionsMu.Lock()
|
||||
defer consoleExceptionsMu.Unlock()
|
||||
@@ -370,7 +370,7 @@ func listenForEventExceptionThrown(ctx context.Context, logger *zap.Logger, cons
|
||||
|
||||
// waitForEventDomContentEventFired waits until the event DomContentEventFired
|
||||
// is fired or the context timeout.
|
||||
func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) func() error {
|
||||
func waitForEventDomContentEventFired(ctx context.Context, logger *slog.Logger) func() error {
|
||||
return func() error {
|
||||
ch := make(chan struct{})
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
@@ -384,7 +384,7 @@ func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) f
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
logger.Debug("event DomContentEventFired fired")
|
||||
logger.DebugContext(ctx, "event DomContentEventFired fired")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("wait for event DomContentEventFired: %w", ctx.Err())
|
||||
@@ -394,7 +394,7 @@ func waitForEventDomContentEventFired(ctx context.Context, logger *zap.Logger) f
|
||||
|
||||
// waitForEventLoadEventFired waits until the event LoadEventFired is fired or
|
||||
// the context timeout.
|
||||
func waitForEventLoadEventFired(ctx context.Context, logger *zap.Logger) func() error {
|
||||
func waitForEventLoadEventFired(ctx context.Context, logger *slog.Logger) func() error {
|
||||
return func() error {
|
||||
ch := make(chan struct{})
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
@@ -408,7 +408,7 @@ func waitForEventLoadEventFired(ctx context.Context, logger *zap.Logger) func()
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
logger.Debug("event LoadEventFired fired")
|
||||
logger.DebugContext(ctx, "event LoadEventFired fired")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("wait for event LoadEventFired: %w", ctx.Err())
|
||||
@@ -418,7 +418,7 @@ func waitForEventLoadEventFired(ctx context.Context, logger *zap.Logger) func()
|
||||
|
||||
// waitForEventNetworkIdle waits until the event networkIdle is fired or the
|
||||
// context timeout.
|
||||
func waitForEventNetworkIdle(ctx context.Context, logger *zap.Logger) func() error {
|
||||
func waitForEventNetworkIdle(ctx context.Context, logger *slog.Logger) func() error {
|
||||
return func() error {
|
||||
ch := make(chan struct{})
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
@@ -434,7 +434,7 @@ func waitForEventNetworkIdle(ctx context.Context, logger *zap.Logger) func() err
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
logger.Debug("event networkIdle fired")
|
||||
logger.DebugContext(ctx, "event networkIdle fired")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("wait for event networkIdle: %w", ctx.Err())
|
||||
@@ -444,7 +444,7 @@ func waitForEventNetworkIdle(ctx context.Context, logger *zap.Logger) func() err
|
||||
|
||||
// waitForEventLoadingFinished waits until the event LoadingFinished is fired
|
||||
// or the context timeout.
|
||||
func waitForEventLoadingFinished(ctx context.Context, logger *zap.Logger) func() error {
|
||||
func waitForEventLoadingFinished(ctx context.Context, logger *slog.Logger) func() error {
|
||||
return func() error {
|
||||
ch := make(chan struct{})
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
@@ -458,7 +458,7 @@ func waitForEventLoadingFinished(ctx context.Context, logger *zap.Logger) func()
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
logger.Debug("event LoadingFinished fired")
|
||||
logger.DebugContext(ctx, "event LoadingFinished fired")
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("wait for event LoadingFinished: %w", ctx.Err())
|
||||
|
||||
@@ -2,38 +2,37 @@ package chromium
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"log/slog"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
// ApiMock is a mock for the [Api] interface.
|
||||
type ApiMock struct {
|
||||
PdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
||||
ScreenshotMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
PdfMock func(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error
|
||||
ScreenshotMock func(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
}
|
||||
|
||||
func (api *ApiMock) Pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
func (api *ApiMock) Pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return api.PdfMock(ctx, logger, url, outputPath, options)
|
||||
}
|
||||
|
||||
func (api *ApiMock) Screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
func (api *ApiMock) Screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return api.ScreenshotMock(ctx, logger, url, outputPath, options)
|
||||
}
|
||||
|
||||
// browserMock is a mock for the [browser] interface.
|
||||
type browserMock struct {
|
||||
gotenberg.ProcessMock
|
||||
pdfMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error
|
||||
screenshotMock func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
pdfMock func(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error
|
||||
screenshotMock func(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error
|
||||
}
|
||||
|
||||
func (b *browserMock) pdf(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
func (b *browserMock) pdf(ctx context.Context, logger *slog.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return b.pdfMock(ctx, logger, url, outputPath, options)
|
||||
}
|
||||
|
||||
func (b *browserMock) screenshot(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
func (b *browserMock) screenshot(ctx context.Context, logger *slog.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return b.screenshotMock(ctx, logger, url, outputPath, options)
|
||||
}
|
||||
|
||||
|
||||
@@ -414,7 +414,7 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
attachmentsPaths := pdfengines.FormDataPdfAttachments(form)
|
||||
|
||||
var url string
|
||||
err := form.
|
||||
@@ -424,7 +424,7 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("validate form data: %w", err)
|
||||
}
|
||||
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, attachmentsPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert URL to PDF: %w", err)
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
attachmentsPaths := pdfengines.FormDataPdfAttachments(form)
|
||||
|
||||
var inputPath string
|
||||
err := form.
|
||||
@@ -488,7 +488,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("file://%s", inputPath)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, attachmentsPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert HTML to PDF: %w", err)
|
||||
}
|
||||
@@ -542,7 +542,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||
userPassword, ownerPassword := pdfengines.FormDataPdfEncrypt(form)
|
||||
embedPaths := pdfengines.FormDataPdfEmbeds(form)
|
||||
attachmentsPaths := pdfengines.FormDataPdfAttachments(form)
|
||||
|
||||
var (
|
||||
inputPath string
|
||||
@@ -562,7 +562,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
||||
}
|
||||
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, embedPaths)
|
||||
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata, userPassword, ownerPassword, attachmentsPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||
}
|
||||
@@ -686,7 +686,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
||||
return fmt.Sprintf("file://%s", inputPath), nil
|
||||
}
|
||||
|
||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]any, userPassword, ownerPassword string, embedPaths []string) error {
|
||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]any, userPassword, ownerPassword string, attachmentsPaths []string) error {
|
||||
outputPath := ctx.GeneratePath(".pdf")
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1130.
|
||||
filename := ctx.OutputFilename(outputPath)
|
||||
@@ -758,9 +758,9 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
||||
return fmt.Errorf("convert PDF(s): %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.EmbedFilesStub(ctx, engine, embedPaths, convertOutputPaths)
|
||||
err = pdfengines.AddAttachmentsStub(ctx, engine, attachmentsPaths, convertOutputPaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("embed files into PDFs: %w", err)
|
||||
return fmt.Errorf("add attachments into PDFs: %w", err)
|
||||
}
|
||||
|
||||
err = pdfengines.WriteMetadataStub(ctx, engine, metadata, convertOutputPaths)
|
||||
|
||||
@@ -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,14 +303,14 @@ 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 {
|
||||
//func extraHttpHeadersActionFunc(logger *slog.Logger, extraHttpHeaders map[string]string) chromedp.ActionFunc {
|
||||
// return func(ctx context.Context) error {
|
||||
// if len(extraHttpHeaders) == 0 {
|
||||
// logger.Debug("no extra HTTP headers")
|
||||
// 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 {
|
||||
@@ -326,9 +326,9 @@ func userAgentOverride(logger *zap.Logger, userAgent string) chromedp.ActionFunc
|
||||
// }
|
||||
//}
|
||||
|
||||
func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent bool) chromedp.ActionFunc {
|
||||
func navigateActionFunc(logger *slog.Logger, url string, skipNetworkIdleEvent 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,7 +344,7 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent boo
|
||||
if !skipNetworkIdleEvent {
|
||||
waitFunc = append(waitFunc, waitForEventNetworkIdle(ctx, logger))
|
||||
} else {
|
||||
logger.Debug("skipping network idle event")
|
||||
logger.DebugContext(ctx, "skipping network idle event")
|
||||
}
|
||||
|
||||
err = runBatch(
|
||||
@@ -360,11 +360,11 @@ func navigateActionFunc(logger *zap.Logger, url string, skipNetworkIdleEvent boo
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -373,7 +373,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{
|
||||
@@ -391,7 +391,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 {
|
||||
@@ -399,7 +399,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
|
||||
}
|
||||
|
||||
@@ -423,10 +423,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
|
||||
}
|
||||
|
||||
@@ -437,12 +437,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 {
|
||||
@@ -464,21 +464,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():
|
||||
@@ -489,21 +489,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 {
|
||||
@@ -531,14 +531,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)
|
||||
|
||||
Reference in New Issue
Block a user