Skip to content

Commit 536017d

Browse files
centralize telemetry rather than wrap each command
1 parent 71fd22b commit 536017d

14 files changed

Lines changed: 130 additions & 163 deletions

File tree

cmd/aws.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import (
1212
"github.com/localstack/lstk/internal/env"
1313
"github.com/localstack/lstk/internal/output"
1414
"github.com/localstack/lstk/internal/runtime"
15-
"github.com/localstack/lstk/internal/telemetry"
1615
"github.com/localstack/lstk/internal/terminal"
1716
"github.com/spf13/cobra"
1817
)
1918

20-
func newAWSCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
19+
func newAWSCmd(cfg *env.Env) *cobra.Command {
2120
return &cobra.Command{
2221
Use: "aws [args...]",
2322
Short: "Run AWS CLI commands against LocalStack",
@@ -35,7 +34,7 @@ Examples:
3534
lstk aws s3 mb s3://my-bucket`,
3635
DisableFlagParsing: true,
3736
PreRunE: initConfig,
38-
RunE: commandWithTelemetry("aws", tel, func(cmd *cobra.Command, args []string) error {
37+
RunE: func(cmd *cobra.Command, args []string) error {
3938
rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
4039
if err != nil {
4140
return err
@@ -93,6 +92,6 @@ Examples:
9392
}
9493

9594
return awscli.Exec(cmd.Context(), "http://"+host, profileExists, stdout, stderr, args)
96-
}),
95+
},
9796
}
9897
}

cmd/config.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,26 @@ import (
55

66
"github.com/localstack/lstk/internal/config"
77
"github.com/localstack/lstk/internal/env"
8-
"github.com/localstack/lstk/internal/telemetry"
98
"github.com/localstack/lstk/internal/ui"
109
"github.com/spf13/cobra"
1110
)
1211

13-
func newConfigCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
12+
func newConfigCmd(cfg *env.Env) *cobra.Command {
1413
cmd := &cobra.Command{
1514
Use: "config",
1615
Short: "Manage configuration",
1716
}
18-
cmd.AddCommand(newConfigProfileCmd(cfg, tel))
19-
cmd.AddCommand(newConfigPathCmd(cfg, tel))
17+
cmd.AddCommand(newConfigProfileCmd(cfg))
18+
cmd.AddCommand(newConfigPathCmd())
2019
return cmd
2120
}
2221

23-
func newConfigProfileCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
22+
func newConfigProfileCmd(cfg *env.Env) *cobra.Command {
2423
return &cobra.Command{
2524
Use: "profile",
2625
Short: "Deprecated: use 'lstk setup aws' instead",
2726
PreRunE: initConfig,
28-
RunE: commandWithTelemetry("config profile", tel, func(cmd *cobra.Command, args []string) error {
27+
RunE: func(cmd *cobra.Command, args []string) error {
2928
appConfig, err := config.Get()
3029
if err != nil {
3130
return fmt.Errorf("failed to get config: %w", err)
@@ -37,15 +36,15 @@ func newConfigProfileCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
3736

3837
// Delegate to the same handler as "lstk setup aws"
3938
return ui.RunConfigProfile(cmd.Context(), appConfig.Containers, cfg.LocalStackHost)
40-
}),
39+
},
4140
}
4241
}
4342

44-
func newConfigPathCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
43+
func newConfigPathCmd() *cobra.Command {
4544
return &cobra.Command{
4645
Use: "path",
4746
Short: "Print the configuration file path",
48-
RunE: commandWithTelemetry("config path", tel, func(cmd *cobra.Command, args []string) error {
47+
RunE: func(cmd *cobra.Command, args []string) error {
4948
path, err := cmd.Flags().GetString("config")
5049
if err != nil {
5150
return err
@@ -62,6 +61,6 @@ func newConfigPathCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
6261

6362
_, err = fmt.Fprintln(cmd.OutOrStdout(), configPath)
6463
return err
65-
}),
64+
},
6665
}
6766
}

cmd/login.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func newLoginCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.
1919
Short: "Manage login",
2020
Long: "Manage login and store credentials in system keyring",
2121
PreRunE: initConfig,
22-
RunE: commandWithTelemetry("login", tel, func(cmd *cobra.Command, args []string) error {
22+
RunE: func(cmd *cobra.Command, args []string) error {
2323
if !isInteractiveMode(cfg) {
2424
return fmt.Errorf("login requires an interactive terminal")
2525
}
@@ -33,6 +33,6 @@ func newLoginCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.
3333
}
3434
}
3535
return nil
36-
}),
36+
},
3737
}
3838
}

