Skip to content

Commit a90fc63

Browse files
committed
Implement lstk logs --follow
1 parent 3200ba4 commit a90fc63

File tree

5 files changed

+35
-14
lines changed

5 files changed

+35
-14
lines changed

cmd/logs.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,23 @@ import (
1111

1212
var logsCmd = &cobra.Command{
1313
Use: "logs",
14-
Short: "Stream container logs",
15-
Long: "Stream logs from the LocalStack container in real-time. Press Ctrl+C to stop.",
14+
Short: "Show container logs",
15+
Long: "Show logs from the LocalStack container. Use --follow to stream in real-time.",
1616
PreRunE: initConfig,
1717
RunE: func(cmd *cobra.Command, args []string) error {
18+
follow, err := cmd.Flags().GetBool("follow")
19+
if err != nil {
20+
return err
21+
}
1822
rt, err := runtime.NewDockerRuntime()
1923
if err != nil {
2024
return err
2125
}
22-
return container.Logs(cmd.Context(), rt, output.NewPlainSink(os.Stdout))
26+
return container.Logs(cmd.Context(), rt, output.NewPlainSink(os.Stdout), follow)
2327
},
2428
}
2529

2630
func init() {
2731
rootCmd.AddCommand(logsCmd)
32+
logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
2833
}

internal/container/logs.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/localstack/lstk/internal/runtime"
1212
)
1313

14-
func Logs(ctx context.Context, rt runtime.Runtime, sink output.Sink) error {
14+
func Logs(ctx context.Context, rt runtime.Runtime, sink output.Sink, follow bool) error {
1515
cfg, err := config.Get()
1616
if err != nil {
1717
return fmt.Errorf("failed to get config: %w", err)
@@ -26,7 +26,7 @@ func Logs(ctx context.Context, rt runtime.Runtime, sink output.Sink) error {
2626
pr, pw := io.Pipe()
2727
errCh := make(chan error, 1)
2828
go func() {
29-
err := rt.StreamLogs(ctx, c.Name(), pw)
29+
err := rt.StreamLogs(ctx, c.Name(), pw, follow)
3030
pw.CloseWithError(err)
3131
errCh <- err
3232
}()

internal/runtime/docker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,11 @@ func (d *DockerRuntime) Logs(ctx context.Context, containerID string, tail int)
152152
return string(logs), nil
153153
}
154154

155-
func (d *DockerRuntime) StreamLogs(ctx context.Context, containerID string, out io.Writer) error {
155+
func (d *DockerRuntime) StreamLogs(ctx context.Context, containerID string, out io.Writer, follow bool) error {
156156
reader, err := d.client.ContainerLogs(ctx, containerID, container.LogsOptions{
157157
ShowStdout: true,
158158
ShowStderr: true,
159-
Follow: true,
159+
Follow: follow,
160160
Tail: "all",
161161
})
162162
if err != nil {

internal/runtime/runtime.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ type Runtime interface {
3030
Remove(ctx context.Context, containerName string) error
3131
IsRunning(ctx context.Context, containerID string) (bool, error)
3232
Logs(ctx context.Context, containerID string, tail int) (string, error)
33-
StreamLogs(ctx context.Context, containerID string, out io.Writer) error
33+
StreamLogs(ctx context.Context, containerID string, out io.Writer, follow bool) error
3434
GetImageVersion(ctx context.Context, imageName string) (string, error)
3535
}

test/integration/logs_test.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,38 @@ import (
1313
"github.com/stretchr/testify/require"
1414
)
1515

16-
func TestLogsCommandFailsWhenNotRunning(t *testing.T) {
16+
func TestLogsExitsByDefault(t *testing.T) {
1717
requireDocker(t)
1818
cleanup()
1919
t.Cleanup(cleanup)
2020

2121
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
2222
defer cancel()
2323

24+
startTestContainer(t, ctx)
25+
2426
cmd := exec.CommandContext(ctx, binaryPath(), "logs")
27+
_, err := cmd.CombinedOutput()
28+
29+
require.NoError(t, err, "lstk logs should exit cleanly when container is running")
30+
}
31+
32+
func TestLogsCommandFailsWhenNotRunning(t *testing.T) {
33+
requireDocker(t)
34+
cleanup()
35+
t.Cleanup(cleanup)
36+
37+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
38+
defer cancel()
39+
40+
cmd := exec.CommandContext(ctx, binaryPath(), "logs", "--follow")
2541
out, err := cmd.CombinedOutput()
2642

27-
require.Error(t, err, "expected lstk logs to fail when container not running")
43+
require.Error(t, err, "expected lstk logs --follow to fail when container not running")
2844
assert.Contains(t, string(out), "emulator is not running")
2945
}
3046

31-
func TestLogsCommandStreamsOutput(t *testing.T) {
47+
func TestLogsFollowStreamsOutput(t *testing.T) {
3248
requireDocker(t)
3349
cleanup()
3450
t.Cleanup(cleanup)
@@ -40,12 +56,12 @@ func TestLogsCommandStreamsOutput(t *testing.T) {
4056

4157
const marker = "lstk-logs-test-marker"
4258

43-
logsCmd := exec.CommandContext(ctx, binaryPath(), "logs")
59+
logsCmd := exec.CommandContext(ctx, binaryPath(), "logs", "--follow")
4460
stdout, err := logsCmd.StdoutPipe()
4561
require.NoError(t, err, "failed to get stdout pipe")
4662

4763
err = logsCmd.Start()
48-
require.NoError(t, err, "failed to start lstk logs")
64+
require.NoError(t, err, "failed to start lstk logs --follow")
4965
t.Cleanup(func() { _ = logsCmd.Process.Kill() })
5066

5167
// Give lstk logs a moment to connect before generating output
@@ -74,6 +90,6 @@ func TestLogsCommandStreamsOutput(t *testing.T) {
7490
select {
7591
case <-found:
7692
case <-ctx.Done():
77-
t.Fatal("marker did not appear in lstk logs output within timeout")
93+
t.Fatal("marker did not appear in lstk logs --follow output within timeout")
7894
}
7995
}

0 commit comments

Comments
 (0)