@@ -2,17 +2,20 @@ package cli
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "os"
78 "os/signal"
89 "time"
910 "unicode"
1011
12+ "github.com/auth0/go-auth0/management"
1113 "github.com/spf13/cobra"
1214
1315 "github.com/auth0/auth0-cli/internal/analytics"
1416 "github.com/auth0/auth0-cli/internal/ansi"
1517 "github.com/auth0/auth0-cli/internal/buildinfo"
18+ "github.com/auth0/auth0-cli/internal/config"
1619 "github.com/auth0/auth0-cli/internal/display"
1720 "github.com/auth0/auth0-cli/internal/instrumentation"
1821)
@@ -62,17 +65,19 @@ func Execute() {
6265 ansi .InitConsole ()
6366
6467 cancelCtx := contextWithCancel ()
65- if err := rootCmd .ExecuteContext (cancelCtx ); err != nil {
68+ err := rootCmd .ExecuteContext (cancelCtx )
69+ trackCommandOutcome (cli , err )
70+
71+ timeoutCtx , cancel := context .WithTimeout (cancelCtx , 3 * time .Second )
72+ defer cancel ()
73+ cli .tracker .Wait (timeoutCtx ) // No event should be tracked after this has run.
74+
75+ if err != nil {
6676 renderErrorMessage (cli .renderer , err .Error ())
6777
6878 instrumentation .ReportException (err )
6979 os .Exit (1 ) // nolint:gocritic
7080 }
71-
72- timeoutCtx , cancel := context .WithTimeout (cancelCtx , 3 * time .Second )
73- // Defers are executed in LIFO order.
74- defer cancel ()
75- defer cli .tracker .Wait (timeoutCtx ) // No event should be tracked after this has run, or it will panic e.g. in earlier deferred functions.
7681}
7782
7883func buildRootCmd (cli * cli ) * cobra.Command {
@@ -84,6 +89,8 @@ func buildRootCmd(cli *cli) *cobra.Command {
8489 Long : rootShort + "\n " + getLogin (cli ),
8590 Version : buildinfo .GetVersionWithCommit (),
8691 PersistentPreRunE : func (cmd * cobra.Command , args []string ) error {
92+ cli .executedCommandPath = cmd .CommandPath ()
93+
8794 ansi .Initialize (cli .noColor )
8895 prepareInteractivity (cmd )
8996 cli .configureRenderer ()
@@ -92,16 +99,6 @@ func buildRootCmd(cli *cli) *cobra.Command {
9299 return nil
93100 }
94101
95- // We're tracking the login command in its Run method, so
96- // we'll only add this defer if the command is not login.
97- defer func () {
98- if cli .tracker != nil &&
99- cmd .CommandPath () != "auth0 login" &&
100- cli .Config .IsLoggedInWithTenant (cli .tenant ) {
101- cli .tracker .TrackCommandRun (cmd , cli .Config .InstallID )
102- }
103- }()
104-
105102 if err := cli .setupWithAuthentication (cmd .Context ()); err != nil {
106103 return err
107104 }
@@ -225,3 +222,77 @@ func renderErrorMessage(display *display.Renderer, errorMessage string) {
225222 display .Errorf (humanReadableErrorMessage )
226223 display .Newline ()
227224}
225+
226+ func trackCommandOutcome (cli * cli , executionErr error ) {
227+ if cli .tracker == nil {
228+ return
229+ }
230+
231+ installID := resolveInstallIDForTracking (cli )
232+ if installID == "" {
233+ return
234+ }
235+
236+ if cli .executedCommandPath == "" {
237+ cli .executedCommandPath = "auth0"
238+ }
239+
240+ if executionErr != nil {
241+ cli .tracker .TrackCommandFailed (cli .executedCommandPath , installID , classifyCommandFailure (executionErr ))
242+ return
243+ }
244+
245+ cli .tracker .TrackCommandSucceeded (cli .executedCommandPath , installID )
246+ }
247+
248+ func resolveInstallIDForTracking (cli * cli ) string {
249+ if cli .Config .InstallID != "" {
250+ return cli .Config .InstallID
251+ }
252+
253+ if err := cli .Config .Initialize (); err != nil {
254+ if errors .Is (err , config .ErrConfigFileMissing ) {
255+ return ""
256+ }
257+ return ""
258+ }
259+
260+ return cli .Config .InstallID
261+ }
262+
263+ func classifyCommandFailure (err error ) map [string ]string {
264+ properties := map [string ]string {
265+ "success" : "false" ,
266+ "error_class" : "unknown" ,
267+ }
268+
269+ if errors .Is (err , config .ErrInvalidToken ) || errors .Is (err , config .ErrMalformedToken ) {
270+ properties ["error_class" ] = "auth"
271+ return properties
272+ }
273+
274+ var missingScopesErr config.ErrTokenMissingRequiredScopes
275+ if errors .As (err , & missingScopesErr ) {
276+ properties ["error_class" ] = "auth"
277+ return properties
278+ }
279+
280+ var managementErr management.Error
281+ if errors .As (err , & managementErr ) {
282+ status := managementErr .Status ()
283+ switch {
284+ case status == 401 || status == 403 :
285+ properties ["error_class" ] = "auth"
286+ case status == 400 || status == 422 :
287+ properties ["error_class" ] = "validation"
288+ case status == 404 :
289+ properties ["error_class" ] = "not_found"
290+ case status == 429 :
291+ properties ["error_class" ] = "rate_limit"
292+ case status >= 500 :
293+ properties ["error_class" ] = "api"
294+ }
295+ }
296+
297+ return properties
298+ }
0 commit comments