cmd/logout.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,16 @@ import (
1313
"github.com/localstack/lstk/internal/log"
1414
"github.com/localstack/lstk/internal/output"
1515
"github.com/localstack/lstk/internal/runtime"
16-
"github.com/localstack/lstk/internal/telemetry"
1716
"github.com/localstack/lstk/internal/ui"
1817
"github.com/spf13/cobra"
1918
)
2019

21-
func newLogoutCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.Command {
20+
func newLogoutCmd(cfg *env.Env, logger log.Logger) *cobra.Command {
2221
return &cobra.Command{
2322
Use: "logout",
2423
Short: "Remove stored authentication credentials",
2524
PreRunE: initConfig,
26-
RunE: commandWithTelemetry("logout", tel, func(cmd *cobra.Command, args []string) error {
25+
RunE: func(cmd *cobra.Command, args []string) error {
2726
platformClient := api.NewPlatformClient(cfg.APIEndpoint, logger)
2827
appConfig, err := config.Get()
2928
if err != nil {
@@ -58,6 +57,6 @@ func newLogoutCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra
5857
}
5958
}
6059
return nil
61-
}),
60+
},
6261
}
6362
}

cmd/logs.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,17 @@ import (
99
"github.com/localstack/lstk/internal/env"
1010
"github.com/localstack/lstk/internal/output"
1111
"github.com/localstack/lstk/internal/runtime"
12-
"github.com/localstack/lstk/internal/telemetry"
1312
"github.com/localstack/lstk/internal/ui"
1413
"github.com/spf13/cobra"
1514
)
1615

17-
func newLogsCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
16+
func newLogsCmd(cfg *env.Env) *cobra.Command {
1817
cmd := &cobra.Command{
1918
Use: "logs",
2019
Short: "Show emulator logs",
2120
Long: "Show logs from the emulator. Use --follow to stream in real-time.",
2221
PreRunE: initConfig,
23-
RunE: commandWithTelemetry("logs", tel, func(cmd *cobra.Command, args []string) error {
22+
RunE: func(cmd *cobra.Command, args []string) error {
2423
follow, err := cmd.Flags().GetBool("follow")
2524
if err != nil {
2625
return err
@@ -41,7 +40,7 @@ func newLogsCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
4140
return ui.RunLogs(cmd.Context(), rt, appConfig.Containers, follow, verbose)
4241
}
4342
return container.Logs(cmd.Context(), rt, output.NewPlainSink(os.Stdout), appConfig.Containers, follow, verbose)
44-
}),
43+
},
4544
}
4645
cmd.Flags().BoolP("follow", "f", false, "Follow log output")
4746
cmd.Flags().BoolP("verbose", "v", false, "Show all log output without filtering")

cmd/restart.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func newRestartCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobr
2121
Short: "Restart emulator",
2222
Long: "Stop and restart emulator and services.",
2323
PreRunE: initConfig,
24-
RunE: commandWithTelemetry("restart", tel, func(cmd *cobra.Command, args []string) error {
24+
RunE: func(cmd *cobra.Command, args []string) error {
2525
rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
2626
if err != nil {
2727
return err
@@ -43,6 +43,6 @@ func newRestartCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobr
4343

4444
sink := output.NewPlainSink(os.Stdout)
4545
return container.Restart(cmd.Context(), rt, sink, stopOpts, startOpts, false)
46-
}),
46+
},
4747
}
4848
}

cmd/root.go

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func NewRootCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.C
4040
if err != nil {
4141
return err
4242
}
43-
return runStart(cmd.Context(), cmd.Flags(), rt, cfg, tel, logger)
43+
return startEmulator(cmd.Context(), rt, cfg, tel, logger)
4444
},
4545
}
4646

