fix(logger): address PR #19 code review feedback
Fixes: - Remove unused import logBufferMetrics from event-buffer.ts - Add safeStringify() for robust data formatting: - Handles circular references gracefully - Formats Error objects properly - Truncates large arrays [Array(n)] - Handles undefined/null/Date/BigInt values - Add getMode() method to AdaptiveTimeoutCalculator to avoid duplicating mode calculation logic in flush() - Fix empty data object handling (no trailing space) Tests: - Add comprehensive test suite (39 tests) covering: - Environment variable check (BAILEYS_LOG=false) - All logging functions with emoji verification - Session name handling - formatLogData edge cases (circular, Error, arrays, etc.)
This commit is contained in:
+85
-10
@@ -521,25 +521,97 @@ function isBaileysLogEnabled(): boolean {
|
||||
return process.env.BAILEYS_LOG !== 'false'
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely stringify a value, handling circular references, Errors, and special types
|
||||
*/
|
||||
function safeStringify(value: unknown, seen: WeakSet<object> = new WeakSet()): string {
|
||||
// Handle primitives
|
||||
if (value === null) return 'null'
|
||||
if (value === undefined) return 'undefined'
|
||||
if (typeof value === 'string') return value
|
||||
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
||||
if (typeof value === 'function') return '[Function]'
|
||||
if (typeof value === 'symbol') return value.toString()
|
||||
if (typeof value === 'bigint') return `${value}n`
|
||||
|
||||
// Handle objects
|
||||
if (typeof value === 'object') {
|
||||
// Check for circular reference
|
||||
if (seen.has(value)) return '[Circular]'
|
||||
seen.add(value)
|
||||
|
||||
// Handle Error objects
|
||||
if (value instanceof Error) {
|
||||
return `${value.name}: ${value.message}`
|
||||
}
|
||||
|
||||
// Handle Date objects
|
||||
if (value instanceof Date) {
|
||||
return value.toISOString()
|
||||
}
|
||||
|
||||
// Handle Arrays
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return '[]'
|
||||
if (value.length <= 3) {
|
||||
const items = value.map(v => safeStringify(v, seen))
|
||||
return `[${items.join(', ')}]`
|
||||
}
|
||||
return `[Array(${value.length})]`
|
||||
}
|
||||
|
||||
// Handle plain objects
|
||||
try {
|
||||
const keys = Object.keys(value as Record<string, unknown>)
|
||||
if (keys.length === 0) return '{}'
|
||||
if (keys.length <= 5) {
|
||||
const pairs = keys.map(k => {
|
||||
const v = (value as Record<string, unknown>)[k]
|
||||
return `${k}: ${safeStringify(v, seen)}`
|
||||
})
|
||||
return `{${pairs.join(', ')}}`
|
||||
}
|
||||
return `{Object(${keys.length} keys)}`
|
||||
} catch {
|
||||
return '[Object]'
|
||||
}
|
||||
}
|
||||
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format data object for single-line or multi-line output
|
||||
* Handles circular references, Error objects, arrays, and undefined values
|
||||
*/
|
||||
function formatLogData(data: Record<string, unknown>, singleLine: boolean = true): string {
|
||||
if (Object.keys(data).length === 0) return ''
|
||||
if (!data || Object.keys(data).length === 0) return ''
|
||||
|
||||
const seen = new WeakSet<object>()
|
||||
|
||||
if (singleLine) {
|
||||
// Single line format: { key1: value1, key2: value2 }
|
||||
const pairs = Object.entries(data).map(([k, v]) => {
|
||||
if (typeof v === 'object' && v !== null) {
|
||||
return `${k}: ${JSON.stringify(v)}`
|
||||
}
|
||||
return `${k}: ${v}`
|
||||
return `${k}: ${safeStringify(v, seen)}`
|
||||
})
|
||||
return `{ ${pairs.join(', ')} }`
|
||||
}
|
||||
|
||||
// Multi-line format for complex objects
|
||||
return JSON.stringify(data, null, 2)
|
||||
// Multi-line format - use safe replacer for JSON.stringify
|
||||
try {
|
||||
return JSON.stringify(data, (key, value) => {
|
||||
if (value instanceof Error) {
|
||||
return { name: value.name, message: value.message, stack: value.stack }
|
||||
}
|
||||
if (typeof value === 'bigint') {
|
||||
return `${value}n`
|
||||
}
|
||||
return value
|
||||
}, 2)
|
||||
} catch {
|
||||
// Fallback for circular references or other issues
|
||||
return safeStringify(data, seen)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -814,7 +886,8 @@ export function logInfo(message: string, data?: Record<string, unknown>, session
|
||||
if (!isBaileysLogEnabled()) return
|
||||
|
||||
const prefix = sessionName ? `[BAILEYS] [${sessionName}]` : '[BAILEYS]'
|
||||
const dataStr = data ? ' ' + formatLogData(data) : ''
|
||||
const formatted = data ? formatLogData(data) : ''
|
||||
const dataStr = formatted ? ' ' + formatted : ''
|
||||
console.log(`${prefix} ℹ️ ${message}${dataStr}`)
|
||||
}
|
||||
|
||||
@@ -829,7 +902,8 @@ export function logWarn(message: string, data?: Record<string, unknown>, session
|
||||
if (!isBaileysLogEnabled()) return
|
||||
|
||||
const prefix = sessionName ? `[BAILEYS] [${sessionName}]` : '[BAILEYS]'
|
||||
const dataStr = data ? ' ' + formatLogData(data) : ''
|
||||
const formatted = data ? formatLogData(data) : ''
|
||||
const dataStr = formatted ? ' ' + formatted : ''
|
||||
console.log(`${prefix} ⚠️ ${message}${dataStr}`)
|
||||
}
|
||||
|
||||
@@ -844,7 +918,8 @@ export function logError(message: string, data?: Record<string, unknown>, sessio
|
||||
if (!isBaileysLogEnabled()) return
|
||||
|
||||
const prefix = sessionName ? `[BAILEYS] [${sessionName}]` : '[BAILEYS]'
|
||||
const dataStr = data ? ' ' + formatLogData(data) : ''
|
||||
const formatted = data ? formatLogData(data) : ''
|
||||
const dataStr = formatted ? ' ' + formatted : ''
|
||||
console.error(`${prefix} ❌ ${message}${dataStr}`)
|
||||
}
|
||||
|
||||
|
||||
+16
-12
@@ -15,7 +15,7 @@ import { trimUndefined } from './generics'
|
||||
import type { ILogger } from './logger'
|
||||
import { updateMessageWithReaction, updateMessageWithReceipt } from './messages'
|
||||
import { isRealMessage, shouldIncrementChatUnread } from './process-message'
|
||||
import { logEventBuffer, logBufferMetrics } from './baileys-logger'
|
||||
import { logEventBuffer } from './baileys-logger'
|
||||
|
||||
// ============================================================================
|
||||
// BUFFER CONFIGURATION - Environment Variable Support
|
||||
@@ -316,6 +316,19 @@ class AdaptiveTimeoutCalculator {
|
||||
this.eventTimestamps = []
|
||||
this.currentTimeout = (this.minTimeout + this.maxTimeout) / 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current adaptive mode based on timeout
|
||||
* Returns 'aggressive', 'balanced', or 'conservative'
|
||||
*/
|
||||
getMode(): 'aggressive' | 'balanced' | 'conservative' {
|
||||
if (this.currentTimeout <= this.minTimeout * 1.5) {
|
||||
return 'aggressive'
|
||||
} else if (this.currentTimeout >= this.maxTimeout * 0.8) {
|
||||
return 'conservative'
|
||||
}
|
||||
return 'balanced'
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -542,21 +555,12 @@ export const makeEventBuffer = (
|
||||
// Record metrics
|
||||
recordFlushMetrics(eventCount, force)
|
||||
|
||||
// Determine adaptive mode label
|
||||
const currentTimeoutMs = config.enableAdaptiveTimeout ? adaptiveTimeout.getTimeout() : config.bufferTimeoutMs
|
||||
let adaptiveMode = 'balanced'
|
||||
if (currentTimeoutMs <= config.minBufferTimeoutMs * 1.5) {
|
||||
adaptiveMode = 'aggressive'
|
||||
} else if (currentTimeoutMs >= config.maxBufferTimeoutMs * 0.8) {
|
||||
adaptiveMode = 'conservative'
|
||||
}
|
||||
|
||||
// Log with [BAILEYS] prefix
|
||||
// Log with [BAILEYS] prefix - use getMode() to avoid duplicating mode calculation logic
|
||||
const flushDuration = Date.now() - flushStartTime
|
||||
logEventBuffer('buffer_flush', {
|
||||
flushCount: stats.totalFlushes,
|
||||
historyCacheSize: stats.historyCacheSize,
|
||||
mode: adaptiveMode,
|
||||
mode: config.enableAdaptiveTimeout ? adaptiveTimeout.getMode() : 'fixed',
|
||||
...(flushDuration > 5 ? { duration: `${flushDuration}ms` } : {})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user