diff --git a/packages/cmd/login.go b/packages/cmd/login.go index 863abf95..dea36b46 100644 --- a/packages/cmd/login.go +++ b/packages/cmd/login.go @@ -241,9 +241,18 @@ var loginCmd = &cobra.Command{ util.HandleError(err, "Unable to write write to Infisical Config file. Please try again") } + // Identify the user in PostHog and alias the anonymous machine ID + // so that pre-login CLI events are merged into the same person record + Telemetry.IdentifyUser(userCredentialsToBeStored.Email) + // clear backed up secrets from prev account util.DeleteBackupSecrets() + // Capture login event and flush the PostHog client (including Identify/Alias + // events enqueued by IdentifyUser above). This must run before any early + // returns so that all enqueued events are flushed via CaptureEvent's Close(). + Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION)) + if plainOutput { util.PrintlnStdout(userCredentialsToBeStored.JTWToken) return @@ -260,8 +269,6 @@ var loginCmd = &cobra.Command{ plainBold.Println("\nQuick links") util.PrintlnStderr("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage") util.PrintlnStderr("- Stuck? Join our slack for quick support https://infisical.com/slack") - - Telemetry.CaptureEvent("cli-command:login", posthog.NewProperties().Set("infisical-backend", config.INFISICAL_URL).Set("version", util.CLI_VERSION)) } else { sdkAuthenticator := util.NewSdkAuthenticator(infisicalClient, cmd) diff --git a/packages/telemetry/telemetry.go b/packages/telemetry/telemetry.go index ffd74345..d40f5437 100644 --- a/packages/telemetry/telemetry.go +++ b/packages/telemetry/telemetry.go @@ -1,6 +1,8 @@ package telemetry import ( + "errors" + "github.com/Infisical/infisical-merge/packages/util" "github.com/denisbrodbeck/machineid" "github.com/posthog/posthog-go" @@ -56,27 +58,62 @@ func (t *Telemetry) CaptureEvent(eventName string, properties posthog.Properties } } +// IdentifyUser sends a PostHog identify call to enrich the person record +// with user properties, and aliases the anonymous machine ID to the user's +// email so that pre-login CLI events are merged into the same person. +func (t *Telemetry) IdentifyUser(email string) { + if !t.isEnabled || email == "" { + return + } + + // Identify the user with their email as the distinctId + t.posthogClient.Enqueue(posthog.Identify{ + DistinctId: email, + Properties: posthog.NewProperties(). + Set("email", email), + }) + + // Alias the anonymous machine ID to the user's email so that + // any events captured before login are linked to this person + machineId, err := machineid.ID() + if err == nil && machineId != "" { + anonymousId := "anonymous_cli_" + machineId + t.posthogClient.Enqueue(posthog.Alias{ + DistinctId: email, + Alias: anonymousId, + }) + } + + // Note: no Close() here — the caller is responsible for ensuring + // CaptureEvent (which calls Close) runs after IdentifyUser to flush + // all enqueued events (Identify, Alias, and Capture). +} + func (t *Telemetry) GetDistinctId() (string, error) { var distinctId string - var outputErr error machineId, err := machineid.ID() if err != nil { - outputErr = err + log.Debug().Err(err).Msg("failed to get machine ID for telemetry") } infisicalConfig, err := util.GetConfigFile() if err != nil { - outputErr = err + log.Debug().Err(err).Msg("failed to get config file for telemetry") } if infisicalConfig.LoggedInUserEmail != "" { distinctId = infisicalConfig.LoggedInUserEmail } else if machineId != "" { distinctId = "anonymous_cli_" + machineId - } else { - distinctId = "" } - return distinctId, outputErr + // Only return an error if we could not resolve any distinctId. + // Non-critical errors (e.g. machineid failure when email is available) + // are logged above but should not prevent event capture. + if distinctId == "" { + return "", errors.New("unable to resolve a distinct ID for telemetry") + } + + return distinctId, nil }