Ctrl K
ComunidadGuías…

cmd/gotenberg.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gotenbergcmd

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"os/signal"
	"syscall"
	"time"

	flag "github.com/spf13/pflag"
	"golang.org/x/sync/errgroup"

	"github.com/gotenberg/gotenberg/v9/pkg/gotenberg"
)

// telemetryShutdownTimeout bounds the final flush of spans, metrics, and logs.
const telemetryShutdownTimeout = 5 * time.Second

// 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() {
	startedAt := time.Now()
	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 ASCII art banner above the startup summary")
	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 format of the logs Gotenberg writes to standard error")
	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 logs Gotenberg writes to standard error, either lower or upper")

	descriptors := gotenberg.GetModuleDescriptors()
	for _, desc := range descriptors {
		fs.AddFlagSet(desc.FlagSet)
	}

	// Parse the flags.
	err := fs.Parse(os.Args[1:])
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	// Override their values if the corresponding environment variables are
	// set.
	err = gotenberg.ApplyEnvOverrides(fs)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[FATAL] %v\n", 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.MustString("log-level"),
		LogFieldsPrefix:       parsedFlags.MustString("log-fields-prefix"),
		LogStdFormat:          parsedFlags.MustString("log-std-format"),
		LogStdEnableGcpFields: parsedFlags.MustBool("log-std-enable-gcp-fields"),
		LogStdLevelCase:       parsedFlags.MustString("log-std-level-case"),
	}

	err = telemetryCfg.Validate()
	if err != nil {
		fmt.Fprintf(os.Stderr, "[FATAL] invalid telemetry config: %s\n", err)
		os.Exit(1)
	}

	shutdownTelemetry, err := gotenberg.StartTelemetry(telemetryCfg)
	if err != nil {
		fmt.Fprintf(os.Stderr, "[FATAL] start telemetry: %s\n", err)
		os.Exit(1)
	}

	logger := gotenberg.ProcessLogger()
	bgCtx := context.Background()

	fatal := func(msg string, err error) {
		logger.ErrorContext(bgCtx, msg, slog.Any("error", err))
		os.Exit(1)
	}

	ctx := gotenberg.NewContext(parsedFlags, descriptors)

	// Start application modules. The API waits for the others to be ready
	// before it listens, so they start together.
	apps, err := ctx.Modules(new(gotenberg.App))
	if err != nil {
		fatal("load modules", err)
	}

	eg := new(errgroup.Group)
	for _, a := range apps {
		eg.Go(func() error {
			err := a.(gotenberg.App).Start()
			if err != nil {
				return fmt.Errorf("start %s: %w", a.(gotenberg.Module).Descriptor().ID, err)
			}

			return nil
		})
	}

	err = eg.Wait()
	if err != nil {
		fatal("start modules", err)
	}

	startup, err := gotenberg.NewStartup(ctx, Version, time.Since(startedAt))
	if err != nil {
		fatal("gather startup summary", err)
	}

	if telemetryCfg.Interactive() {
		// See https://no-color.org.
		err = startup.Write(os.Stdout, !hideBanner, os.Getenv("NO_COLOR") == "")
		if err != nil {
			fatal("print startup summary", err)
		}
	} else {
		startup.Log(bgCtx, logger)
	}

	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()
	}()

	logger.InfoContext(bgCtx, "graceful shutdown", slog.String("duration", gracefulShutdownDuration.String()))

	stoppable := make([]gotenberg.App, len(apps))
	for i, a := range apps {
		stoppable[i] = a.(gotenberg.App)
	}

	err = gotenberg.StopApps(gracefulShutdownCtx, stoppable, func(app gotenberg.App) {
		logger.InfoContext(bgCtx, "module stopped", slog.String("module", app.(gotenberg.Module).Descriptor().ID))
	})
	if err != nil {
		cancel()
		fatal("stop modules", err)
	}

	// Shutdown telemetry (flush spans, metrics, logs). The graceful shutdown's
	// context may already be done, by its deadline or a forced quit, so the
	// flush gets its own.
	telemetryCtx, telemetryCancel := context.WithTimeout(context.Background(), telemetryShutdownTimeout)
	defer telemetryCancel()

	err = shutdownTelemetry(telemetryCtx)
	if err != nil {
		cancel()
		// The telemetry, logs included, is down: write to stderr.
		fmt.Fprintf(os.Stderr, "[FATAL] shut down telemetry: %v\n", err)
		os.Exit(1) //nolint:gocritic // defers are already called explicitly above
	}

	os.Exit(0)
}