event.go 2.3 KB

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