Skip to content

Add opt-in output forwarding for scheduled foreground commands#60729

Open
ManicardiFrancesco wants to merge 1 commit into
laravel:13.xfrom
ManicardiFrancesco:forward-output-to-console
Open

Add opt-in output forwarding for scheduled foreground commands#60729
ManicardiFrancesco wants to merge 1 commit into
laravel:13.xfrom
ManicardiFrancesco:forward-output-to-console

Conversation

@ManicardiFrancesco

Copy link
Copy Markdown
Contributor

Summary

Laravel currently forwards scheduled command output (stdout/stderr) to the parent schedule:run process only when running on Laravel Cloud (#53623). On self-hosted/containerized deployments (Docker, Kubernetes), scheduled command output is redirected to a file or /dev/null by default, so log lines written by scheduled commands (e.g. LOG_CHANNEL=stderr) never reach docker logs / kubectl logs, even though the parent "Running [...]" line does. Discussion #47448 tracks demand for this; a previous attempt (#56611) added default forwarding and was closed because the implementation wasn't loved.

This PR generalizes the existing Laravel Cloud capability into an explicit, opt-in API instead of changing default behavior:

// Per event
$schedule->command('foo')->everyMinute()->forwardOutputToConsole();

// Or for every foreground event on the schedule, regardless of call order
$schedule->forwardOutputToConsole();
$schedule->command('foo')->everyMinute();
$schedule->command('bar')->hourly();
  • Event::forwardOutputToConsole() opts a single event in.
  • Schedule::forwardOutputToConsole() is a schedule-wide static toggle (mirrors Schedule::$pausable/$interruptible/useCache()) — it applies to every foreground event regardless of whether it's called before or after command()/exec().
  • Output is streamed to the event's configured destination (sendOutputTo/appendOutputTo) via the Symfony Process output callback instead of shell redirection (tee), so:
    • the scheduled command's real exit code is preserved (a failing command isn't masked by a successful tee),
    • memory stays bounded for long-running/chatty commands (streamed, not buffered),
    • it works on Windows without a tee dependency.
  • Background events (runInBackground()) are intentionally unaffected — they already run through a detached shell that owns its own output redirection via schedule:finish, so there is nothing for this process to forward. Covered explicitly by tests rather than attempting to support it.
  • If the configured output destination can't be opened, the event now throws a descriptive exception (surfaced as ScheduledTaskFailed + reported) instead of silently succeeding.
  • Defaults are unchanged. Laravel Cloud's existing behavior (laravel_cloud()) is untouched.

This avoids application-level workarounds like writing to a temp file and using after() to fwrite it out.

Test plan

  • Event::forwardOutputToConsole() sets the flag and returns $this
  • Foreground command is built without shell redirection/tee when forwarding is enabled (including a failing command, to verify exit code preservation)
  • Event::execute() uses the forwarding callback when enabled, and the no-op callback by default
  • Output is still written to an explicitly configured destination (sendOutputTo/appendOutputTo), both truncating and appending
  • Background events are unaffected by forwarding (unit + integration test)
  • Schedule::forwardOutputToConsole() applies to events created before and after the call (integration test through real schedule:run execution)
  • Unwritable output destination throws a descriptive exception instead of silently succeeding
  • Existing default behavior (shell redirection, Laravel Cloud tee path) is unchanged
  • Full tests/Console and tests/Integration/Console suites pass; Pint and PHPStan clean on touched files

🤖 Generated with Claude Code

Generalizes the existing Laravel Cloud-only stdout/stderr forwarding
into an explicit opt-in API so self-hosted/containerized deployments
can pick up scheduled command output via `docker logs`/`kubectl logs`
without application-level workarounds (temp files + after() callbacks).

- Event::forwardOutputToConsole() opts a single event in.
- Schedule::forwardOutputToConsole() opts every foreground event in,
  regardless of call order (mirrors Schedule::$pausable/$interruptible
  and useCache()).
- Output is streamed to the configured destination via the Process
  callback instead of shell redirection, so exit codes are preserved,
  memory stays bounded for chatty commands, and it works on Windows
  (no `tee` dependency).
- Background events are intentionally unaffected: they already run
  through a detached shell that owns its own output redirection.
- Defaults are unchanged; Laravel Cloud behavior is untouched.
/**
* Get the callback used to forward the process output to the console.
*
* @return callable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could narrow this down:

Suggested change
* @return callable
* @return callable(object|string, string): true|null

or even

Suggested change
* @return callable
* @return callable(object|string, 'err'|'out'): true|null

/**
* Write a line of process output to the console's standard streams.
*
* @param string $type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to narrow this further:

Suggested change
* @param string $type
* @param 'err'|'out' $type

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants