fix(logger): cache isProduction and prevent error double-serialization
Cache isProduction() result at module load to avoid repeated property lookups. Add isSerializedError() check in format functions to skip re-serialization when error objects have already been processed by logError/formatErrorForLogging. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,23 @@ import path from 'path'
|
|||||||
import { serializeError, sanitizeError } from './error-utils'
|
import { serializeError, sanitizeError } from './error-utils'
|
||||||
import { getLogDir, isProduction } from './shared'
|
import { getLogDir, isProduction } from './shared'
|
||||||
|
|
||||||
|
// Cache isProduction() at module load — app.isPackaged never changes at runtime
|
||||||
|
const IS_PROD = isProduction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an error has already been serialized (plain object with name/message but not an Error instance).
|
||||||
|
* Prevents double-serialization when logError() output passes through the format pipeline.
|
||||||
|
*/
|
||||||
|
function isSerializedError(value: unknown): boolean {
|
||||||
|
return (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
!(value instanceof Error) &&
|
||||||
|
'name' in value &&
|
||||||
|
'message' in value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Custom format for console output - includes full error details
|
// Custom format for console output - includes full error details
|
||||||
const consoleFormat = winston.format.combine(
|
const consoleFormat = winston.format.combine(
|
||||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
@@ -24,7 +41,10 @@ const consoleFormat = winston.format.combine(
|
|||||||
// Format error with full stack trace
|
// Format error with full stack trace
|
||||||
let errorStr = ''
|
let errorStr = ''
|
||||||
if (error) {
|
if (error) {
|
||||||
const serialized = isProduction()
|
// Skip re-serialization if already a serialized error object
|
||||||
|
const serialized: { stack?: string; message: string } = isSerializedError(error)
|
||||||
|
? (error as { stack?: string; message: string })
|
||||||
|
: IS_PROD
|
||||||
? sanitizeError(serializeError(error))
|
? sanitizeError(serializeError(error))
|
||||||
: serializeError(error)
|
: serializeError(error)
|
||||||
if (serialized.stack) {
|
if (serialized.stack) {
|
||||||
@@ -43,17 +63,19 @@ const consoleFormat = winston.format.combine(
|
|||||||
const fileFormat = winston.format.combine(
|
const fileFormat = winston.format.combine(
|
||||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
winston.format((info) => {
|
winston.format((info) => {
|
||||||
// Serialize errors in metadata
|
// Serialize errors in metadata (skip if already serialized)
|
||||||
if (info.error) {
|
if (info.error) {
|
||||||
info.error = isProduction()
|
if (!isSerializedError(info.error)) {
|
||||||
|
info.error = IS_PROD
|
||||||
? sanitizeError(serializeError(info.error))
|
? sanitizeError(serializeError(info.error))
|
||||||
: serializeError(info.error)
|
: serializeError(info.error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize any error in meta fields
|
// Serialize any error in meta fields (skip if already serialized)
|
||||||
for (const key of Object.keys(info)) {
|
for (const key of Object.keys(info)) {
|
||||||
if (key !== 'error' && info[key] instanceof Error) {
|
if (key !== 'error' && info[key] instanceof Error) {
|
||||||
info[key] = isProduction()
|
info[key] = IS_PROD
|
||||||
? sanitizeError(serializeError(info[key]))
|
? sanitizeError(serializeError(info[key]))
|
||||||
: serializeError(info[key])
|
: serializeError(info[key])
|
||||||
}
|
}
|
||||||
@@ -119,7 +141,7 @@ export function applyLoggingConfig(config: { level: string; appRetention: number
|
|||||||
logger.add(createFileTransport(undefined, retentionStr))
|
logger.add(createFileTransport(undefined, retentionStr))
|
||||||
|
|
||||||
// Add error-specific file transport in production
|
// Add error-specific file transport in production
|
||||||
if (isProduction()) {
|
if (IS_PROD) {
|
||||||
logger.add(
|
logger.add(
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
filename: path.join(getLogDir(), 'error-%DATE%.log'),
|
filename: path.join(getLogDir(), 'error-%DATE%.log'),
|
||||||
|
|||||||
Reference in New Issue
Block a user