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