about summary refs log tree commit diff
path: root/src/lib/statsig
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-02-28 17:13:49 -0600
committerGitHub <noreply@github.com>2025-02-28 17:13:49 -0600
commit96f4f6359add6a4f2a37df8f17cf3f2f59f0a2a6 (patch)
treeafabc0fe9628a66f1db69f9e07b824db725aa6e1 /src/lib/statsig
parent9e59a2eef2f82511a9a9a056dbdb0c62fe2f61c6 (diff)
downloadvoidsky-96f4f6359add6a4f2a37df8f17cf3f2f59f0a2a6.tar.zst
Logger metrics (#7867)
* Adjust datalake abstraction

(cherry picked from commit 8ba6a8d45b1bd5698afbd06d9e858a91789f0ea6)

* Just be really really specific

(cherry picked from commit 920198959659329a7f7f7282a1293aaad198d8e3)

* Add metric method to logger, replace datalake calls with new method

(cherry picked from commit 7a026bbeae75514b64f928d7ff59707c518fd5e5)

* Clarify types

(cherry picked from commit 422b150deb158a70ef37e8a456d91bf26cd0b1bc)
Diffstat (limited to 'src/lib/statsig')
-rw-r--r--src/lib/statsig/events.ts308
-rw-r--r--src/lib/statsig/statsig.tsx35
2 files changed, 27 insertions, 316 deletions
diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts
deleted file mode 100644
index 519e3997e..000000000
--- a/src/lib/statsig/events.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-export type LogEvents = {
-  // App events
-  init: {
-    initMs: number
-  }
-  'account:loggedIn': {
-    logContext:
-      | 'LoginForm'
-      | 'SwitchAccount'
-      | 'ChooseAccountForm'
-      | 'Settings'
-      | 'Notification'
-    withPassword: boolean
-  }
-  'account:loggedOut': {
-    logContext:
-      | 'SwitchAccount'
-      | 'Settings'
-      | 'SignupQueued'
-      | 'Deactivated'
-      | 'Takendown'
-    scope: 'current' | 'every'
-  }
-  'notifications:openApp': {}
-  'notifications:request': {
-    context: 'StartOnboarding' | 'AfterOnboarding' | 'Login' | 'Home'
-    status: 'granted' | 'denied' | 'undetermined'
-  }
-  'state:background': {
-    secondsActive: number
-  }
-  'state:foreground': {}
-  'lake:router:navigate': {
-    from?: string
-  }
-  'deepLink:referrerReceived': {
-    to: string
-    referrer: string
-    hostname: string
-  }
-
-  // Screen events
-  'splash:signInPressed': {}
-  'splash:createAccountPressed': {}
-  'signup:nextPressed': {
-    activeStep: number
-    phoneVerificationRequired?: boolean
-  }
-  'signup:backPressed': {
-    activeStep: number
-  }
-  'signup:captchaSuccess': {}
-  'signup:captchaFailure': {}
-  'signin:hostingProviderPressed': {
-    hostingProviderDidChange: boolean
-  }
-  'signin:hostingProviderFailedResolution': {}
-  'signin:success': {
-    failedAttemptsCount: number
-    isUsingCustomProvider: boolean
-    timeTakenSeconds: number
-  }
-  'signin:backPressed': {
-    failedAttemptsCount: number
-  }
-  'signin:forgotPasswordPressed': {}
-  'signin:passwordReset': {}
-  'signin:passwordResetSuccess': {}
-  'signin:passwordResetFailure': {}
-  'onboarding:interests:nextPressed': {
-    selectedInterests: string[]
-    selectedInterestsLength: number
-  }
-  'onboarding:suggestedAccounts:nextPressed': {
-    selectedAccountsLength: number
-    skipped: boolean
-  }
-  'onboarding:followingFeed:nextPressed': {}
-  'onboarding:algoFeeds:nextPressed': {
-    selectedPrimaryFeeds: string[]
-    selectedPrimaryFeedsLength: number
-    selectedSecondaryFeeds: string[]
-    selectedSecondaryFeedsLength: number
-  }
-  'onboarding:topicalFeeds:nextPressed': {
-    selectedFeeds: string[]
-    selectedFeedsLength: number
-  }
-  'onboarding:moderation:nextPressed': {}
-  'onboarding:profile:nextPressed': {}
-  'onboarding:finished:nextPressed': {
-    usedStarterPack: boolean
-    starterPackName?: string
-    starterPackCreator?: string
-    starterPackUri?: string
-    profilesFollowed: number
-    feedsPinned: number
-  }
-  'onboarding:finished:avatarResult': {
-    avatarResult: 'default' | 'created' | 'uploaded'
-  }
-  'home:feedDisplayed': {
-    feedUrl: string
-    feedType: string
-    index: number
-  }
-  'feed:endReached': {
-    feedUrl: string
-    feedType: string
-    itemCount: number
-  }
-  'feed:refresh': {
-    feedUrl: string
-    feedType: string
-    reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
-  }
-  'discover:showMore': {
-    feedContext: string
-  }
-  'discover:showLess': {
-    feedContext: string
-  }
-  'discover:clickthrough': {
-    count: number
-  }
-  'discover:engaged': {
-    count: number
-  }
-  'discover:seen': {
-    count: number
-  }
-
-  'composer:gif:open': {}
-  'composer:gif:select': {}
-
-  // Data events
-  'account:create:begin': {}
-  'account:create:success': {}
-  'post:create': {
-    imageCount: number
-    isReply: boolean
-    isPartOfThread: boolean
-    hasLink: boolean
-    hasQuote: boolean
-    langs: string
-    logContext: 'Composer'
-  }
-  'thread:create': {
-    postCount: number
-    isReply: boolean
-  }
-  'post:like': {
-    doesLikerFollowPoster: boolean | undefined
-    doesPosterFollowLiker: boolean | undefined
-    likerClout: number | undefined
-    postClout: number | undefined
-    logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
-  }
-  'post:repost': {
-    logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
-  }
-  'post:unlike': {
-    logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
-  }
-  'post:unrepost': {
-    logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
-  }
-  'post:mute': {}
-  'post:unmute': {}
-  'post:pin': {}
-  'post:unpin': {}
-  'profile:follow': {
-    didBecomeMutual: boolean | undefined
-    followeeClout: number | undefined
-    followerClout: number | undefined
-    logContext:
-      | 'RecommendedFollowsItem'
-      | 'PostThreadItem'
-      | 'ProfileCard'
-      | 'ProfileHeader'
-      | 'ProfileHeaderSuggestedFollows'
-      | 'ProfileMenu'
-      | 'ProfileHoverCard'
-      | 'AvatarButton'
-      | 'StarterPackProfilesList'
-      | 'FeedInterstitial'
-      | 'ProfileHeaderSuggestedFollows'
-      | 'PostOnboardingFindFollows'
-      | 'ImmersiveVideo'
-  }
-  'suggestedUser:follow': {
-    logContext:
-      | 'Explore'
-      | 'InterstitialDiscover'
-      | 'InterstitialProfile'
-      | 'Profile'
-    location: 'Card' | 'Profile'
-    recId?: number
-    position: number
-  }
-  'suggestedUser:press': {
-    logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile'
-    recId?: number
-    position: number
-  }
-  'suggestedUser:seen': {
-    logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile'
-    recId?: number
-    position: number
-  }
-  'profile:unfollow': {
-    logContext:
-      | 'RecommendedFollowsItem'
-      | 'PostThreadItem'
-      | 'ProfileCard'
-      | 'ProfileHeader'
-      | 'ProfileHeaderSuggestedFollows'
-      | 'ProfileMenu'
-      | 'ProfileHoverCard'
-      | 'Chat'
-      | 'AvatarButton'
-      | 'StarterPackProfilesList'
-      | 'FeedInterstitial'
-      | 'ProfileHeaderSuggestedFollows'
-      | 'PostOnboardingFindFollows'
-      | 'ImmersiveVideo'
-  }
-  'chat:create': {
-    logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog'
-  }
-  'chat:open': {
-    logContext:
-      | 'ProfileHeader'
-      | 'NewChatDialog'
-      | 'ChatsList'
-      | 'SendViaChatDialog'
-  }
-  'starterPack:share': {
-    starterPack: string
-    shareType: 'link' | 'qrcode'
-    qrShareType?: 'save' | 'copy' | 'share'
-  }
-  'starterPack:followAll': {
-    logContext: 'StarterPackProfilesList' | 'Onboarding'
-    starterPack: string
-    count: number
-  }
-  'starterPack:delete': {}
-  'starterPack:create': {
-    setName: boolean
-    setDescription: boolean
-    profilesCount: number
-    feedsCount: number
-  }
-  'starterPack:ctaPress': {
-    starterPack: string
-  }
-  'starterPack:opened': {
-    starterPack: string
-  }
-  'link:clicked': {
-    url: string
-    domain: string
-  }
-
-  'feed:interstitial:feedCard:press': {}
-
-  'profile:header:suggestedFollowsCard:press': {}
-
-  'test:all:always': {}
-  'test:all:sometimes': {}
-  'test:all:boosted_by_gate1': {reason: 'base' | 'gate1'}
-  'test:all:boosted_by_gate2': {reason: 'base' | 'gate2'}
-  'test:all:boosted_by_both': {reason: 'base' | 'gate1' | 'gate2'}
-  'test:gate1:always': {}
-  'test:gate1:sometimes': {}
-  'test:gate2:always': {}
-  'test:gate2:sometimes': {}
-
-  'tmd:share': {}
-  'tmd:download': {}
-  'tmd:post': {}
-
-  'trendingTopics:show': {
-    context: 'settings'
-  }
-  'trendingTopics:hide': {
-    context: 'settings' | 'sidebar' | 'interstitial' | 'explore:trending'
-  }
-  'trendingTopic:click': {
-    context: 'sidebar' | 'interstitial' | 'explore'
-  }
-  'recommendedTopic:click': {
-    context: 'explore'
-  }
-  'trendingVideos:show': {
-    context: 'settings'
-  }
-  'trendingVideos:hide': {
-    context: 'settings' | 'interstitial:discover' | 'interstitial:explore'
-  }
-  'videoCard:click': {
-    context: 'interstitial:discover' | 'interstitial:explore' | 'feed'
-  }
-
-  'progressGuide:hide': {}
-  'progressGuide:followDialog:open': {}
-}
diff --git a/src/lib/statsig/statsig.tsx b/src/lib/statsig/statsig.tsx
index a64c710ea..7f04da126 100644
--- a/src/lib/statsig/statsig.tsx
+++ b/src/lib/statsig/statsig.tsx
@@ -5,12 +5,12 @@ import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
 
 import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
 import {logger} from '#/logger'
+import {MetricEvents} from '#/logger/metrics'
 import {isWeb} from '#/platform/detection'
 import * as persisted from '#/state/persisted'
 import {useSession} from '../../state/session'
 import {timeout} from '../async/timeout'
 import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
-import {LogEvents} from './events'
 import {Gate} from './gates'
 
 const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
@@ -42,7 +42,7 @@ if (isWeb && typeof window !== 'undefined') {
   refUrl = decodeURIComponent(params.get('ref_url') ?? '')
 }
 
-export type {LogEvents}
+export type {MetricEvents as LogEvents}
 
 function createStatsigOptions(prefetchUsers: StatsigUser[]) {
   return {
@@ -91,25 +91,44 @@ export function toClout(n: number | null | undefined): number | undefined {
   }
 }
 
-export function logEvent<E extends keyof LogEvents>(
+/**
+ * @deprecated use `logger.metric()` instead
+ */
+export function logEvent<E extends keyof MetricEvents>(
   eventName: E & string,
-  rawMetadata: LogEvents[E] & FlatJSONRecord,
+  rawMetadata: MetricEvents[E] & FlatJSONRecord,
+  options: {
+    /**
+     * Send to our data lake only, not to StatSig
+     */
+    lake?: boolean
+  } = {lake: false},
 ) {
   try {
     const fullMetadata = toStringRecord(rawMetadata)
     fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
     if (Statsig.initializeCalled()) {
-      Statsig.logEvent(eventName, null, fullMetadata)
+      let ev: string = eventName
+      if (options.lake) {
+        ev = `lake:${ev}`
+      }
+      Statsig.logEvent(ev, null, fullMetadata)
+    }
+    /**
+     * All datalake events should be sent using `logger.metric`, and we don't
+     * want to double-emit logs to other transports.
+     */
+    if (!options.lake) {
+      logger.info(eventName, fullMetadata)
     }
-    logger.info(eventName, fullMetadata)
   } catch (e) {
     // A log should never interrupt the calling code, whatever happens.
     logger.error('Failed to log an event', {message: e})
   }
 }
 
-function toStringRecord<E extends keyof LogEvents>(
-  metadata: LogEvents[E] & FlatJSONRecord,
+function toStringRecord<E extends keyof MetricEvents>(
+  metadata: MetricEvents[E] & FlatJSONRecord,
 ): Record<string, string> {
   const record: Record<string, string> = {}
   for (let key in metadata) {