refactor(logger): extract sentry reporter
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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<PinoLogger["debug"]>;
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user