优化日志切分和保留策略
This commit is contained in:
@@ -27,27 +27,42 @@ test('shouldWriteLog respects configured minimum log level', () => {
|
||||
})
|
||||
|
||||
test('resolveLogFilePath uses daily log file names by channel', () => {
|
||||
assert.match(resolveLogFilePath('app', '2026-04-14T10:00:00.000Z'), /app-2026-04-14\.log$/)
|
||||
assert.match(resolveLogFilePath('integration', '2026-04-14T10:00:00.000Z'), /integration-2026-04-14\.log$/)
|
||||
assert.match(resolveLogFilePath('app', '2026-05-28T00:00:19.194+08:00'), /app-2026-05-28\.log$/)
|
||||
assert.match(resolveLogFilePath('integration', '2026-05-28T00:00:19.194+08:00'), /integration-2026-05-28\.log$/)
|
||||
})
|
||||
|
||||
test('resolveExpiredLogFilenames keeps latest seven days and ignores unknown files', () => {
|
||||
test('resolveLogFilePath uses local date for utc timestamps', () => {
|
||||
assert.match(resolveLogFilePath('app', '2026-05-27T16:00:19.194Z'), /app-2026-05-28\.log$/)
|
||||
})
|
||||
|
||||
test('resolveExpiredLogFilenames keeps latest thirty days by default and ignores unknown files', () => {
|
||||
const expired = resolveExpiredLogFilenames([
|
||||
'app-2026-04-14.log',
|
||||
'app-2026-04-13.log',
|
||||
'app-2026-04-08.log',
|
||||
'app-2026-04-07.log',
|
||||
'integration-2026-04-06.log',
|
||||
'app-2026-05-30.log',
|
||||
'app-2026-05-01.log',
|
||||
'app-2026-04-30.log',
|
||||
'integration-2026-04-29.log',
|
||||
'app.log',
|
||||
'integration.log',
|
||||
'random.txt',
|
||||
], '2026-04-14T12:00:00.000Z', 7)
|
||||
], '2026-05-30')
|
||||
|
||||
assert.deepEqual(expired, [
|
||||
'app-2026-04-30.log',
|
||||
'integration-2026-04-29.log',
|
||||
'app.log',
|
||||
'integration.log',
|
||||
])
|
||||
})
|
||||
|
||||
test('resolveExpiredLogFilenames supports explicit retention days', () => {
|
||||
const expired = resolveExpiredLogFilenames([
|
||||
'app-2026-04-14.log',
|
||||
'app-2026-04-08.log',
|
||||
'app-2026-04-07.log',
|
||||
], '2026-04-14', 7)
|
||||
|
||||
assert.deepEqual(expired, [
|
||||
'app-2026-04-07.log',
|
||||
'integration-2026-04-06.log',
|
||||
'app.log',
|
||||
'integration.log',
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ const ANSI = {
|
||||
const DATA_ROOT = resolveDataRoot()
|
||||
const LOG_DIR = path.join(DATA_ROOT, 'logs')
|
||||
const ACTIVE_LOG_LEVEL = normalizeLogLevel(runtimeConfig.logging?.level)
|
||||
const LOG_RETENTION_DAYS = 7
|
||||
const DEFAULT_LOG_RETENTION_DAYS = 30
|
||||
const LOG_RETENTION_DAYS = normalizeLogRetentionDays(runtimeConfig.logging?.retentionDays)
|
||||
const LEGACY_LOG_FILES = new Set(['app.log', 'integration.log'])
|
||||
const SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|pwd|cookie|authorization|credential|card|cards|cardno|cardpwd|apikey|api_key|devicekey|session/i
|
||||
const MAX_SANITIZE_DEPTH = 8
|
||||
@@ -203,7 +204,7 @@ export function resolveExpiredLogFilenames(
|
||||
referenceTime = new Date().toISOString(),
|
||||
retentionDays = LOG_RETENTION_DAYS,
|
||||
): string[] {
|
||||
const normalizedRetentionDays = Math.max(1, Number(retentionDays) || LOG_RETENTION_DAYS)
|
||||
const normalizedRetentionDays = normalizeLogRetentionDays(retentionDays)
|
||||
const cutoffDateKey = toDateKey(offsetDate(referenceTime, -(normalizedRetentionDays - 1)))
|
||||
|
||||
return (Array.isArray(fileNames) ? fileNames : [])
|
||||
@@ -444,7 +445,7 @@ async function cleanupExpiredLogsIfNeeded(dateKey: string): Promise<void> {
|
||||
|
||||
try {
|
||||
const fileNames = await fs.readdir(LOG_DIR)
|
||||
const expired = resolveExpiredLogFilenames(fileNames, `${dateKey}T00:00:00.000Z`)
|
||||
const expired = resolveExpiredLogFilenames(fileNames, dateKey, LOG_RETENTION_DAYS)
|
||||
await Promise.all(expired.map((fileName) => fs.rm(path.join(LOG_DIR, fileName), { force: true })))
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error || '未知错误')
|
||||
@@ -453,26 +454,56 @@ async function cleanupExpiredLogsIfNeeded(dateKey: string): Promise<void> {
|
||||
}
|
||||
|
||||
function extractLogDateKey(time: unknown): string {
|
||||
const normalized = String(time || '').trim()
|
||||
return /^\d{4}-\d{2}-\d{2}/.test(normalized) ? normalized.slice(0, 10) : toDateKey(normalized)
|
||||
return toDateKey(time)
|
||||
}
|
||||
|
||||
function toDateKey(value: unknown): string {
|
||||
const date = new Date(value instanceof Date ? value : String(value || ''))
|
||||
const date = parseLogDate(value)
|
||||
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return new Date().toISOString().slice(0, 10)
|
||||
return formatLocalDate(new Date())
|
||||
}
|
||||
|
||||
return date.toISOString().slice(0, 10)
|
||||
return formatLocalDate(date)
|
||||
}
|
||||
|
||||
function offsetDate(value: unknown, offsetDays: unknown): Date {
|
||||
const date = new Date(value instanceof Date ? value : String(value || ''))
|
||||
date.setUTCDate(date.getUTCDate() + Number(offsetDays || 0))
|
||||
const date = parseLogDate(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return new Date()
|
||||
}
|
||||
|
||||
date.setDate(date.getDate() + Number(offsetDays || 0))
|
||||
return date
|
||||
}
|
||||
|
||||
function parseLogDate(value: unknown): Date {
|
||||
if (value instanceof Date) {
|
||||
return new Date(value.getTime())
|
||||
}
|
||||
|
||||
const text = String(value || '').trim()
|
||||
const matchedDateKey = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text)
|
||||
if (matchedDateKey) {
|
||||
return new Date(
|
||||
Number(matchedDateKey[1]),
|
||||
Number(matchedDateKey[2]) - 1,
|
||||
Number(matchedDateKey[3]),
|
||||
)
|
||||
}
|
||||
|
||||
return new Date(text)
|
||||
}
|
||||
|
||||
function normalizeLogRetentionDays(value: unknown): number {
|
||||
const parsed = Number(value)
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return DEFAULT_LOG_RETENTION_DAYS
|
||||
}
|
||||
|
||||
return Math.max(1, Math.floor(parsed))
|
||||
}
|
||||
|
||||
function shouldDeleteLogFileByDate(fileName: unknown, cutoffDateKey: string): boolean {
|
||||
if (LEGACY_LOG_FILES.has(String(fileName || '').trim())) {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user