125 lines
3.2 KiB
JavaScript
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)
|
|
}
|
|
}
|