diff --git a/cmd/gotenberg/main.go b/cmd/gotenberg/main.go index 0d7d7cea..dae9192e 100644 --- a/cmd/gotenberg/main.go +++ b/cmd/gotenberg/main.go @@ -25,6 +25,7 @@ const ( disableGoogleChromeEnvVar = "DISABLE_GOOGLE_CHROME" disableUnoconvEnvVar = "DISABLE_UNOCONV" disableHealthcheckLoggingEnvVar = "DISABLE_HEALTHCHECK_LOGGING" + debugProcessStartup = "DEBUG_PROCESS_STARTUP" ) func mustParseEnvVar() *api.Options { @@ -49,37 +50,31 @@ func mustParseEnvVar() *api.Options { } opts.DefaultListenPort = v } - if v, ok := os.LookupEnv(disableGoogleChromeEnvVar); ok { - if v != "1" && v != "0" { - notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableGoogleChromeEnvVar, v)) - os.Exit(1) + //checkBoolEnv is a convenience function for reading an env var with a bool value where `1` is true and `0` is false + checkBoolEnv := func(name string) bool { + if v, ok := os.LookupEnv(name); ok { + if v != "1" && v != "0" { + notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", name, v)) + os.Exit(1) + } + return v == "1" } - opts.EnableChromeEndpoints = v != "1" - } - if v, ok := os.LookupEnv(disableUnoconvEnvVar); ok { - if v != "1" && v != "0" { - notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableUnoconvEnvVar, v)) - os.Exit(1) - } - opts.EnableUnoconvEndpoints = v != "1" - } - if v, ok := os.LookupEnv(disableHealthcheckLoggingEnvVar); ok { - if v != "1" && v != "0" { - notify.ErrPrint(fmt.Errorf("%s: wrong value: want \"0\" or \"1\" got %v", disableHealthcheckLoggingEnvVar, v)) - os.Exit(1) - } - opts.EnableHealthcheckLogging = v != "1" + return false } + opts.EnableChromeEndpoints = !checkBoolEnv(disableGoogleChromeEnvVar) + opts.EnableUnoconvEndpoints = !checkBoolEnv(disableUnoconvEnvVar) + opts.EnableHealthcheckLogging = !checkBoolEnv(disableHealthcheckLoggingEnvVar) + opts.DebugProcessStartup = checkBoolEnv(debugProcessStartup) return opts } func mustStartProcesses(opts *api.Options) []pm2.Process { var processes []pm2.Process if opts.EnableChromeEndpoints { - processes = append(processes, pm2.NewChrome()) + processes = append(processes, pm2.NewChrome(opts.DebugProcessStartup)) } if opts.EnableUnoconvEndpoints { - processes = append(processes, pm2.NewUnoconv()) + processes = append(processes, pm2.NewUnoconv(opts.DebugProcessStartup)) } for _, p := range processes { notify.Printf("starting %s with PM2...", p.Fullname()) diff --git a/internal/app/api/api.go b/internal/app/api/api.go index 1edd6063..d00e01eb 100644 --- a/internal/app/api/api.go +++ b/internal/app/api/api.go @@ -1,8 +1,6 @@ package api -import ( - "github.com/labstack/echo/v4" -) +import "github.com/labstack/echo/v4" const pingEndpoint = "/ping" @@ -14,6 +12,7 @@ type Options struct { EnableChromeEndpoints bool EnableUnoconvEndpoints bool EnableHealthcheckLogging bool + DebugProcessStartup bool } // DefaultOptions returns default options. diff --git a/internal/pkg/pm2/chrome.go b/internal/pkg/pm2/chrome.go index dfcdc1f9..52326713 100644 --- a/internal/pkg/pm2/chrome.go +++ b/internal/pkg/pm2/chrome.go @@ -7,15 +7,19 @@ import ( "github.com/mafredri/cdp/devtool" ) +const ( + warmupTime = 10 * time.Second +) + type chrome struct { manager *processManager } // NewChrome retruns a Google Chrome // headless process. -func NewChrome() Process { +func NewChrome(debug bool) Process { return &chrome{ - manager: &processManager{}, + manager: &processManager{verbose: debug}, } } @@ -58,12 +62,20 @@ func (p *chrome) viable() bool { // check if Google Chrome is correctly running. ctx, cancel := context.WithCancel(context.Background()) defer cancel() - _, err := devtool.New("http://localhost:9222").Version(ctx) - return err == nil + p.manager.notifyf(`%v: checking Chrome liveness via debug version endpoint +'http://localhost:9222/json/version'`, p.name()) + v, err := devtool.New("http://localhost:9222").Version(ctx) + if err != nil { + p.manager.notifyf("%v: Chrome version endpoint returned error: %v", p.name(), err) + return false + } + p.manager.notifyf("%v: Chrome returned version info: %+v", p.name(), *v) + return true } func (p *chrome) warmup() { - time.Sleep(5 * time.Second) + p.manager.notifyf("%v: allowing Chrome %v to startup", p.name(), warmupTime) + time.Sleep(warmupTime) } // Compile-time checks to ensure type implements desired interfaces. diff --git a/internal/pkg/pm2/chrome_test.go b/internal/pkg/pm2/chrome_test.go index fa30c443..edb8b5af 100644 --- a/internal/pkg/pm2/chrome_test.go +++ b/internal/pkg/pm2/chrome_test.go @@ -7,13 +7,13 @@ import ( ) func TestChromeStart(t *testing.T) { - p := NewChrome() + p := NewChrome(false) err := p.Start() require.Nil(t, err) } func TestChromeShutdown(t *testing.T) { - p := NewChrome() + p := NewChrome(false) err := p.Shutdown() require.Nil(t, err) } diff --git a/internal/pkg/pm2/pm2.go b/internal/pkg/pm2/pm2.go index c4cf937a..9fc253c4 100644 --- a/internal/pkg/pm2/pm2.go +++ b/internal/pkg/pm2/pm2.go @@ -1,8 +1,14 @@ package pm2 import ( + "bufio" "fmt" + "io" "os/exec" + "strings" + "time" + + "github.com/thecodingmachine/gotenberg/internal/pkg/notify" ) const ( @@ -25,6 +31,7 @@ type Process interface { type processManager struct { heuristicState int32 + verbose bool } func (m *processManager) start(p Process) error { @@ -76,8 +83,43 @@ func (m *processManager) pm2(p Process, cmdName string) error { "pm2", cmdArgs..., ) + m.notifyf("executing command '%v'", strings.Join(cmd.Args, " ")) + if m.verbose { + chromeStdErr, err := cmd.StderrPipe() + if err != nil { + return fmt.Errorf("failed getting Chrome stderr: %v", err) + } + chromeStdOut, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("failed getting Chrome stdout: %v", err) + } + readFromPipe := func(name string, reader io.ReadCloser) { + r := bufio.NewReader(reader) + defer reader.Close() + for { + line, _, err := r.ReadLine() + if err != nil { + if err != io.EOF { + m.notifyf("error reading from %v for process %v", name, p.name()) + } + break + } + if len(line) != 0 { + m.notifyf("%v %v: %s", p.name(), name, string(line)) + } + } + } + go readFromPipe("stdout", chromeStdOut) + go readFromPipe("stderr", chromeStdErr) + } if err := cmd.Start(); err != nil { return fmt.Errorf("%s %s with PM2: %v", cmdName, p.Fullname(), err) } return nil } + +func (m *processManager) notifyf(format string, args ...interface{}) { + if m.verbose { + notify.Printf(fmt.Sprintf("%v: %s", time.Now().Format(time.RFC3339), format), args...) + } +} diff --git a/internal/pkg/pm2/unoconv.go b/internal/pkg/pm2/unoconv.go index f2881010..7c4fa13e 100644 --- a/internal/pkg/pm2/unoconv.go +++ b/internal/pkg/pm2/unoconv.go @@ -6,9 +6,9 @@ type unoconv struct { // NewUnoconv retruns a unoconv listener // process. -func NewUnoconv() Process { +func NewUnoconv(debug bool) Process { return &unoconv{ - manager: &processManager{}, + manager: &processManager{verbose: debug}, } } diff --git a/internal/pkg/pm2/unoconv_test.go b/internal/pkg/pm2/unoconv_test.go index 8c74b244..6995109e 100644 --- a/internal/pkg/pm2/unoconv_test.go +++ b/internal/pkg/pm2/unoconv_test.go @@ -7,13 +7,13 @@ import ( ) func TestUnoconvStart(t *testing.T) { - p := NewUnoconv() + p := NewUnoconv(false) err := p.Start() require.Nil(t, err) } func TestUnoconvShutdown(t *testing.T) { - p := NewUnoconv() + p := NewUnoconv(false) err := p.Shutdown() require.Nil(t, err) }