@@ -337,26 +337,16 @@ func (m *manager) RunInitContainer(
337337}
338338
339339// StreamLogs streams container logs to the provided writers.
340+ // Note: Podman's REST API delivers logs in bursts with higher latency
341+ // than Docker's multiplexed binary stream. This is a known limitation.
340342func (m * manager ) StreamLogs (
341343 ctx context.Context ,
342344 containerID string ,
343345 stdout , stderr io.Writer ,
344346) error {
345- // Unlike Docker's ContainerLogs (which waits for a "created" container
346- // to start producing output), Podman's Logs API returns immediately
347- // with EOF when the container hasn't started yet. Poll until the
348- // container is running so we don't silently lose all log output.
349- if err := m .waitForRunning (ctx , containerID ); err != nil {
350- return fmt .Errorf ("waiting for container to start: %w" , err )
351- }
352-
353- follow := true
354- showStdout := true
355- showStderr := true
356-
357347 // Derive a context from m.conn (carries Podman connection info) that
358348 // also cancels when the caller's ctx is cancelled. This ensures that
359- // follow-mode log streaming terminates when the caller cancels (e.g.,
349+ // the attach connection terminates when the caller cancels (e.g.,
360350 // after a container checkpoint).
361351 logConn , cancel := context .WithCancel (m .conn )
362352 defer cancel ()
@@ -369,50 +359,22 @@ func (m *manager) StreamLogs(
369359 }
370360 }()
371361
372- stdoutCh := make (chan string , 100 )
373- stderrCh := make (chan string , 100 )
374-
375- var wg sync.WaitGroup
376-
377- wg .Add (1 )
378-
379- go func () {
380- defer wg .Done ()
381-
382- for line := range stdoutCh {
383- if stdout != nil {
384- _ , _ = io .WriteString (stdout , line + "\n " )
385- }
386- }
387- }()
388-
389- wg .Add (1 )
390-
391- go func () {
392- defer wg .Done ()
393-
394- for line := range stderrCh {
395- if stderr != nil {
396- _ , _ = io .WriteString (stderr , line + "\n " )
397- }
362+ // Use the attach API instead of logs. The logs API reads from the
363+ // container's log file (written by conmon) which adds 100-250ms of
364+ // latency. Attach connects directly to the container's stdio streams
365+ // via a WebSocket-upgraded connection, giving us real-time output —
366+ // the same low-latency behavior as Docker's ContainerLogs.
367+ //
368+ // Unlike the logs API, attach works on "created" containers (it blocks
369+ // until the container starts), so we don't need waitForRunning.
370+ err := containers .Attach (logConn , containerID , nil , stdout , stderr , nil , nil )
371+ if err != nil {
372+ // Context cancellation is expected during cleanup.
373+ if ctx .Err () != nil {
374+ return nil
398375 }
399- }()
400-
401- err := containers .Logs (logConn , containerID , & containers.LogOptions {
402- Follow : & follow ,
403- Stdout : & showStdout ,
404- Stderr : & showStderr ,
405- }, stdoutCh , stderrCh )
406-
407- // Podman's Logs does not close the channels on return. Close them so
408- // the reader goroutines exit their range loops.
409- close (stdoutCh )
410- close (stderrCh )
411376
412- wg .Wait ()
413-
414- if err != nil {
415- return fmt .Errorf ("streaming logs: %w" , err )
377+ return fmt .Errorf ("attaching to container: %w" , err )
416378 }
417379
418380 return nil
0 commit comments