package ldevents import ( "encoding/json" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "github.com/launchdarkly/go-sdk-common/v3/ldreason" "github.com/launchdarkly/go-sdk-common/v3/ldtime" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" ) // EventInputContext represents context information that is being used as part of the inputs to an // event-generating action. It is a combination of the standard Context struct with additional // information that may be relevant outside of the standard SDK event generation context. // // Specifically, this is because ld-relay uses go-sdk-events to post-process events it has // received from the PHP SDK. In this scenario the PHP SDK will have already applied the // private-attribute-redaction logic, so there is no need to do any further transformation // of the context. // // That requirement is specific to ld-relay. In regular usage of the Go SDK, we always just use the // plain Context constructor. type EventInputContext struct { context ldcontext.Context preserialized json.RawMessage } // Context creates an EventInputContext that is exactly equivalent to the given Context. func Context(context ldcontext.Context) EventInputContext { return EventInputContext{context: context} } // PreserializedContext creates an EventInputContext that contains both a Context and its already-computed // JSON representation. This representation will be written directly to the output with no further // processing. The properties of the wrapped Context are not important except for its Kind, Key, and // FullyQualifiedKey, which are used for context deduplication. func PreserializedContext(context ldcontext.Context, jsonData json.RawMessage) EventInputContext { return EventInputContext{context: context, preserialized: jsonData} } // BaseEvent provides properties common to all events. type BaseEvent struct { CreationDate ldtime.UnixMillisecondTime Context EventInputContext } // EvaluationData is generated by evaluating a feature flag or one of a flag's prerequisites. type EvaluationData struct { BaseEvent // Key is the flag key. Key string // Variation is the result variation index. It is empty if evaluation failed. Variation ldvalue.OptionalInt // Value is the result value. Value ldvalue.Value // Default is the default value that was passed in by the application. Default ldvalue.Value // Version is the flag version. It is empty if the flag was not found. Version ldvalue.OptionalInt // PrereqOf is normally empty, but if this evaluation was done for a prerequisite, it is the key of the // original key that referenced this flag as a prerequisite. PrereqOf ldvalue.OptionalString // Reason is the evaluation reason, if the reason should be included in the event, or empty otherwise. Reason ldreason.EvaluationReason // RequireFullEvent is true if an individual evaluation event should be included in the output event data, // or false if this evaluation should only produce summary data (and potentially an index event). RequireFullEvent bool // DebugEventsUntilDate is non-zero if event debugging has been temporarily enabled for the flag. It is the // time at which debugging mode should expire. DebugEventsUntilDate ldtime.UnixMillisecondTime // debug is true if this is a copy of an evaluation event that we have queued to be output as a debug // event. This field is not exported because it is never part of the input parameters from the application; // we debug events only internally, based on DebugEventsUntilDate. debug bool } // CustomEventData is generated by calling the client's Track method. type CustomEventData struct { BaseEvent Key string Data ldvalue.Value HasMetric bool MetricValue float64 } // IdentifyEventData is generated by calling the client's Identify method. type IdentifyEventData struct { BaseEvent } // indexEvent is generated internally to capture user details from other events. It is an implementation // detail of DefaultEventProcessor, so it is not exported. type indexEvent struct { BaseEvent } // rawEvent is used internally when the Relay Proxy needs to inject a JSON event into the outbox that // will be sent exactly as is with no processing. type rawEvent struct { data json.RawMessage } // FlagEventProperties contains basic information about a feature flag that the events package needs. This allows // go-sdk-events to be implemented independently of go-server-side-evaluation where the flag model is defined. It // also allows us to use go-sdk-events in a client-side Go SDK, where the flag model will be different, if we ever // implement a client-side Go SDK. type FlagEventProperties struct { // Key is the feature flag key. Key string // Version is the feature flag version. Version int // RequireFullEvent is true if the flag has been configured to always generate detailed event data. RequireFullEvent bool // DebugEventsUntilDate is non-zero if event debugging has been temporarily enabled for the flag. It is the // time at which debugging mode should expire. DebugEventsUntilDate ldtime.UnixMillisecondTime } // EventFactory is a configurable factory for event objects. type EventFactory struct { includeReasons bool timeFn func() ldtime.UnixMillisecondTime } // NewEventFactory creates an EventFactory. // // The includeReasons parameter is true if evaluation events should always include the EvaluationReason (this is // used by the SDK when one of the "VariationDetail" methods is called). The timeFn parameter is normally nil but // can be used to instrument the EventFactory with a source of time data other than the standard clock. // // The isExperimentFn parameter is necessary to provide the additional experimentation behavior that is func NewEventFactory(includeReasons bool, timeFn func() ldtime.UnixMillisecondTime) EventFactory { if timeFn == nil { timeFn = ldtime.UnixMillisNow } return EventFactory{includeReasons, timeFn} } // NewUnknownFlagEvaluationData creates EvaluationData for a missing flag. func (f EventFactory) NewUnknownFlagEvaluationData( key string, context EventInputContext, defaultVal ldvalue.Value, reason ldreason.EvaluationReason, ) EvaluationData { ed := EvaluationData{ BaseEvent: BaseEvent{ CreationDate: f.timeFn(), Context: context, }, Key: key, Value: defaultVal, Default: defaultVal, } if f.includeReasons { ed.Reason = reason } return ed } // NewEvaluationData creates EvaluationData for an existing flag. // // The isExperiment parameter, if true, means that a full evaluation event should be generated (regardless // of whether flagProps.RequireFullEvent is true) and the evaluation reason should be included in the event // (even if it normally would not have been). In the server-side SDK, that is determined by the IsExperiment // field returned by the evaluator. func (f EventFactory) NewEvaluationData( flagProps FlagEventProperties, context EventInputContext, detail ldreason.EvaluationDetail, isExperiment bool, defaultVal ldvalue.Value, prereqOf string, ) EvaluationData { ed := EvaluationData{ BaseEvent: BaseEvent{ CreationDate: f.timeFn(), Context: context, }, Key: flagProps.Key, Version: ldvalue.NewOptionalInt(flagProps.Version), Variation: detail.VariationIndex, Value: detail.Value, Default: defaultVal, RequireFullEvent: isExperiment || flagProps.RequireFullEvent, DebugEventsUntilDate: flagProps.DebugEventsUntilDate, } if f.includeReasons || isExperiment { ed.Reason = detail.Reason } if prereqOf != "" { ed.PrereqOf = ldvalue.NewOptionalString(prereqOf) } return ed } // NewCustomEventData creates input parameters for a custom event. No event is actually generated until you // call EventProcessor.RecordCustomEvent. func (f EventFactory) NewCustomEventData( key string, context EventInputContext, data ldvalue.Value, withMetric bool, metricValue float64, ) CustomEventData { ce := CustomEventData{ BaseEvent: BaseEvent{ CreationDate: f.timeFn(), Context: context, }, Key: key, Data: data, HasMetric: withMetric, MetricValue: metricValue, } return ce } // NewIdentifyEventData constructs input parameters for an identify event. No event is actually generated until you // call EventProcessor.RecordIdentifyEvent. func (f EventFactory) NewIdentifyEventData(context EventInputContext) IdentifyEventData { return IdentifyEventData{ BaseEvent: BaseEvent{ CreationDate: f.timeFn(), Context: context, }, } }