package thinkingdata import ( "encoding/json" "errors" "sync" "time" ) const ( Track = "track" TrackUpdate = "track_update" TrackOverwrite = "track_overwrite" UserSet = "user_set" UserUnset = "user_unset" UserSetOnce = "user_setOnce" UserAdd = "user_add" UserAppend = "user_append" UserUniqAppend = "user_uniq_append" UserDel = "user_del" SdkVersion = "2.0.3" LibName = "Golang" ) type Data struct { IsComplex bool `json:"-"` // properties are nested or not AccountId string `json:"#account_id,omitempty"` DistinctId string `json:"#distinct_id,omitempty"` Type string `json:"#type"` Time string `json:"#time"` Timestamp int64 `json:"#timestamp,omitempty"` EventName string `json:"#event_name,omitempty"` EventId string `json:"#event_id,omitempty"` FirstCheckId string `json:"#first_check_id,omitempty"` Ip string `json:"#ip,omitempty"` UUID string `json:"#uuid,omitempty"` AppId string `json:"#app_id,omitempty"` Properties map[string]interface{} `json:"properties"` PropertiesSummary string `json:"properties_summary,omitempty"` } // TDConsumer define operation interface type TDConsumer interface { Add(d Data) error Flush() error Close() error IsStringent() bool // check data or not. } type TDAnalytics struct { consumer TDConsumer superProperties map[string]interface{} mutex *sync.RWMutex dynamicSuperProperties func() map[string]interface{} } // New init SDK func New(c TDConsumer) TDAnalytics { tdLogInfo("init SDK success") return TDAnalytics{ consumer: c, superProperties: make(map[string]interface{}), mutex: new(sync.RWMutex), } } // GetSuperProperties get common properties func (ta *TDAnalytics) GetSuperProperties() map[string]interface{} { result := make(map[string]interface{}) ta.mutex.Lock() mergeProperties(result, ta.superProperties) ta.mutex.Unlock() return result } // SetSuperProperties set common properties func (ta *TDAnalytics) SetSuperProperties(superProperties map[string]interface{}) { ta.mutex.Lock() mergeProperties(ta.superProperties, superProperties) ta.mutex.Unlock() } // ClearSuperProperties clear common properties func (ta *TDAnalytics) ClearSuperProperties() { ta.mutex.Lock() ta.superProperties = make(map[string]interface{}) ta.mutex.Unlock() } // SetDynamicSuperProperties set common properties dynamically. // not recommend to add the operation which with a lot of computation func (ta *TDAnalytics) SetDynamicSuperProperties(action func() map[string]interface{}) { ta.mutex.Lock() ta.dynamicSuperProperties = action ta.mutex.Unlock() } // GetDynamicSuperProperties dynamic common properties func (ta *TDAnalytics) GetDynamicSuperProperties() map[string]interface{} { result := make(map[string]interface{}) ta.mutex.RLock() if ta.dynamicSuperProperties != nil { mergeProperties(result, ta.dynamicSuperProperties()) } ta.mutex.RUnlock() return result } // Track report ordinary event func (ta *TDAnalytics) Track(accountId, distinctId, eventName string, properties map[string]interface{}) error { return ta.track(accountId, distinctId, Track, eventName, "", properties) } // TrackFirst report first event func (ta *TDAnalytics) TrackFirst(accountId, distinctId, eventName, firstCheckId string, properties map[string]interface{}) error { if len(firstCheckId) == 0 { msg := "the 'firstCheckId' must be provided" tdLogInfo(msg) return errors.New(msg) } p := make(map[string]interface{}) mergeProperties(p, properties) p["#first_check_id"] = firstCheckId return ta.track(accountId, distinctId, Track, eventName, "", p) } // TrackUpdate report updatable event func (ta *TDAnalytics) TrackUpdate(accountId, distinctId, eventName, eventId string, properties map[string]interface{}) error { return ta.track(accountId, distinctId, TrackUpdate, eventName, eventId, properties) } // TrackOverwrite report overridable event func (ta *TDAnalytics) TrackOverwrite(accountId, distinctId, eventName, eventId string, properties map[string]interface{}) error { return ta.track(accountId, distinctId, TrackOverwrite, eventName, eventId, properties) } func (ta *TDAnalytics) track(accountId, distinctId, dataType, eventName, eventId string, properties map[string]interface{}) error { defer func() { if r := recover(); r != nil { tdLogError("%+v\ndata: %+v", r, properties) } }() if len(eventName) == 0 { msg := "the event name must be provided" tdLogError(msg) return errors.New(msg) } // eventId not be null unless eventType is equal Track. if len(eventId) == 0 && dataType != Track { msg := "the event id must be provided" tdLogError(msg) return errors.New(msg) } p := ta.GetSuperProperties() dynamicSuperProperties := ta.GetDynamicSuperProperties() mergeProperties(p, dynamicSuperProperties) // preset properties has the highest priority p["#lib"] = LibName p["#lib_version"] = SdkVersion // custom properties mergeProperties(p, properties) return ta.add(accountId, distinctId, dataType, eventName, eventId, p) } // UserSet set user properties. would overwrite existing names. func (ta *TDAnalytics) UserSet(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserSet, properties) } // UserUnset clear the user properties of users. func (ta *TDAnalytics) UserUnset(accountId string, distinctId string, s []string) error { if len(s) == 0 { msg := "invalid params for UserUnset: keys is nil" tdLogInfo(msg) return errors.New(msg) } prop := make(map[string]interface{}) for _, v := range s { prop[v] = 0 } return ta.user(accountId, distinctId, UserUnset, prop) } func (ta *TDAnalytics) UserUnsetWithProperties(accountId string, distinctId string, properties map[string]interface{}) error { if len(properties) == 0 { msg := "invalid params for UserUnset: properties is nil" tdLogInfo(msg) return errors.New(msg) } return ta.user(accountId, distinctId, UserUnset, properties) } // UserSetOnce set user properties, If such property had been set before, this message would be neglected. func (ta *TDAnalytics) UserSetOnce(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserSetOnce, properties) } // UserAdd to accumulate operations against the property. func (ta *TDAnalytics) UserAdd(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserAdd, properties) } // UserAppend to add user properties of array type. func (ta *TDAnalytics) UserAppend(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserAppend, properties) } // UserUniqAppend append user properties to array type by unique. func (ta *TDAnalytics) UserUniqAppend(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserUniqAppend, properties) } // UserDelete delete a user, This operation cannot be undone. func (ta *TDAnalytics) UserDelete(accountId string, distinctId string) error { return ta.user(accountId, distinctId, UserDel, nil) } // UserDeleteWithProperties delete a user, This operation cannot be undone. func (ta *TDAnalytics) UserDeleteWithProperties(accountId string, distinctId string, properties map[string]interface{}) error { return ta.user(accountId, distinctId, UserDel, properties) } func (ta *TDAnalytics) user(accountId, distinctId, dataType string, properties map[string]interface{}) error { defer func() { if r := recover(); r != nil { tdLogError("%+v\ndata: %+v", r, properties) } }() if properties == nil && dataType != UserDel { msg := "invalid params for " + dataType + ": properties is nil" tdLogError(msg) return errors.New(msg) } p := make(map[string]interface{}) mergeProperties(p, properties) return ta.add(accountId, distinctId, dataType, "", "", p) } // Flush report data immediately. func (ta *TDAnalytics) Flush() error { return ta.consumer.Flush() } // Close and exit sdk func (ta *TDAnalytics) Close() error { err := ta.consumer.Close() tdLogInfo("SDK close") return err } func (ta *TDAnalytics) add(accountId, distinctId, dataType, eventName, eventId string, properties map[string]interface{}) error { if len(accountId) == 0 && len(distinctId) == 0 { msg := "invalid parameters: account_id and distinct_id cannot be empty at the same time" tdLogError(msg) return errors.New(msg) } // get "#ip" value in properties, empty string will be return when not found. ip := extractStringProperty(properties, "#ip") // get "#app_id" value in properties, empty string will be return when not found. appId := extractStringProperty(properties, "#app_id") // get "#time" value in properties, empty string will be return when not found. eventTime := extractTime(properties) firstCheckId := extractStringProperty(properties, "#first_check_id") // get "#uuid" value in properties, empty string will be return when not found. uuid := extractStringProperty(properties, "#uuid") if len(uuid) == 0 { uuid = generateUUID() } properties_summary, err := json.Marshal(properties) if err != nil { properties_summary = []byte{} } data := Data{ AccountId: accountId, DistinctId: distinctId, Type: dataType, Time: eventTime, Timestamp: time.Now().Unix(), EventName: eventName, EventId: eventId, FirstCheckId: firstCheckId, Ip: ip, UUID: uuid, Properties: properties, PropertiesSummary: string(properties_summary), } if len(appId) > 0 { data.AppId = appId } err = formatProperties(&data, ta) if err != nil { return err } return ta.consumer.Add(data) } // Deprecated: please use TDConsumer type Consumer interface { TDConsumer }