77 "bytes"
88 "context"
99 "fmt"
10+ "io"
1011 "net"
1112
1213 "github.com/containerd/errdefs"
@@ -24,7 +25,8 @@ type Resource interface {
2425 GetPort (portID string ) string
2526 GetBoundIP (portID string ) string
2627 GetHostPort (portID string ) string
27- Logs (ctx context.Context ) (string , error )
28+ Logs (ctx context.Context ) (stdout , stderr string , err error )
29+ FollowLogs (ctx context.Context , stdout , stderr io.Writer ) error
2830 Exec (ctx context.Context , cmd []string ) (ExecResult , error )
2931 ConnectToNetwork (ctx context.Context , net Network ) error
3032 DisconnectFromNetwork (ctx context.Context , net Network ) error
@@ -153,28 +155,50 @@ func (r *resource) Cleanup(t TestingTB) {
153155 })
154156}
155157
156- // Logs returns the container logs, demultiplexing stdout and stderr streams.
157- // Both stdout and stderr are combined in the returned string.
158- func (r * resource ) Logs (ctx context.Context ) (string , error ) {
158+ func (r * resource ) containerLogReader (ctx context.Context , follow bool ) (io.ReadCloser , error ) {
159159 if r .pool == nil || r .pool .client == nil {
160- return "" , ErrClientClosed
160+ return nil , ErrClientClosed
161161 }
162-
163162 reader , err := r .pool .client .ContainerLogs (ctx , r .container .ID , mobyclient.ContainerLogsOptions {
164163 ShowStdout : true ,
165164 ShowStderr : true ,
165+ Follow : follow ,
166166 })
167167 if err != nil {
168- return "" , fmt .Errorf ("failed to get container logs: %w" , err )
168+ return nil , fmt .Errorf ("failed to get container logs: %w" , err )
169+ }
170+ return reader , nil
171+ }
172+
173+ // Logs returns the container logs, demultiplexing stdout and stderr.
174+ func (r * resource ) Logs (ctx context.Context ) (stdout , stderr string , err error ) {
175+ reader , err := r .containerLogReader (ctx , false )
176+ if err != nil {
177+ return "" , "" , err
169178 }
170179 defer reader .Close ()
171180
172- var buf bytes.Buffer
173- if _ , err := stdcopy .StdCopy (& buf , & buf , reader ); err != nil {
174- return "" , fmt .Errorf ("failed to read container logs: %w" , err )
181+ var outBuf , errBuf bytes.Buffer
182+ if _ , err := stdcopy .StdCopy (& outBuf , & errBuf , reader ); err != nil {
183+ return "" , "" , fmt .Errorf ("failed to read container logs: %w" , err )
184+ }
185+
186+ return outBuf .String (), errBuf .String (), nil
187+ }
188+
189+ // FollowLogs streams container logs to stdout and stderr until ctx is cancelled
190+ // or the container exits. Pass io.Discard for writers you don't need.
191+ func (r * resource ) FollowLogs (ctx context.Context , stdout , stderr io.Writer ) error {
192+ reader , err := r .containerLogReader (ctx , true )
193+ if err != nil {
194+ return err
175195 }
196+ defer reader .Close ()
176197
177- return buf .String (), nil
198+ if _ , err := stdcopy .StdCopy (stdout , stderr , reader ); err != nil {
199+ return fmt .Errorf ("failed to follow container logs: %w" , err )
200+ }
201+ return nil
178202}
179203
180204// ExecResult holds the output of a command executed inside a container.
0 commit comments