From 345c5f7f2520fa2042d913293107dc35cd23f9ef Mon Sep 17 00:00:00 2001 From: chenhang Date: Tue, 30 Jun 2026 11:24:13 +0800 Subject: [PATCH] refactor(logger): extract sentry reporter --- src/utils/logger-sentry-reporter.ts | 61 +++++++++++++++++++++++++++++ src/utils/logger.ts | 31 +++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/utils/logger-sentry-reporter.ts diff --git a/src/utils/logger-sentry-reporter.ts b/src/utils/logger-sentry-reporter.ts new file mode 100644 index 00000000..6347d740 --- /dev/null +++ b/src/utils/logger-sentry-reporter.ts @@ -0,0 +1,61 @@ +import * as Sentry from "@sentry/nextjs"; +import type { CaptureContext, SeverityLevel } from "@sentry/nextjs"; + +export type RemoteLogLevel = "warn" | "error" | "fatal"; + +export interface SentryLogPayload { + level: RemoteLogLevel; + component: string; + message: string; + data: unknown[]; + formatValue: (value: unknown) => string; +} + +export function reportSentryLog(payload: SentryLogPayload): void { + try { + const error = findError(payload.data); + const captureContext = toSentryCaptureContext(payload); + + if (error) { + Sentry.captureException(error, captureContext); + return; + } + + Sentry.captureMessage( + payload.message || + `[${payload.component}] ${payload.level.toUpperCase()} log`, + captureContext, + ); + } catch { + // Logging must never break the app runtime. + } +} + +function toSentryCaptureContext({ + level, + component, + message, + data, + formatValue, +}: SentryLogPayload): CaptureContext { + return { + level: toSentrySeverityLevel(level), + tags: { + component, + log_level: level, + }, + extra: { + component, + message, + data: data.map((item) => formatValue(item)), + }, + }; +} + +function toSentrySeverityLevel(level: RemoteLogLevel): SeverityLevel { + return level === "warn" ? "warning" : level; +} + +function findError(args: unknown[]): Error | null { + return args.find((arg): arg is Error => arg instanceof Error) ?? null; +} diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 5ef7df70..2b548b80 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,6 +1,10 @@ import pino, { type Logger as PinoLogger, type LoggerOptions } from "pino"; import { AppEnvUtil } from "./app-env"; +import { + reportSentryLog, + type RemoteLogLevel, +} from "./logger-sentry-reporter"; type LogArgs = unknown[]; type PinoLogArgs = Parameters; @@ -133,6 +137,8 @@ export class Logger { } private write(level: LogLevel, args: LogArgs): void { + Logger.reportImportantProductionLog(level, this.component, args); + if (Logger.shouldUseBrowserConsole()) { Logger.writeBrowserConsole(level, this.component, args); return; @@ -161,6 +167,31 @@ export class Logger { return typeof window !== "undefined" && AppEnvUtil.canOutputLogs(); } + private static shouldReportToRemote(level: LogLevel): level is RemoteLogLevel { + return AppEnvUtil.isProduction() && ( + level === "warn" || + level === "error" || + level === "fatal" + ); + } + + private static reportImportantProductionLog( + level: LogLevel, + component: string, + args: LogArgs, + ): void { + if (!Logger.shouldReportToRemote(level)) return; + + const { message, data } = Logger.toBrowserConsolePayload(args); + reportSentryLog({ + level, + component, + message, + data, + formatValue: Logger.formatValue, + }); + } + private static writeBrowserConsole( level: LogLevel, component: string,