source dump of claude code
at main 114 lines 3.5 kB view raw
1/** 2 * Analytics sink implementation 3 * 4 * This module contains the actual analytics routing logic and should be 5 * initialized during app startup. It routes events to Datadog and 1P event 6 * logging. 7 * 8 * Usage: Call initializeAnalyticsSink() during app startup to attach the sink. 9 */ 10 11import { trackDatadogEvent } from './datadog.js' 12import { logEventTo1P, shouldSampleEvent } from './firstPartyEventLogger.js' 13import { checkStatsigFeatureGate_CACHED_MAY_BE_STALE } from './growthbook.js' 14import { attachAnalyticsSink, stripProtoFields } from './index.js' 15import { isSinkKilled } from './sinkKillswitch.js' 16 17// Local type matching the logEvent metadata signature 18type LogEventMetadata = { [key: string]: boolean | number | undefined } 19 20const DATADOG_GATE_NAME = 'tengu_log_datadog_events' 21 22// Module-level gate state - starts undefined, initialized during startup 23let isDatadogGateEnabled: boolean | undefined = undefined 24 25/** 26 * Check if Datadog tracking is enabled. 27 * Falls back to cached value from previous session if not yet initialized. 28 */ 29function shouldTrackDatadog(): boolean { 30 if (isSinkKilled('datadog')) { 31 return false 32 } 33 if (isDatadogGateEnabled !== undefined) { 34 return isDatadogGateEnabled 35 } 36 37 // Fallback to cached value from previous session 38 try { 39 return checkStatsigFeatureGate_CACHED_MAY_BE_STALE(DATADOG_GATE_NAME) 40 } catch { 41 return false 42 } 43} 44 45/** 46 * Log an event (synchronous implementation) 47 */ 48function logEventImpl(eventName: string, metadata: LogEventMetadata): void { 49 // Check if this event should be sampled 50 const sampleResult = shouldSampleEvent(eventName) 51 52 // If sample result is 0, the event was not selected for logging 53 if (sampleResult === 0) { 54 return 55 } 56 57 // If sample result is a positive number, add it to metadata 58 const metadataWithSampleRate = 59 sampleResult !== null 60 ? { ...metadata, sample_rate: sampleResult } 61 : metadata 62 63 if (shouldTrackDatadog()) { 64 // Datadog is a general-access backend — strip _PROTO_* keys 65 // (unredacted PII-tagged values meant only for the 1P privileged column). 66 void trackDatadogEvent(eventName, stripProtoFields(metadataWithSampleRate)) 67 } 68 69 // 1P receives the full payload including _PROTO_* — the exporter 70 // destructures and routes those keys to proto fields itself. 71 logEventTo1P(eventName, metadataWithSampleRate) 72} 73 74/** 75 * Log an event (asynchronous implementation) 76 * 77 * With Segment removed the two remaining sinks are fire-and-forget, so this 78 * just wraps the sync impl — kept to preserve the sink interface contract. 79 */ 80function logEventAsyncImpl( 81 eventName: string, 82 metadata: LogEventMetadata, 83): Promise<void> { 84 logEventImpl(eventName, metadata) 85 return Promise.resolve() 86} 87 88/** 89 * Initialize analytics gates during startup. 90 * 91 * Updates gate values from server. Early events use cached values from previous 92 * session to avoid data loss during initialization. 93 * 94 * Called from main.tsx during setupBackend(). 95 */ 96export function initializeAnalyticsGates(): void { 97 isDatadogGateEnabled = 98 checkStatsigFeatureGate_CACHED_MAY_BE_STALE(DATADOG_GATE_NAME) 99} 100 101/** 102 * Initialize the analytics sink. 103 * 104 * Call this during app startup to attach the analytics backend. 105 * Any events logged before this is called will be queued and drained. 106 * 107 * Idempotent: safe to call multiple times (subsequent calls are no-ops). 108 */ 109export function initializeAnalyticsSink(): void { 110 attachAnalyticsSink({ 111 logEvent: logEventImpl, 112 logEventAsync: logEventAsyncImpl, 113 }) 114}