Merge pull request #11 from mautrix/segment-to-rudderstack

add generic analytics section for segment compatible api
This commit is contained in:
the-newman 2023-10-09 16:29:32 +01:00 committed by GitHub
commit 15bb0f8d18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 53 additions and 40 deletions

View file

@ -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(http.MethodPost, 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")
}
}()
}

View file

@ -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"`

View file

@ -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"},

View file

@ -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:

20
main.go
View file

@ -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")
}
}

View file

@ -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")

View file

@ -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