From c7244e67da30c2f1c13ef7a4cc72a6579ec1ce55 Mon Sep 17 00:00:00 2001 From: John Newman Date: Mon, 9 Oct 2023 13:03:58 +0100 Subject: [PATCH] add generic analytics section for segment compatible api --- segment.go => analytics.go | 39 +++++++++++++++++++------------------- config/config.go | 7 +++++-- config/upgrade.go | 7 ++++--- example-config.yaml | 12 ++++++++---- main.go | 20 ++++++++++++------- provisioning.go | 6 +++--- user.go | 2 +- 7 files changed, 53 insertions(+), 40 deletions(-) rename segment.go => analytics.go (66%) diff --git a/segment.go b/analytics.go similarity index 66% rename from segment.go rename to analytics.go index 6bba669..60ee0c3 100644 --- a/segment.go +++ b/analytics.go @@ -26,27 +26,26 @@ import ( "maunium.net/go/mautrix/id" ) -const SegmentURL = "https://api.segment.io/v1/track" - -type SegmentClient struct { +type AnalyticsClient struct { + url string key string userID string log zerolog.Logger client http.Client } -var Segment SegmentClient +var Analytics AnalyticsClient -func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties map[string]interface{}) error { +func (ac *AnalyticsClient) trackSync(userID id.UserID, event string, properties map[string]interface{}) error { var buf bytes.Buffer - var segmentUserID string - if Segment.userID != "" { - segmentUserID = Segment.userID + var analyticsUserId string + if Analytics.userID != "" { + analyticsUserId = Analytics.userID } else { - segmentUserID = userID.String() + analyticsUserId = userID.String() } err := json.NewEncoder(&buf).Encode(map[string]interface{}{ - "userId": segmentUserID, + "userId": analyticsUserId, "event": event, "properties": properties, }) @@ -54,12 +53,12 @@ func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties ma return err } - req, err := http.NewRequest("POST", SegmentURL, &buf) + req, err := http.NewRequest("POST", ac.url, &buf) if err != nil { return err } - req.SetBasicAuth(sc.key, "") - resp, err := sc.client.Do(req) + req.SetBasicAuth(ac.key, "") + resp, err := ac.client.Do(req) if err != nil { return err } @@ -70,12 +69,12 @@ func (sc *SegmentClient) trackSync(userID id.UserID, event string, properties ma return nil } -func (sc *SegmentClient) IsEnabled() bool { - return len(sc.key) > 0 +func (ac *AnalyticsClient) IsEnabled() bool { + return len(ac.key) > 0 } -func (sc *SegmentClient) Track(userID id.UserID, event string, properties ...map[string]interface{}) { - if !sc.IsEnabled() { +func (ac *AnalyticsClient) Track(userID id.UserID, event string, properties ...map[string]interface{}) { + if !ac.IsEnabled() { return } else if len(properties) > 1 { panic("Track should be called with at most one property map") @@ -87,11 +86,11 @@ func (sc *SegmentClient) Track(userID id.UserID, event string, properties ...map props = properties[0] } props["bridge"] = "gmessages" - err := sc.trackSync(userID, event, props) + err := ac.trackSync(userID, event, props) if err != nil { - sc.log.Err(err).Str("event", event).Msg("Error tracking event") + ac.log.Err(err).Str("event", event).Msg("Error tracking event") } else { - sc.log.Debug().Str("event", event).Msg("Tracked event") + ac.log.Debug().Str("event", event).Msg("Tracked event") } }() } diff --git a/config/config.go b/config/config.go index 25d9123..d96140f 100644 --- a/config/config.go +++ b/config/config.go @@ -24,8 +24,11 @@ import ( type Config struct { *bridgeconfig.BaseConfig `yaml:",inline"` - SegmentKey string `yaml:"segment_key"` - SegmentUserID string `yaml:"segment_user_id"` + Analytics struct { + Host string `yaml:"host"` + Token string `yaml:"token"` + UserID string `yaml:"user_id"` + } Metrics struct { Enabled bool `yaml:"enabled"` diff --git a/config/upgrade.go b/config/upgrade.go index a9f7d40..9527adf 100644 --- a/config/upgrade.go +++ b/config/upgrade.go @@ -25,8 +25,9 @@ import ( func DoUpgrade(helper *up.Helper) { bridgeconfig.Upgrader.DoUpgrade(helper) - helper.Copy(up.Str|up.Null, "segment_key") - helper.Copy(up.Str|up.Null, "segment_user_id") + helper.Copy(up.Str|up.Null, "analytics", "host") + helper.Copy(up.Str|up.Null, "analytics", "token") + helper.Copy(up.Str|up.Null, "analytics", "user_id") helper.Copy(up.Bool, "metrics", "enabled") helper.Copy(up.Str, "metrics", "listen") @@ -104,7 +105,7 @@ var SpacedBlocks = [][]string{ {"appservice", "database"}, {"appservice", "id"}, {"appservice", "as_token"}, - {"segment_key"}, + {"analytics"}, {"metrics"}, {"google_messages"}, {"bridge"}, diff --git a/example-config.yaml b/example-config.yaml index e524f63..56e77bf 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -76,10 +76,14 @@ appservice: as_token: "This value is generated when generating the registration" hs_token: "This value is generated when generating the registration" -# Segment API key to track some events, like provisioning API login and encryption errors. -segment_key: null -# Optional user_id to use when sending Segment events. If null, defaults to using mxID. -segment_user_id: null +# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors. +analytics: + # Hostname of the tracking server. The path is hardcoded to /v1/track + host: api.segment.io + # API key to send with tracking requests. Tracking is disabled if this is null. + token: null + # Optional user ID for tracking events. If null, defaults to using Matrix user ID. + user_id: null # Prometheus config. metrics: diff --git a/main.go b/main.go index cc12952..f65fe7e 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ package main import ( _ "embed" + "net/url" "sync" "go.mau.fi/util/configupgrade" @@ -81,13 +82,18 @@ func (br *GMBridge) Init() { util.BrowserDetailsMessage.DeviceType = gmproto.DeviceType(deviceVal) } - Segment.log = br.ZLog.With().Str("component", "segment").Logger() - Segment.key = br.Config.SegmentKey - Segment.userID = br.Config.SegmentUserID - if Segment.IsEnabled() { - Segment.log.Info().Msg("Segment metrics are enabled") - if Segment.userID != "" { - Segment.log.Info().Str("user_id", Segment.userID).Msg("Overriding Segment user ID") + Analytics.log = br.ZLog.With().Str("component", "segment").Logger() + Analytics.url = (&url.URL{ + Scheme: "https", + Host: br.Config.Analytics.Host, + Path: "/v1/track", + }).String() + Analytics.key = br.Config.Analytics.Token + Analytics.userID = br.Config.Analytics.UserID + if Analytics.IsEnabled() { + Analytics.log.Info().Msg("Analytics are enabled") + if Analytics.userID != "" { + Analytics.log.Info().Str("user_id", Analytics.userID).Msg("Overriding Analytics user ID") } } diff --git a/provisioning.go b/provisioning.go index 2b069ef..edd96ea 100644 --- a/provisioning.go +++ b/provisioning.go @@ -359,11 +359,11 @@ Loop: switch { case item.qr != "": log.Debug().Msg("Got code in QR channel") - Segment.Track(user.MXID, "$qrcode_retrieved") + Analytics.Track(user.MXID, "$qrcode_retrieved") jsonResponse(w, http.StatusOK, LoginResponse{Status: "qr", Code: item.qr}) case item.err != nil: log.Err(item.err).Msg("Got error in QR channel") - Segment.Track(user.MXID, "$login_failure") + Analytics.Track(user.MXID, "$login_failure") var resp LoginResponse switch { case errors.Is(item.err, ErrLoginTimeout): @@ -375,7 +375,7 @@ Loop: jsonResponse(w, http.StatusOK, resp) case item.success: log.Debug().Msg("Got pair success in QR channel") - Segment.Track(user.MXID, "$login_success") + Analytics.Track(user.MXID, "$login_success") jsonResponse(w, http.StatusOK, LoginResponse{Status: "success"}) default: log.Error().Any("item_data", item).Msg("Unknown item in QR channel") diff --git a/user.go b/user.go index 48e0ee0..2ba5e86 100644 --- a/user.go +++ b/user.go @@ -403,7 +403,7 @@ func (user *User) Login(maxAttempts int) (<-chan qrChannelItem, error) { user.loginInProgress.Store(false) return nil, fmt.Errorf("failed to connect to Google Messages: %w", err) } - Segment.Track(user.MXID, "$login_start") + Analytics.Track(user.MXID, "$login_start") ch := make(chan qrChannelItem, maxAttempts+2) ctx, cancel := context.WithCancel(context.Background()) user.cancelLogin = cancel