1 package ldevents 2 3 import ( 4 "encoding/json" 5 6 "github.com/launchdarkly/go-sdk-common/v3/ldcontext" 7 "github.com/launchdarkly/go-sdk-common/v3/ldreason" 8 "github.com/launchdarkly/go-sdk-common/v3/ldtime" 9 "github.com/launchdarkly/go-sdk-common/v3/ldvalue" 10 ) 11 12 // EventInputContext represents context information that is being used as part of the inputs to an 13 // event-generating action. It is a combination of the standard Context struct with additional 14 // information that may be relevant outside of the standard SDK event generation context. 15 // 16 // Specifically, this is because ld-relay uses go-sdk-events to post-process events it has 17 // received from the PHP SDK. In this scenario the PHP SDK will have already applied the 18 // private-attribute-redaction logic, so there is no need to do any further transformation 19 // of the context. 20 // 21 // That requirement is specific to ld-relay. In regular usage of the Go SDK, we always just use the 22 // plain Context constructor. 23 type EventInputContext struct { 24 context ldcontext.Context 25 preserialized json.RawMessage 26 } 27 28 // Context creates an EventInputContext that is exactly equivalent to the given Context. 29 func Context(context ldcontext.Context) EventInputContext { 30 return EventInputContext{context: context} 31 } 32 33 // PreserializedContext creates an EventInputContext that contains both a Context and its already-computed 34 // JSON representation. This representation will be written directly to the output with no further 35 // processing. The properties of the wrapped Context are not important except for its Kind, Key, and 36 // FullyQualifiedKey, which are used for context deduplication. 37 func PreserializedContext(context ldcontext.Context, jsonData json.RawMessage) EventInputContext { 38 return EventInputContext{context: context, preserialized: jsonData} 39 } 40 41 // BaseEvent provides properties common to all events. 42 type BaseEvent struct { 43 CreationDate ldtime.UnixMillisecondTime 44 Context EventInputContext 45 } 46 47 // EvaluationData is generated by evaluating a feature flag or one of a flag's prerequisites. 48 type EvaluationData struct { 49 BaseEvent 50 // Key is the flag key. 51 Key string 52 // Variation is the result variation index. It is empty if evaluation failed. 53 Variation ldvalue.OptionalInt 54 // Value is the result value. 55 Value ldvalue.Value 56 // Default is the default value that was passed in by the application. 57 Default ldvalue.Value 58 // Version is the flag version. It is empty if the flag was not found. 59 Version ldvalue.OptionalInt 60 // PrereqOf is normally empty, but if this evaluation was done for a prerequisite, it is the key of the 61 // original key that referenced this flag as a prerequisite. 62 PrereqOf ldvalue.OptionalString 63 // Reason is the evaluation reason, if the reason should be included in the event, or empty otherwise. 64 Reason ldreason.EvaluationReason 65 // RequireFullEvent is true if an individual evaluation event should be included in the output event data, 66 // or false if this evaluation should only produce summary data (and potentially an index event). 67 RequireFullEvent bool 68 // DebugEventsUntilDate is non-zero if event debugging has been temporarily enabled for the flag. It is the 69 // time at which debugging mode should expire. 70 DebugEventsUntilDate ldtime.UnixMillisecondTime 71 // debug is true if this is a copy of an evaluation event that we have queued to be output as a debug 72 // event. This field is not exported because it is never part of the input parameters from the application; 73 // we debug events only internally, based on DebugEventsUntilDate. 74 debug bool 75 } 76 77 // CustomEventData is generated by calling the client's Track method. 78 type CustomEventData struct { 79 BaseEvent 80 Key string 81 Data ldvalue.Value 82 HasMetric bool 83 MetricValue float64 84 } 85 86 // IdentifyEventData is generated by calling the client's Identify method. 87 type IdentifyEventData struct { 88 BaseEvent 89 } 90 91 // indexEvent is generated internally to capture user details from other events. It is an implementation 92 // detail of DefaultEventProcessor, so it is not exported. 93 type indexEvent struct { 94 BaseEvent 95 } 96 97 // rawEvent is used internally when the Relay Proxy needs to inject a JSON event into the outbox that 98 // will be sent exactly as is with no processing. 99 type rawEvent struct { 100 data json.RawMessage 101 } 102 103 // FlagEventProperties contains basic information about a feature flag that the events package needs. This allows 104 // go-sdk-events to be implemented independently of go-server-side-evaluation where the flag model is defined. It 105 // also allows us to use go-sdk-events in a client-side Go SDK, where the flag model will be different, if we ever 106 // implement a client-side Go SDK. 107 type FlagEventProperties struct { 108 // Key is the feature flag key. 109 Key string 110 // Version is the feature flag version. 111 Version int 112 // RequireFullEvent is true if the flag has been configured to always generate detailed event data. 113 RequireFullEvent bool 114 // DebugEventsUntilDate is non-zero if event debugging has been temporarily enabled for the flag. It is the 115 // time at which debugging mode should expire. 116 DebugEventsUntilDate ldtime.UnixMillisecondTime 117 } 118 119 // EventFactory is a configurable factory for event objects. 120 type EventFactory struct { 121 includeReasons bool 122 timeFn func() ldtime.UnixMillisecondTime 123 } 124 125 // NewEventFactory creates an EventFactory. 126 // 127 // The includeReasons parameter is true if evaluation events should always include the EvaluationReason (this is 128 // used by the SDK when one of the "VariationDetail" methods is called). The timeFn parameter is normally nil but 129 // can be used to instrument the EventFactory with a source of time data other than the standard clock. 130 // 131 // The isExperimentFn parameter is necessary to provide the additional experimentation behavior that is 132 func NewEventFactory(includeReasons bool, timeFn func() ldtime.UnixMillisecondTime) EventFactory { 133 if timeFn == nil { 134 timeFn = ldtime.UnixMillisNow 135 } 136 return EventFactory{includeReasons, timeFn} 137 } 138 139 // NewUnknownFlagEvaluationData creates EvaluationData for a missing flag. 140 func (f EventFactory) NewUnknownFlagEvaluationData( 141 key string, 142 context EventInputContext, 143 defaultVal ldvalue.Value, 144 reason ldreason.EvaluationReason, 145 ) EvaluationData { 146 ed := EvaluationData{ 147 BaseEvent: BaseEvent{ 148 CreationDate: f.timeFn(), 149 Context: context, 150 }, 151 Key: key, 152 Value: defaultVal, 153 Default: defaultVal, 154 } 155 if f.includeReasons { 156 ed.Reason = reason 157 } 158 return ed 159 } 160 161 // NewEvaluationData creates EvaluationData for an existing flag. 162 // 163 // The isExperiment parameter, if true, means that a full evaluation event should be generated (regardless 164 // of whether flagProps.RequireFullEvent is true) and the evaluation reason should be included in the event 165 // (even if it normally would not have been). In the server-side SDK, that is determined by the IsExperiment 166 // field returned by the evaluator. 167 func (f EventFactory) NewEvaluationData( 168 flagProps FlagEventProperties, 169 context EventInputContext, 170 detail ldreason.EvaluationDetail, 171 isExperiment bool, 172 defaultVal ldvalue.Value, 173 prereqOf string, 174 ) EvaluationData { 175 ed := EvaluationData{ 176 BaseEvent: BaseEvent{ 177 CreationDate: f.timeFn(), 178 Context: context, 179 }, 180 Key: flagProps.Key, 181 Version: ldvalue.NewOptionalInt(flagProps.Version), 182 Variation: detail.VariationIndex, 183 Value: detail.Value, 184 Default: defaultVal, 185 RequireFullEvent: isExperiment || flagProps.RequireFullEvent, 186 DebugEventsUntilDate: flagProps.DebugEventsUntilDate, 187 } 188 if f.includeReasons || isExperiment { 189 ed.Reason = detail.Reason 190 } 191 if prereqOf != "" { 192 ed.PrereqOf = ldvalue.NewOptionalString(prereqOf) 193 } 194 return ed 195 } 196 197 // NewCustomEventData creates input parameters for a custom event. No event is actually generated until you 198 // call EventProcessor.RecordCustomEvent. 199 func (f EventFactory) NewCustomEventData( 200 key string, 201 context EventInputContext, 202 data ldvalue.Value, 203 withMetric bool, 204 metricValue float64, 205 ) CustomEventData { 206 ce := CustomEventData{ 207 BaseEvent: BaseEvent{ 208 CreationDate: f.timeFn(), 209 Context: context, 210 }, 211 Key: key, 212 Data: data, 213 HasMetric: withMetric, 214 MetricValue: metricValue, 215 } 216 return ce 217 } 218 219 // NewIdentifyEventData constructs input parameters for an identify event. No event is actually generated until you 220 // call EventProcessor.RecordIdentifyEvent. 221 func (f EventFactory) NewIdentifyEventData(context EventInputContext) IdentifyEventData { 222 return IdentifyEventData{ 223 BaseEvent: BaseEvent{ 224 CreationDate: f.timeFn(), 225 Context: context, 226 }, 227 } 228 } 229