about summary refs log tree commit diff
path: root/src/logger
diff options
context:
space:
mode:
Diffstat (limited to 'src/logger')
-rw-r--r--src/logger/index.ts21
-rw-r--r--src/logger/metrics.ts308
-rw-r--r--src/logger/transports/bitdrift.ts3
-rw-r--r--src/logger/transports/sentry.ts3
-rw-r--r--src/logger/types.ts10
5 files changed, 339 insertions, 6 deletions
diff --git a/src/logger/index.ts b/src/logger/index.ts
index 410d29bb3..0a50a9d21 100644
--- a/src/logger/index.ts
+++ b/src/logger/index.ts
@@ -1,6 +1,8 @@
 import {nanoid} from 'nanoid/non-secure'
 
+import {logEvent} from '#/lib/statsig/statsig'
 import {add} from '#/logger/logDump'
+import {MetricEvents} from '#/logger/metrics'
 import {bitdriftTransport} from '#/logger/transports/bitdrift'
 import {consoleTransport} from '#/logger/transports/console'
 import {sentryTransport} from '#/logger/transports/sentry'
@@ -89,6 +91,25 @@ export class Logger {
     this.transport({level: LogLevel.Error, message: error, metadata})
   }
 
+  metric<E extends keyof MetricEvents>(
+    event: E & string,
+    metadata: MetricEvents[E],
+    options: {
+      /**
+       * Optionally also send to StatSig
+       */
+      statsig?: boolean
+    } = {statsig: false},
+  ) {
+    logEvent(event, metadata, {
+      lake: !options.statsig,
+    })
+
+    for (const transport of this.transports) {
+      transport(LogLevel.Info, LogContext.Metric, event, metadata, Date.now())
+    }
+  }
+
   addTransport(transport: Transport) {
     this.transports.push(transport)
     return () => {
diff --git a/src/logger/metrics.ts b/src/logger/metrics.ts
new file mode 100644
index 000000000..e3bd93314
--- /dev/null
+++ b/src/logger/metrics.ts
@@ -0,0 +1,308 @@
+export type MetricEvents = {
+  // 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': {}
+  '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/logger/transports/bitdrift.ts b/src/logger/transports/bitdrift.ts
index cf125c6e2..a407f9485 100644
--- a/src/logger/transports/bitdrift.ts
+++ b/src/logger/transports/bitdrift.ts
@@ -18,8 +18,7 @@ export const bitdriftTransport: Transport = (
 ) => {
   const log = logFunctions[level]
   log(message.toString(), {
-    // match Sentry payload
-    context,
+    __context__: context,
     ...prepareMetadata(metadata),
   })
 }
diff --git a/src/logger/transports/sentry.ts b/src/logger/transports/sentry.ts
index 890918d67..33dd78ec2 100644
--- a/src/logger/transports/sentry.ts
+++ b/src/logger/transports/sentry.ts
@@ -11,8 +11,7 @@ export const sentryTransport: Transport = (
   timestamp,
 ) => {
   const meta = {
-    // match Bitdrift payload
-    context,
+    __context__: context,
     ...prepareMetadata(metadata),
   }
   let _tags = tags || {}
diff --git a/src/logger/types.ts b/src/logger/types.ts
index 517893d29..9110a8c6f 100644
--- a/src/logger/types.ts
+++ b/src/logger/types.ts
@@ -9,6 +9,12 @@ export enum LogContext {
   Notifications = 'notifications',
   ConversationAgent = 'conversation-agent',
   DMsAgent = 'dms-agent',
+
+  /**
+   * METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this
+   * context
+   */
+  Metric = 'metric',
 }
 
 export enum LogLevel {
@@ -33,9 +39,9 @@ export type Transport = (
  */
 export type Metadata = {
   /**
-   * Reserved for appending `LogContext` to logging payloads
+   * Reserved for appending `LogContext` in logging payloads
    */
-  context?: undefined
+  __context__?: undefined
 
   /**
    * Applied as Sentry breadcrumb types. Defaults to `default`.