diff --git a/cspell.json b/cspell.json index e1d0075f5dae5..3ece0a1630865 100644 --- a/cspell.json +++ b/cspell.json @@ -16,6 +16,7 @@ "Airplus", "airshipconfig", "airside", + "alrt", "Amal", "Amal's", "americanexpressfdx", diff --git a/src/libs/Log.ts b/src/libs/Log.ts index 62e305fd6c7bb..57fc67a41f51d 100644 --- a/src/libs/Log.ts +++ b/src/libs/Log.ts @@ -15,6 +15,7 @@ import {shouldAttachLog} from './Console'; import getPlatform from './getPlatform'; import {post} from './Network'; import requireParameters from './requireParameters'; +import forwardLogsToSentry from './telemetry/forwardLogsToSentry'; let timeout: NodeJS.Timeout; let shouldCollectLogs = false; @@ -59,6 +60,8 @@ function serverLoggingCallback(logger: Logger, params: ServerLoggingCallbackOpti if (requestParams.parameters) { requestParams.parameters = JSON.stringify(requestParams.parameters); } + // Mirror backend log payload into Telemetry logger for better context + forwardLogsToSentry(requestParams.logPacket); clearTimeout(timeout); timeout = setTimeout(() => logger.info('Flushing logs older than 10 minutes', true, {}, true), 10 * 60 * 1000); return LogCommand(requestParams); diff --git a/src/libs/telemetry/forwardLogsToSentry.ts b/src/libs/telemetry/forwardLogsToSentry.ts new file mode 100644 index 0000000000000..7983063ce4c90 --- /dev/null +++ b/src/libs/telemetry/forwardLogsToSentry.ts @@ -0,0 +1,69 @@ +import * as Sentry from '@sentry/react-native'; + +type SentryLogLevel = 'debug' | 'info' | 'warn' | 'error'; + +/** + * Method deciding whether a log packet should be forwarded to Sentry. + * + * ATTENTION! + * Currently, this always returns false because we want to deliberately decide what is being forwarded. + * There is no redaction / filtering of sensitive data implemented yet. When you implement any log forwarding logic, make sure that you do not leak any sensitive data. + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function shouldForwardLog(log: {message?: string; parameters?: Record | undefined}) { + return false; +} + +function mapLogMessageToSentryLevel(message: string): SentryLogLevel { + if (message.startsWith('[alrt]')) { + return 'error'; + } + if (message.startsWith('[warn]') || message.startsWith('[hmmm]')) { + return 'warn'; + } + if (message.startsWith('[info]')) { + return 'info'; + } + return 'debug'; +} + +function forwardLogsToSentry(logPacket: string | undefined) { + if (!logPacket) { + return; + } + + let parsedPacket: Array<{message?: string; parameters?: Record | undefined}> | undefined; + try { + parsedPacket = JSON.parse(logPacket) as typeof parsedPacket; + } catch { + Sentry.logger.warn('Failed to parse log packet for Sentry forwarding', {logPacket}); + return; + } + + if (!Array.isArray(parsedPacket)) { + return; + } + + for (const logLine of parsedPacket) { + if (!logLine || typeof logLine.message !== 'string') { + continue; + } + if (!shouldForwardLog(logLine)) { + continue; + } + + const level = mapLogMessageToSentryLevel(logLine.message); + const logMethod = Sentry.logger[level]; + if (!logMethod) { + continue; + } + + if (logLine.parameters) { + logMethod(logLine.message, logLine.parameters); + } else { + logMethod(logLine.message); + } + } +} + +export default forwardLogsToSentry; diff --git a/src/setup/telemetry/index.ts b/src/setup/telemetry/index.ts index 10e654fc4e7e7..8f8375c54b269 100644 --- a/src/setup/telemetry/index.ts +++ b/src/setup/telemetry/index.ts @@ -27,6 +27,7 @@ export default function (): void { environment: CONFIG.ENVIRONMENT, release: `${pkg.name}@${pkg.version}`, beforeSendTransaction: processBeforeSendTransactions, + enableLogs: true, }); startSpan(CONST.TELEMETRY.SPAN_APP_STARTUP, {