@@ -63,15 +63,15 @@ func NewRootCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.C
6363
newStopCmd(cfg, tel),
6464
newRestartCmd(cfg, tel, logger),
6565
newLoginCmd(cfg, tel, logger),
66-
newLogoutCmd(cfg, tel, logger),
67-
newStatusCmd(cfg, tel),
68-
newLogsCmd(cfg, tel),
69-
newSetupCmd(cfg, tel),
70-
newConfigCmd(cfg, tel),
71-
newVolumeCmd(cfg, tel),
72-
newUpdateCmd(cfg, tel),
66+
newLogoutCmd(cfg, logger),
67+
newStatusCmd(cfg),
68+
newLogsCmd(cfg),
69+
newSetupCmd(cfg),
70+
newConfigCmd(cfg),
71+
newVolumeCmd(cfg),
72+
newUpdateCmd(cfg),
7373
newDocsCmd(),
74-
newAWSCmd(cfg, tel),
74+
newAWSCmd(cfg),
7575
)
7676

7777
return root
@@ -118,6 +118,7 @@ func Execute(ctx context.Context) error {
118118
root := NewRootCmd(cfg, tel, logger)
119119
root.SilenceErrors = true
120120
root.SilenceUsage = true
121+
instrumentCommands(root, tel)
121122
if cfg.TracesEnabled {
122123
wrapCommandsWithTracing(root)
123124
}
@@ -192,48 +193,34 @@ func startEmulator(ctx context.Context, rt runtime.Runtime, cfg *env.Env, tel *t
192193
return container.Start(ctx, rt, sink, opts, false)
193194
}
194195

195-
func runStart(ctx context.Context, cmdFlags *pflag.FlagSet, rt runtime.Runtime, cfg *env.Env, tel *telemetry.Client, logger log.Logger) error {
196-
startTime := time.Now()
197-
198-
var flags []string
199-
cmdFlags.Visit(func(f *pflag.Flag) {
200-
flags = append(flags, "--"+f.Name)
201-
})
202-
203-
runErr := startEmulator(ctx, rt, cfg, tel, logger)
204-
205-
exitCode := 0
206-
errorMsg := ""
207-
if runErr != nil {
208-
exitCode = 1
209-
errorMsg = runErr.Error()
210-
}
211-
tel.EmitCommand(ctx, "start", flags, time.Since(startTime).Milliseconds(), exitCode, errorMsg)
212-
213-
return runErr
214-
}
215-
216-
// wraps a RunE function so that an lstk_command event is emitted after every invocation
217-
// used for commands that do not emit lstk_lifecycle events (i.e. status, logs, config path, etc)
218-
func commandWithTelemetry(name string, tel *telemetry.Client, fn func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
219-
return func(cmd *cobra.Command, args []string) error {
220-
startTime := time.Now()
221-
runErr := fn(cmd, args)
196+
// instrumentCommands walks the Cobra command tree and wraps every RunE with telemetry emission.
197+
func instrumentCommands(cmd *cobra.Command, tel *telemetry.Client) {
198+
if cmd.RunE != nil {
199+
original := cmd.RunE
200+
cmd.RunE = func(c *cobra.Command, args []string) error {
201+
startTime := time.Now()
202+
runErr := original(c, args)
203+
204+
var flags []string
205+
c.Flags().Visit(func(f *pflag.Flag) {
206+
flags = append(flags, "--"+f.Name)
207+
})
208+
209+
exitCode := 0
210+
errorMsg := ""
211+
if runErr != nil {
212+
exitCode = 1
213+
errorMsg = runErr.Error()
214+
}
222215

223-
var flags []string
224-
cmd.Flags().Visit(func(f *pflag.Flag) {
225-
flags = append(flags, "--"+f.Name)
226-
})
216+
commandName := strings.TrimPrefix(c.CommandPath(), c.Root().Name()+" ")
217+
tel.EmitCommand(c.Context(), commandName, flags, time.Since(startTime).Milliseconds(), exitCode, errorMsg)
227218

228-
exitCode := 0
229-
errorMsg := ""
230-
if runErr != nil {
231-
exitCode = 1
232-
errorMsg = runErr.Error()
219+
return runErr
233220
}
234-
tel.EmitCommand(cmd.Context(), name, flags, time.Since(startTime).Milliseconds(), exitCode, errorMsg)
235-
236-
return runErr
221+
}
222+
for _, child := range cmd.Commands() {
223+
instrumentCommands(child, tel)
237224
}
238225
}
239226

cmd/setup.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,27 @@ import (
55

66
"github.com/localstack/lstk/internal/config"
77
"github.com/localstack/lstk/internal/env"
8-
"github.com/localstack/lstk/internal/telemetry"
98
"github.com/localstack/lstk/internal/ui"
109
"github.com/spf13/cobra"
1110
)
1211

13-
func newSetupCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
12+
func newSetupCmd(cfg *env.Env) *cobra.Command {
1413
cmd := &cobra.Command{
1514
Use: "setup",
1615
Short: "Set up emulator CLI integration",
1716
Long: "Set up emulator CLI integration. Currently only AWS is supported.",
1817
}
19-
cmd.AddCommand(newSetupAWSCmd(cfg, tel))
18+
cmd.AddCommand(newSetupAWSCmd(cfg))
2019
return cmd
2120
}
2221

23-
func newSetupAWSCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
22+
func newSetupAWSCmd(cfg *env.Env) *cobra.Command {
2423
return &cobra.Command{
2524
Use: "aws",
2625
Short: "Set up the LocalStack AWS profile",
2726
Long: "Set up the LocalStack AWS profile in ~/.aws/config and ~/.aws/credentials for use with AWS CLI and SDKs.",
2827
PreRunE: initConfig,
29-
RunE: commandWithTelemetry("setup aws", tel, func(cmd *cobra.Command, args []string) error {
28+
RunE: func(cmd *cobra.Command, args []string) error {
3029
appConfig, err := config.Get()
3130
if err != nil {
3231
return fmt.Errorf("failed to get config: %w", err)
@@ -37,6 +36,6 @@ func newSetupAWSCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
3736
}
3837

3938
return ui.RunConfigProfile(cmd.Context(), appConfig.Containers, cfg.LocalStackHost)
40-
}),
39+
},
4140
}
4241
}

cmd/start.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func newStartCmd(cfg *env.Env, tel *telemetry.Client, logger log.Logger) *cobra.
1919
if err != nil {
2020
return err
2121
}
22-
return runStart(cmd.Context(), cmd.Flags(), rt, cfg, tel, logger)
22+
return startEmulator(cmd.Context(), rt, cfg, tel, logger)
2323
},
2424
}
2525
}

cmd/status.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,17 @@ import (
1010
"github.com/localstack/lstk/internal/env"
1111
"github.com/localstack/lstk/internal/output"
1212
"github.com/localstack/lstk/internal/runtime"
13-
"github.com/localstack/lstk/internal/telemetry"
1413
"github.com/localstack/lstk/internal/ui"
1514
"github.com/spf13/cobra"
1615
)
1716

18-
func newStatusCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
17+
func newStatusCmd(cfg *env.Env) *cobra.Command {
1918
return &cobra.Command{
2019
Use: "status",
2120
Short: "Show emulator status and deployed resources",
2221
Long: "Show the status of a running emulator and its deployed resources",
2322
PreRunE: initConfig,
24-
RunE: commandWithTelemetry("status", tel, func(cmd *cobra.Command, args []string) error {
23+
RunE: func(cmd *cobra.Command, args []string) error {
2524
rt, err := runtime.NewDockerRuntime(cfg.DockerHost)
2625
if err != nil {
2726
return err
@@ -37,6 +36,6 @@ func newStatusCmd(cfg *env.Env, tel *telemetry.Client) *cobra.Command {
3736
return ui.RunStatus(cmd.Context(), rt, appCfg.Containers, cfg.LocalStackHost, awsClient)
3837
}
3938
return container.Status(cmd.Context(), rt, appCfg.Containers, cfg.LocalStackHost, awsClient, output.NewPlainSink(os.Stdout))
40-
}),
39+
},
4140
}
4241
}

0 commit comments

Comments
 (0)