mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-07-02 00:17:40 +08:00
258 lines
7.4 KiB
Go
258 lines
7.4 KiB
Go
package gotenbergcmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
flag "github.com/spf13/pflag"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
|
)
|
|
|
|
// See https://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Gotenberg.
|
|
// Credits: https://github.com/labstack/echo/blob/v4.3.0/echo.go#L240.
|
|
const banner = `
|
|
_____ __ __
|
|
/ ___/__ / /____ ___ / / ___ _______ _
|
|
/ (_ / _ \/ __/ -_) _ \/ _ \/ -_) __/ _ '/
|
|
\___/\___/\__/\__/_//_/_.__/\__/_/ \_, /
|
|
/___/
|
|
|
|
A Docker-based API for converting documents to PDF.
|
|
Version: %s
|
|
-------------------------------------------------------
|
|
`
|
|
|
|
// Version is the... version of the Gotenberg application. We set it at the
|
|
// build stage of the Docker image.
|
|
var Version = "snapshot"
|
|
|
|
// Run starts the Gotenberg application. Call this in the main of your program.
|
|
func Run() {
|
|
gotenberg.Version = Version
|
|
|
|
// Create the root FlagSet and adds the modules flags to it.
|
|
fs := flag.NewFlagSet("gotenberg", flag.ExitOnError)
|
|
fs.Bool("gotenberg-hide-banner", false, "Hide the banner")
|
|
fs.Duration("gotenberg-graceful-shutdown-duration", time.Duration(30)*time.Second, "Set the graceful shutdown duration")
|
|
fs.Bool("gotenberg-build-debug-data", true, "Set if build data is needed")
|
|
|
|
// Logging & telemetry flags.
|
|
fs.String("log-level", gotenberg.InfoLoggingLevel, "Set the log level")
|
|
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")
|
|
fs.Bool("log-enable-gcp-fields", false, "Use GCP-compatible field names")
|
|
|
|
if err := errors.Join(
|
|
fs.MarkDeprecated("log-format", "use --log-std-format instead"),
|
|
fs.MarkDeprecated("log-enable-gcp-fields", "use --log-std-enable-gcp-fields instead"),
|
|
); err != nil {
|
|
fmt.Printf("[FATAL] mark deprecated flags: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
descriptors := gotenberg.GetModuleDescriptors()
|
|
var modsInfo strings.Builder
|
|
for _, desc := range descriptors {
|
|
fs.AddFlagSet(desc.FlagSet)
|
|
modsInfo.WriteString(desc.ID + " ")
|
|
}
|
|
|
|
// Parse the flags.
|
|
err := fs.Parse(os.Args[1:])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Override their values if the corresponding environment variables are
|
|
// set.
|
|
fs.VisitAll(func(f *flag.Flag) {
|
|
envName := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
|
|
val, ok := os.LookupEnv(envName)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
sliceVal, ok := f.Value.(flag.SliceValue)
|
|
if ok {
|
|
// We don't want to append the values (default pflag behavior).
|
|
items := strings.Split(val, ",")
|
|
err = sliceVal.Replace(items)
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] invalid overriding value '%s' from %s: %v\n", val, envName, err)
|
|
os.Exit(1)
|
|
}
|
|
f.Changed = true
|
|
return
|
|
}
|
|
|
|
err = fs.Set(f.Name, val)
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] invalid overriding value '%s' from %s: %v\n", val, envName, err)
|
|
os.Exit(1)
|
|
}
|
|
})
|
|
|
|
// Create a wrapper around our flags.
|
|
parsedFlags := gotenberg.ParsedFlags{FlagSet: fs}
|
|
hideBanner := parsedFlags.MustBool("gotenberg-hide-banner")
|
|
gracefulShutdownDuration := parsedFlags.MustDuration("gotenberg-graceful-shutdown-duration")
|
|
|
|
// Initialize telemetry (logging + OTEL).
|
|
serviceName := os.Getenv("OTEL_SERVICE_NAME")
|
|
if serviceName == "" {
|
|
serviceName = "gotenberg"
|
|
}
|
|
|
|
telemetryCfg := gotenberg.TelemetryConfig{
|
|
ServiceName: serviceName,
|
|
ServiceVersion: Version,
|
|
LogLevel: parsedFlags.MustDeprecatedString("log-format", "log-std-format"),
|
|
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")
|
|
|
|
err = telemetryCfg.Validate()
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] invalid telemetry config: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
shutdownTelemetry, err := gotenberg.StartTelemetry(telemetryCfg)
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] start telemetry: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !hideBanner {
|
|
fmt.Printf(banner, Version)
|
|
}
|
|
fmt.Printf("[SYSTEM] modules: %s\n", modsInfo.String())
|
|
|
|
ctx := gotenberg.NewContext(parsedFlags, descriptors)
|
|
|
|
// Start application modules.
|
|
apps, err := ctx.Modules(new(gotenberg.App))
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
for _, a := range apps {
|
|
go func(app gotenberg.App) {
|
|
id := app.(gotenberg.Module).Descriptor().ID
|
|
err = app.Start()
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] starting %s: %s\n", id, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
startupMessage := app.StartupMessage()
|
|
if startupMessage == "" {
|
|
fmt.Printf("[SYSTEM] %s: application started\n", id)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("[SYSTEM] %s: %s\n", id, startupMessage)
|
|
}(a.(gotenberg.App))
|
|
}
|
|
|
|
// Get modules that want to print system messages.
|
|
sysLoggers, err := ctx.Modules(new(gotenberg.SystemLogger))
|
|
if err != nil {
|
|
fmt.Printf("[FATAL] %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
for _, l := range sysLoggers {
|
|
go func(logger gotenberg.SystemLogger) {
|
|
id := logger.(gotenberg.Module).Descriptor().ID
|
|
|
|
for _, message := range logger.SystemMessages() {
|
|
fmt.Printf("[SYSTEM] %s: %s\n", id, message)
|
|
}
|
|
}(l.(gotenberg.SystemLogger))
|
|
}
|
|
|
|
if parsedFlags.MustBool("gotenberg-build-debug-data") {
|
|
// Build the debug data.
|
|
gotenberg.BuildDebug(ctx)
|
|
}
|
|
|
|
quit := make(chan os.Signal, 1)
|
|
|
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM (Kubernetes).
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Block until we receive our signal.
|
|
<-quit
|
|
|
|
gracefulShutdownCtx, cancel := context.WithTimeout(context.Background(), gracefulShutdownDuration)
|
|
defer cancel()
|
|
|
|
forceQuit := make(chan os.Signal, 1)
|
|
signal.Notify(forceQuit, syscall.SIGINT)
|
|
|
|
go func() {
|
|
// In case of force quit, cancel the context.
|
|
<-forceQuit
|
|
cancel()
|
|
}()
|
|
|
|
fmt.Printf("[SYSTEM] graceful shutdown of %s\n", gracefulShutdownDuration)
|
|
|
|
eg, _ := errgroup.WithContext(gracefulShutdownCtx)
|
|
|
|
for _, a := range apps {
|
|
eg.Go(func(app gotenberg.App) func() error {
|
|
return func() error {
|
|
id := app.(gotenberg.Module).Descriptor().ID
|
|
|
|
err = app.Stop(gracefulShutdownCtx)
|
|
if errors.Is(err, gotenberg.ErrCancelGracefulShutdownContext) {
|
|
cancel()
|
|
} else if err != nil {
|
|
return fmt.Errorf("stopping %s: %w", id, err)
|
|
}
|
|
|
|
fmt.Printf("[SYSTEM] %s: application stopped\n", id)
|
|
return nil
|
|
}
|
|
}(a.(gotenberg.App)))
|
|
}
|
|
|
|
err = eg.Wait()
|
|
if err != nil {
|
|
cancel()
|
|
fmt.Printf("[FATAL] %v\n", err)
|
|
os.Exit(1) //nolint:gocritic // defers are already called explicitly above
|
|
}
|
|
|
|
// Shutdown telemetry (flush spans, metrics, logs).
|
|
err = shutdownTelemetry(gracefulShutdownCtx)
|
|
if err != nil {
|
|
cancel()
|
|
fmt.Printf("[FATAL] %v\n", err)
|
|
os.Exit(1) //nolint:gocritic // defers are already called explicitly above
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|