Bluesky app fork with some witchin' additions 💫

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)

authored by

Eric Bailey and committed by
GitHub
96f4f635 9e59a2ee

+63 -18
+3 -2
src/Navigation.tsx
··· 32 32 import {RouteParams, State} from '#/lib/routes/types' 33 33 import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' 34 34 import {bskyTitle} from '#/lib/strings/headings' 35 + import {logger} from '#/logger' 35 36 import {isNative, isWeb} from '#/platform/detection' 36 37 import {useModalControls} from '#/state/modals' 37 38 import {useUnreadNotifications} from '#/state/queries/notifications/unread' ··· 729 730 linking={LINKING} 730 731 theme={theme} 731 732 onStateChange={() => { 732 - logEvent('lake:router:navigate', { 733 + logger.metric('router:navigate', { 733 734 from: prevLoggedRouteName.current, 734 735 }) 735 736 prevLoggedRouteName.current = getCurrentRouteName() ··· 738 739 attachRouteToLogEvents(getCurrentRouteName) 739 740 logModuleInitTime() 740 741 onReady() 741 - logEvent('lake:router:navigate', {}) 742 + logger.metric('router:navigate', {}) 742 743 }}> 743 744 {children} 744 745 </NavigationContainer>
+2 -2
src/lib/statsig/events.ts src/logger/metrics.ts
··· 1 - export type LogEvents = { 1 + export type MetricEvents = { 2 2 // App events 3 3 init: { 4 4 initMs: number ··· 30 30 secondsActive: number 31 31 } 32 32 'state:foreground': {} 33 - 'lake:router:navigate': { 33 + 'router:navigate': { 34 34 from?: string 35 35 } 36 36 'deepLink:referrerReceived': {
+27 -8
src/lib/statsig/statsig.tsx
··· 5 5 6 6 import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info' 7 7 import {logger} from '#/logger' 8 + import {MetricEvents} from '#/logger/metrics' 8 9 import {isWeb} from '#/platform/detection' 9 10 import * as persisted from '#/state/persisted' 10 11 import {useSession} from '../../state/session' 11 12 import {timeout} from '../async/timeout' 12 13 import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback' 13 - import {LogEvents} from './events' 14 14 import {Gate} from './gates' 15 15 16 16 const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV' ··· 42 42 refUrl = decodeURIComponent(params.get('ref_url') ?? '') 43 43 } 44 44 45 - export type {LogEvents} 45 + export type {MetricEvents as LogEvents} 46 46 47 47 function createStatsigOptions(prefetchUsers: StatsigUser[]) { 48 48 return { ··· 91 91 } 92 92 } 93 93 94 - export function logEvent<E extends keyof LogEvents>( 94 + /** 95 + * @deprecated use `logger.metric()` instead 96 + */ 97 + export function logEvent<E extends keyof MetricEvents>( 95 98 eventName: E & string, 96 - rawMetadata: LogEvents[E] & FlatJSONRecord, 99 + rawMetadata: MetricEvents[E] & FlatJSONRecord, 100 + options: { 101 + /** 102 + * Send to our data lake only, not to StatSig 103 + */ 104 + lake?: boolean 105 + } = {lake: false}, 97 106 ) { 98 107 try { 99 108 const fullMetadata = toStringRecord(rawMetadata) 100 109 fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)' 101 110 if (Statsig.initializeCalled()) { 102 - Statsig.logEvent(eventName, null, fullMetadata) 111 + let ev: string = eventName 112 + if (options.lake) { 113 + ev = `lake:${ev}` 114 + } 115 + Statsig.logEvent(ev, null, fullMetadata) 116 + } 117 + /** 118 + * All datalake events should be sent using `logger.metric`, and we don't 119 + * want to double-emit logs to other transports. 120 + */ 121 + if (!options.lake) { 122 + logger.info(eventName, fullMetadata) 103 123 } 104 - logger.info(eventName, fullMetadata) 105 124 } catch (e) { 106 125 // A log should never interrupt the calling code, whatever happens. 107 126 logger.error('Failed to log an event', {message: e}) 108 127 } 109 128 } 110 129 111 - function toStringRecord<E extends keyof LogEvents>( 112 - metadata: LogEvents[E] & FlatJSONRecord, 130 + function toStringRecord<E extends keyof MetricEvents>( 131 + metadata: MetricEvents[E] & FlatJSONRecord, 113 132 ): Record<string, string> { 114 133 const record: Record<string, string> = {} 115 134 for (let key in metadata) {
+21
src/logger/index.ts
··· 1 1 import {nanoid} from 'nanoid/non-secure' 2 2 3 + import {logEvent} from '#/lib/statsig/statsig' 3 4 import {add} from '#/logger/logDump' 5 + import {MetricEvents} from '#/logger/metrics' 4 6 import {bitdriftTransport} from '#/logger/transports/bitdrift' 5 7 import {consoleTransport} from '#/logger/transports/console' 6 8 import {sentryTransport} from '#/logger/transports/sentry' ··· 87 89 88 90 error(error: Error | string, metadata: Metadata = {}) { 89 91 this.transport({level: LogLevel.Error, message: error, metadata}) 92 + } 93 + 94 + metric<E extends keyof MetricEvents>( 95 + event: E & string, 96 + metadata: MetricEvents[E], 97 + options: { 98 + /** 99 + * Optionally also send to StatSig 100 + */ 101 + statsig?: boolean 102 + } = {statsig: false}, 103 + ) { 104 + logEvent(event, metadata, { 105 + lake: !options.statsig, 106 + }) 107 + 108 + for (const transport of this.transports) { 109 + transport(LogLevel.Info, LogContext.Metric, event, metadata, Date.now()) 110 + } 90 111 } 91 112 92 113 addTransport(transport: Transport) {
+1 -2
src/logger/transports/bitdrift.ts
··· 18 18 ) => { 19 19 const log = logFunctions[level] 20 20 log(message.toString(), { 21 - // match Sentry payload 22 - context, 21 + __context__: context, 23 22 ...prepareMetadata(metadata), 24 23 }) 25 24 }
+1 -2
src/logger/transports/sentry.ts
··· 11 11 timestamp, 12 12 ) => { 13 13 const meta = { 14 - // match Bitdrift payload 15 - context, 14 + __context__: context, 16 15 ...prepareMetadata(metadata), 17 16 } 18 17 let _tags = tags || {}
+8 -2
src/logger/types.ts
··· 9 9 Notifications = 'notifications', 10 10 ConversationAgent = 'conversation-agent', 11 11 DMsAgent = 'dms-agent', 12 + 13 + /** 14 + * METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this 15 + * context 16 + */ 17 + Metric = 'metric', 12 18 } 13 19 14 20 export enum LogLevel { ··· 33 39 */ 34 40 export type Metadata = { 35 41 /** 36 - * Reserved for appending `LogContext` to logging payloads 42 + * Reserved for appending `LogContext` in logging payloads 37 43 */ 38 - context?: undefined 44 + __context__?: undefined 39 45 40 46 /** 41 47 * Applied as Sentry breadcrumb types. Defaults to `default`.