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:
+3
-2
@@ -32,6 +32,7 @@ import {
|
|||||||
import {RouteParams, State} from '#/lib/routes/types'
|
import {RouteParams, State} from '#/lib/routes/types'
|
||||||
import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
|
import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
|
||||||
import {bskyTitle} from '#/lib/strings/headings'
|
import {bskyTitle} from '#/lib/strings/headings'
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
|
||||||
@@ -729,7 +730,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
linking={LINKING}
|
linking={LINKING}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
onStateChange={() => {
|
onStateChange={() => {
|
||||||
logEvent('lake:router:navigate', {
|
logger.metric('router:navigate', {
|
||||||
from: prevLoggedRouteName.current,
|
from: prevLoggedRouteName.current,
|
||||||
})
|
})
|
||||||
prevLoggedRouteName.current = getCurrentRouteName()
|
prevLoggedRouteName.current = getCurrentRouteName()
|
||||||
@@ -738,7 +739,7 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
|||||||
attachRouteToLogEvents(getCurrentRouteName)
|
attachRouteToLogEvents(getCurrentRouteName)
|
||||||
logModuleInitTime()
|
logModuleInitTime()
|
||||||
onReady()
|
onReady()
|
||||||
logEvent('lake:router:navigate', {})
|
logger.metric('router:navigate', {})
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {Statsig, StatsigProvider} from 'statsig-react-native-expo'
|
|||||||
|
|
||||||
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
|
import {BUNDLE_DATE, BUNDLE_IDENTIFIER, IS_TESTFLIGHT} from '#/lib/app-info'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {MetricEvents} from '#/logger/metrics'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {useSession} from '../../state/session'
|
import {useSession} from '../../state/session'
|
||||||
import {timeout} from '../async/timeout'
|
import {timeout} from '../async/timeout'
|
||||||
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '../hooks/useNonReactiveCallback'
|
||||||
import {LogEvents} from './events'
|
|
||||||
import {Gate} from './gates'
|
import {Gate} from './gates'
|
||||||
|
|
||||||
const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
|
const SDK_KEY = 'client-SXJakO39w9vIhl3D44u8UupyzFl4oZ2qPIkjwcvuPsV'
|
||||||
@@ -42,7 +42,7 @@ if (isWeb && typeof window !== 'undefined') {
|
|||||||
refUrl = decodeURIComponent(params.get('ref_url') ?? '')
|
refUrl = decodeURIComponent(params.get('ref_url') ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
export type {LogEvents}
|
export type {MetricEvents as LogEvents}
|
||||||
|
|
||||||
function createStatsigOptions(prefetchUsers: StatsigUser[]) {
|
function createStatsigOptions(prefetchUsers: StatsigUser[]) {
|
||||||
return {
|
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,
|
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 {
|
try {
|
||||||
const fullMetadata = toStringRecord(rawMetadata)
|
const fullMetadata = toStringRecord(rawMetadata)
|
||||||
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
|
fullMetadata.routeName = getCurrentRouteName() ?? '(Uninitialized)'
|
||||||
if (Statsig.initializeCalled()) {
|
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) {
|
} catch (e) {
|
||||||
// A log should never interrupt the calling code, whatever happens.
|
// A log should never interrupt the calling code, whatever happens.
|
||||||
logger.error('Failed to log an event', {message: e})
|
logger.error('Failed to log an event', {message: e})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toStringRecord<E extends keyof LogEvents>(
|
function toStringRecord<E extends keyof MetricEvents>(
|
||||||
metadata: LogEvents[E] & FlatJSONRecord,
|
metadata: MetricEvents[E] & FlatJSONRecord,
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const record: Record<string, string> = {}
|
const record: Record<string, string> = {}
|
||||||
for (let key in metadata) {
|
for (let key in metadata) {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {nanoid} from 'nanoid/non-secure'
|
import {nanoid} from 'nanoid/non-secure'
|
||||||
|
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {add} from '#/logger/logDump'
|
import {add} from '#/logger/logDump'
|
||||||
|
import {MetricEvents} from '#/logger/metrics'
|
||||||
import {bitdriftTransport} from '#/logger/transports/bitdrift'
|
import {bitdriftTransport} from '#/logger/transports/bitdrift'
|
||||||
import {consoleTransport} from '#/logger/transports/console'
|
import {consoleTransport} from '#/logger/transports/console'
|
||||||
import {sentryTransport} from '#/logger/transports/sentry'
|
import {sentryTransport} from '#/logger/transports/sentry'
|
||||||
@@ -89,6 +91,25 @@ export class Logger {
|
|||||||
this.transport({level: LogLevel.Error, message: error, metadata})
|
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) {
|
addTransport(transport: Transport) {
|
||||||
this.transports.push(transport)
|
this.transports.push(transport)
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type LogEvents = {
|
export type MetricEvents = {
|
||||||
// App events
|
// App events
|
||||||
init: {
|
init: {
|
||||||
initMs: number
|
initMs: number
|
||||||
@@ -30,7 +30,7 @@ export type LogEvents = {
|
|||||||
secondsActive: number
|
secondsActive: number
|
||||||
}
|
}
|
||||||
'state:foreground': {}
|
'state:foreground': {}
|
||||||
'lake:router:navigate': {
|
'router:navigate': {
|
||||||
from?: string
|
from?: string
|
||||||
}
|
}
|
||||||
'deepLink:referrerReceived': {
|
'deepLink:referrerReceived': {
|
||||||
@@ -18,8 +18,7 @@ export const bitdriftTransport: Transport = (
|
|||||||
) => {
|
) => {
|
||||||
const log = logFunctions[level]
|
const log = logFunctions[level]
|
||||||
log(message.toString(), {
|
log(message.toString(), {
|
||||||
// match Sentry payload
|
__context__: context,
|
||||||
context,
|
|
||||||
...prepareMetadata(metadata),
|
...prepareMetadata(metadata),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ export const sentryTransport: Transport = (
|
|||||||
timestamp,
|
timestamp,
|
||||||
) => {
|
) => {
|
||||||
const meta = {
|
const meta = {
|
||||||
// match Bitdrift payload
|
__context__: context,
|
||||||
context,
|
|
||||||
...prepareMetadata(metadata),
|
...prepareMetadata(metadata),
|
||||||
}
|
}
|
||||||
let _tags = tags || {}
|
let _tags = tags || {}
|
||||||
|
|||||||
+8
-2
@@ -9,6 +9,12 @@ export enum LogContext {
|
|||||||
Notifications = 'notifications',
|
Notifications = 'notifications',
|
||||||
ConversationAgent = 'conversation-agent',
|
ConversationAgent = 'conversation-agent',
|
||||||
DMsAgent = 'dms-agent',
|
DMsAgent = 'dms-agent',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* METRIC IS FOR INTERNAL USE ONLY, don't create any other loggers using this
|
||||||
|
* context
|
||||||
|
*/
|
||||||
|
Metric = 'metric',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
@@ -33,9 +39,9 @@ export type Transport = (
|
|||||||
*/
|
*/
|
||||||
export type Metadata = {
|
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`.
|
* Applied as Sentry breadcrumb types. Defaults to `default`.
|
||||||
|
|||||||
Reference in New Issue
Block a user