Files
BIPMaterialManager/src/main/services/logger/shared.ts
Misaka fce8dbc37f feat(logging-p0): add screenshot capture and browser console diagnostics for ERP errors
Enhance ERP automation error diagnostics by capturing PNG screenshots
on every error and forwarding browser console warnings/errors to the
structured logger. Includes automatic cleanup of old screenshots
aligned with the configured log retention period.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 14:00:48 +08:00

93 lines
2.6 KiB
TypeScript

/**
* Shared Logger Utilities
* Common functions used across logger modules
*/
import path from 'path'
import fs from 'fs'
import { app } from 'electron'
/**
* Get log directory path
* Uses app.getPath('logs') in production, local logs dir in development
* Production = app.isPackaged === true
*/
export function getLogDir(): string {
// Check if running in production (packed app)
// This must be checked BEFORE app.getPath('logs') because Electron
// always returns the user data logs path regardless of environment
if (app && app.isReady() && app.isPackaged) {
return app.getPath('logs')
}
// Development environment: use logs directory in project root
// Note: synchronous FS calls are acceptable here because this branch
// executes in dev environments or before app is ready.
const devLogDir = path.join(process.cwd(), 'logs')
if (!fs.existsSync(devLogDir)) {
fs.mkdirSync(devLogDir, { recursive: true })
}
return devLogDir
}
/**
* Check if running in production environment
* Uses app.isPackaged as the single source of truth
*/
export function isProduction(): boolean {
return app?.isPackaged ?? false
}
/**
* Log level priority mapping (higher number = more severe)
*/
export const LOG_LEVEL_PRIORITY: Record<string, number> = {
verbose: 0,
debug: 1,
info: 2,
warn: 3,
error: 4
}
/**
* Check if a log level should be logged given a threshold
* @param level - The log level of the message
* @param threshold - The minimum log level threshold
* @returns true if the message should be logged
*/
export function isLoggable(level: string, threshold: string): boolean {
return (LOG_LEVEL_PRIORITY[level] ?? 0) >= (LOG_LEVEL_PRIORITY[threshold] ?? 2)
}
/**
* Delete screenshot files older than the given retention period.
* Scans <logDir>/screenshots/ and removes .png files whose mtime exceeds
* the retention window. Defensive: never throws.
*
* @param retentionDays - Number of days to keep screenshots
*/
export function cleanupOldScreenshots(retentionDays: number): void {
try {
const screenshotDir = path.join(getLogDir(), 'screenshots')
if (!fs.existsSync(screenshotDir)) return
const files = fs.readdirSync(screenshotDir)
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000
for (const file of files) {
if (!file.endsWith('.png')) continue
const filePath = path.join(screenshotDir, file)
try {
const stat = fs.statSync(filePath)
if (stat.mtimeMs < cutoff) {
fs.unlinkSync(filePath)
}
} catch {
// individual file deletion failure should not stop the loop
}
}
} catch {
// entire cleanup is best-effort
}
}