Files
order_site/apps/backend/src/utils/logger.js
T
2026-04-09 09:06:08 +08:00

125 lines
3.2 KiB
JavaScript

import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import { PROJECT_ROOT, runtimeConfig } from '../config/runtime.js'
const DATA_ROOT = resolveDataRoot()
const LOG_DIR = path.join(DATA_ROOT, 'logs')
const APP_LOG_FILE = path.join(LOG_DIR, 'app.log')
const WEBHOOK_LOG_FILE = path.join(LOG_DIR, 'webhook.log')
let writeQueue = Promise.resolve()
export function createRequestId(prefix = 'req') {
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
}
export function logDebug(scope, message, detail) {
return writeLog('debug', scope, message, detail)
}
export function logInfo(scope, message, detail) {
return writeLog('info', scope, message, detail)
}
export function logWarn(scope, message, detail) {
return writeLog('warn', scope, message, detail)
}
export function logError(scope, message, detail) {
return writeLog('error', scope, message, detail)
}
export function logWebhook(scope, message, detail, { level = 'info' } = {}) {
return writeLog(level, scope, message, detail, { channel: 'webhook' })
}
function writeLog(level, scope, message, detail, { channel = 'app' } = {}) {
const entry = {
time: new Date().toISOString(),
level,
scope: String(scope || 'app').trim() || 'app',
message: String(message || '').trim() || '-',
pid: process.pid,
detail: normalizeLogValue(detail),
}
writeConsole(entry)
enqueueFileWrite(entry, channel === 'webhook' ? WEBHOOK_LOG_FILE : APP_LOG_FILE)
return entry
}
function enqueueFileWrite(entry, filePath) {
const line = JSON.stringify(entry)
writeQueue = writeQueue
.then(async () => {
await fs.mkdir(LOG_DIR, { recursive: true })
await fs.appendFile(filePath, `${line}\n`, 'utf8')
})
.catch((error) => {
const reason = error instanceof Error ? error.message : String(error || '未知错误')
console.error('[logger] failed to write log file:', reason)
})
}
function writeConsole(entry) {
const prefix = `[${entry.time}] [${entry.level}] [${entry.scope}] ${entry.message}`
const logger = resolveConsoleMethod(entry.level)
if (typeof entry.detail === 'undefined') {
logger(prefix)
return
}
logger(prefix, entry.detail)
}
function resolveConsoleMethod(level) {
if (level === 'error') {
return console.error
}
if (level === 'warn') {
return console.warn
}
return console.log
}
function resolveDataRoot() {
const configured = String(runtimeConfig.data?.root || '').trim()
return configured ? path.resolve(configured) : path.resolve(PROJECT_ROOT, 'data')
}
function normalizeLogValue(value) {
if (typeof value === 'undefined') {
return undefined
}
try {
return JSON.parse(
JSON.stringify(value, (_key, current) => {
if (current instanceof Error) {
return {
name: current.name,
message: current.message,
stack: current.stack,
statusCode: current.statusCode,
errorCode: current.errorCode,
}
}
if (typeof current === 'bigint') {
return String(current)
}
return current
}),
)
} catch {
return String(value)
}
}