1package event
2
3import (
4 "fmt"
5 "log/slog"
6 "os"
7 "reflect"
8 "runtime"
9
10 "github.com/charmbracelet/crush/internal/version"
11 "github.com/denisbrodbeck/machineid"
12 "github.com/posthog/posthog-go"
13)
14
15const (
16 endpoint = "https://data.charm.land"
17 key = "phc_4zt4VgDWLqbYnJYEwLRxFoaTL2noNrQij0C6E8k3I0V"
18)
19
20var (
21 client posthog.Client
22
23 baseProps = posthog.NewProperties().
24 Set("GOOS", runtime.GOOS).
25 Set("GOARCH", runtime.GOARCH).
26 Set("TERM", os.Getenv("TERM")).
27 Set("SHELL", os.Getenv("SHELL")).
28 Set("Version", version.Version).
29 Set("GoVersion", runtime.Version())
30)
31
32func Init() {
33 c, err := posthog.NewWithConfig(key, posthog.Config{
34 Endpoint: endpoint,
35 Logger: logger{},
36 })
37 if err != nil {
38 slog.Error("Failed to initialize PostHog client", "error", err)
39 }
40 client = c
41}
42
43// send logs an event to PostHog with the given event name and properties.
44func send(event string, props ...any) {
45 if client == nil {
46 return
47 }
48 err := client.Enqueue(posthog.Capture{
49 DistinctId: distinctId(),
50 Event: event,
51 Properties: pairsToProps(props...).Merge(baseProps),
52 })
53 if err != nil {
54 slog.Error("Failed to enqueue PostHog event", "event", event, "props", props, "error", err)
55 return
56 }
57}
58
59// Error logs an error event to PostHog with the error type and message.
60func Error(err any, props ...any) {
61 if client == nil {
62 return
63 }
64 // The PostHog Go client does not yet support sending exceptions.
65 // We're mimicking the behavior by sending the minimal info required
66 // for PostHog to recognize this as an exception event.
67 props = append(
68 []any{
69 "$exception_list",
70 []map[string]string{
71 {"type": reflect.TypeOf(err).String(), "value": fmt.Sprintf("%v", err)},
72 },
73 },
74 props...,
75 )
76 send("$exception", props...)
77}
78
79func Flush() {
80 if client == nil {
81 return
82 }
83 if err := client.Close(); err != nil {
84 slog.Error("Failed to flush PostHog events", "error", err)
85 }
86}
87
88func pairsToProps(props ...any) posthog.Properties {
89 p := posthog.NewProperties()
90
91 if !isEven(len(props)) {
92 slog.Error("Event properties must be provided as key-value pairs", "props", props)
93 return p
94 }
95
96 for i := 0; i < len(props); i += 2 {
97 key := props[i].(string)
98 value := props[i+1]
99 p = p.Set(key, value)
100 }
101 return p
102}
103
104func isEven(n int) bool {
105 return n%2 == 0
106}
107
108func distinctId() string {
109 id, err := machineid.ProtectedID("charm")
110 if err != nil {
111 return "crush-cli"
112 }
113 return id
114}