event.go

  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}