-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathanalytics.go
More file actions
104 lines (94 loc) · 3.62 KB
/
analytics.go
File metadata and controls
104 lines (94 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Package analytics provides a thin abstraction over anonymous usage events.
//
// Callers depend only on the Analytics interface, so the rest of the codebase
// never imports a concrete SDK. Two implementations are provided:
//
// - MixpanelAnalytics sends events via the official Mixpanel Go SDK.
// - StdoutAnalytics prints events to stdout as JSON for local / CI use.
//
// The factory New() picks between them based on whether a build-time token is
// present and whether the caller asked for debug mode.
package analytics
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
mixpanel "github.com/mixpanel/mixpanel-go"
)
// Analytics is the single entry point for emitting a usage event. A nil
// implementation is never returned by New; callers can rely on Send never
// panicking.
type Analytics interface {
Send(event string, properties map[string]any) error
}
// StdoutAnalytics prints each event to an io.Writer (stdout by default) as a
// single human-readable line. It is the zero-configuration implementation
// used for local development, CI, and when no Mixpanel token is wired in.
type StdoutAnalytics struct {
// Writer is where events are printed. Nil means os.Stdout.
Writer io.Writer
}
// Send formats the event and writes it as a single line. Errors from the
// underlying writer are returned unchanged.
func (s StdoutAnalytics) Send(event string, properties map[string]any) error {
w := s.Writer
if w == nil {
w = os.Stdout
}
payload, err := json.Marshal(properties)
if err != nil {
return fmt.Errorf("analytics: marshal properties: %w", err)
}
if _, err := fmt.Fprintf(w, "[analytics] event=%q properties=%s\n", event, payload); err != nil {
return fmt.Errorf("analytics: write event: %w", err)
}
return nil
}
// MixpanelAnalytics sends events to the Mixpanel HTTP API via the official
// mixpanel/mixpanel-go SDK. It uses a distinctID of "anonymous" because
// reposcan does not have a persistent user identity (that is handled in the
// telemetry layer built on top of this).
type MixpanelAnalytics struct {
client *mixpanel.ApiClient
distinctID string
}
// NewMixpanelAnalytics constructs a live client against the Mixpanel API.
// The token is required; passing an empty token is a caller bug.
func NewMixpanelAnalytics(token string) *MixpanelAnalytics {
return &MixpanelAnalytics{
client: mixpanel.NewApiClient(token),
distinctID: "anonymous",
}
}
// Send forwards the event to Mixpanel. Failures are returned to the caller
// verbatim - it is the caller's responsibility to decide whether telemetry
// failures should be logged or swallowed.
func (m *MixpanelAnalytics) Send(event string, properties map[string]any) error {
ev := m.client.NewEvent(event, m.distinctID, properties)
if err := m.client.Track(context.Background(), []*mixpanel.Event{ev}); err != nil {
return fmt.Errorf("analytics: mixpanel track: %w", err)
}
return nil
}
// compile-time interface checks
var (
_ Analytics = StdoutAnalytics{}
_ Analytics = (*MixpanelAnalytics)(nil)
)
// New picks the implementation based on build- and run-time inputs.
//
// - If debug is true, events go to stdout - even when a real token is wired
// in. This keeps local development loops noisy and predictable and mirrors
// the behavior expected by CI harnesses.
// - If token is empty (a local dev build without the -ldflags injection),
// fall back silently to stdout. This means no-one has to set up a Mixpanel
// project just to run the CLI.
// - Otherwise, return a live MixpanelAnalytics.
func New(token string, debug bool) Analytics {
if debug || token == "" {
return StdoutAnalytics{}
}
return NewMixpanelAnalytics(token)
}