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)
This commit is contained in:
Eric Bailey
2025-02-28 17:13:49 -06:00
committed by GitHub
parent 9e59a2eef2
commit 96f4f6359a
7 changed files with 63 additions and 18 deletions
-308
View File
@@ -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': {}
}
+27 -8
View File
@@ -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) {