debug.log 保留策略:只留最近3天+≤5MB,任一超标即清理到两条件同时满足
This commit is contained in:
@@ -45,6 +45,11 @@ function debugLog($msg, $ctx = []) {
|
||||
if ($ctx) $entry .= ' | Context: ' . json_encode($ctx, JSON_UNESCAPED_UNICODE);
|
||||
$entry .= "\n";
|
||||
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
|
||||
// ★ 保留策略(2026-10-05):只留最近3天 + ≤5MB,任一超标即清理到两条件同时满足
|
||||
if (is_file(__DIR__ . '/../log_maintain.php')) {
|
||||
@require_once __DIR__ . '/../log_maintain.php';
|
||||
if (function_exists('logMaintain')) { @logMaintain($logFile); }
|
||||
}
|
||||
}
|
||||
} // end if (!function_exists('debugLog'))
|
||||
|
||||
|
||||
+7
-1
@@ -119,7 +119,13 @@ function cgLog($msg) {
|
||||
}
|
||||
$dir = __DIR__ . '/db';
|
||||
if (!is_dir($dir)) { @mkdir($dir, 0755, true); }
|
||||
@file_put_contents($dir . '/debug.log', $line, FILE_APPEND | LOCK_EX);
|
||||
$f = $dir . '/debug.log';
|
||||
@file_put_contents($f, $line, FILE_APPEND | LOCK_EX);
|
||||
// ★ 保留策略(2026-10-05):只留最近3天 + ≤5MB,任一超标即清理到两条件同时满足
|
||||
if (is_file(__DIR__ . '/log_maintain.php')) {
|
||||
@require_once __DIR__ . '/log_maintain.php';
|
||||
if (function_exists('logMaintain')) { @logMaintain($f); }
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
|
||||
@@ -26,6 +26,12 @@ function debugLog($message, $context = []) {
|
||||
}
|
||||
|
||||
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
|
||||
|
||||
// ★ 保留策略(2026-10-05):只留最近3天 + ≤5MB,任一超标即清理到两条件同时满足
|
||||
if (is_file(__DIR__ . '/log_maintain.php')) {
|
||||
@require_once __DIR__ . '/log_maintain.php';
|
||||
if (function_exists('logMaintain')) { @logMaintain($logFile); }
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 数据库初始化 =====
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* debug 日志保留策略(2026-10-05)
|
||||
*
|
||||
* 规则(两条同时满足才算干净,达成任一条超标即触发清理):
|
||||
* 1. 只保留最近 3 天的日志行(按行首 [Y-m-d H:i:s] 时间戳判断);
|
||||
* 2. 文件总大小 ≤ 5MB。
|
||||
*
|
||||
* 设计要点:
|
||||
* - 自包含:不依赖 core.php / config.php(captcha_guard、poller 等独立入口都能用);
|
||||
* - 廉价预检:每次写入后只做 1 次 stat + 读前几行找最旧时间戳,命中才做全量清理,
|
||||
* 绝大多数调用在预检处直接返回,开销微秒级;
|
||||
* - append-only 假设:日志按时间追加,文件开头即最旧行;
|
||||
* - 并发安全:清理期间 flock 日志文件排他(写入方 file_put_contents(LOCK_EX) 会短暂
|
||||
* 等待),锁内先复查,避免多个请求排队重复重建;
|
||||
* - 流式处理:逐行读写、临时文件中转,60MB+ 大文件也不会撑爆 memory_limit;
|
||||
* - 无时间戳前缀的行(多行消息的续行)跟随上一行去留,避免留下孤儿行;
|
||||
* - 临时文件放在同目录(db/ 已被 nginx 整目录 404 屏蔽,外网不可见)。
|
||||
*
|
||||
* 接入点:core.php:debugLog / orders.php 内联 debugLog / captcha_guard.php:cgLog
|
||||
* / api/poller_online.php:debugLog(api/pay_poller.php 为无引用模板,不接入)。
|
||||
*/
|
||||
|
||||
if (!function_exists('logMaintain')) {
|
||||
|
||||
/** 从行首解析 [Y-m-d H:i:s] → unix 时间戳;解析失败返回 0 */
|
||||
function logMaintainLineTs($line) {
|
||||
if (preg_match('/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/', $line, $m)) {
|
||||
$ts = strtotime($m[1]);
|
||||
return ($ts === false) ? 0 : $ts;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件最旧的可解析时间戳(最多扫前 maxLines 行找第一个带时间戳的行)。
|
||||
* 找不到返回 0(调用方视为「无法判定年龄」,不触发超龄清理)。
|
||||
* @param resource $fh 已打开的文件句柄(会被 rewind)
|
||||
*/
|
||||
function logMaintainOldestTs($fh, $maxLines = 10) {
|
||||
if (rewind($fh) === false) return 0;
|
||||
for ($i = 0; $i < $maxLines; $i++) {
|
||||
$line = fgets($fh);
|
||||
if ($line === false) break;
|
||||
$ts = logMaintainLineTs($line);
|
||||
if ($ts > 0) return $ts;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重建日志:过滤掉 cutoff 之前的行;若剩余仍 > maxBytes,再从最旧端按行丢弃到 ≤ maxBytes。
|
||||
* 调用方必须已持有 $fh 的排他锁。
|
||||
* @param resource $fh 日志文件句柄(读写)
|
||||
* @param string $logFile 日志路径(临时文件建在同目录)
|
||||
* @return bool 成功 true
|
||||
*/
|
||||
function logMaintainRebuild($fh, $logFile, $maxBytes, $cutoff) {
|
||||
$tmp = $logFile . '.tmp';
|
||||
|
||||
// ===== 第一遍:读原文件,过滤超龄行,写入临时文件 =====
|
||||
$out = @fopen($tmp, 'wb');
|
||||
if (!$out) return false;
|
||||
if (rewind($fh) === false) { fclose($out); @unlink($tmp); return false; }
|
||||
|
||||
$keepPrev = true; // 无时间戳的续行跟随上一行;首行无时间戳时保守保留
|
||||
$kept = 0;
|
||||
while (($line = fgets($fh)) !== false) {
|
||||
$ts = logMaintainLineTs($line);
|
||||
if ($ts > 0) {
|
||||
$keepPrev = ($ts >= $cutoff);
|
||||
}
|
||||
if ($keepPrev) {
|
||||
fwrite($out, $line);
|
||||
$kept += strlen($line);
|
||||
}
|
||||
}
|
||||
fflush($out);
|
||||
fclose($out);
|
||||
clearstatcache(true, $tmp);
|
||||
|
||||
// ===== 第二步:剩余仍超 5MB → 从最旧端按整行丢弃到 ≤ maxBytes =====
|
||||
$skipBytes = ($kept > $maxBytes) ? ($kept - $maxBytes) : 0;
|
||||
|
||||
$in = @fopen($tmp, 'rb');
|
||||
if (!$in) { @unlink($tmp); return false; }
|
||||
if ($skipBytes > 0) {
|
||||
$acc = 0;
|
||||
// 逐行累计丢弃;按整行丢,最终大小 kept-acc 必 ≤ maxBytes
|
||||
while ($acc < $skipBytes && ($line = fgets($in)) !== false) {
|
||||
$acc += strlen($line);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 写回日志本体(仍持锁,写入方在锁外等待,不会丢)=====
|
||||
if (ftruncate($fh, 0) === false) { fclose($in); @unlink($tmp); return false; }
|
||||
rewind($fh);
|
||||
while (!feof($in)) {
|
||||
$chunk = fread($in, 1048576);
|
||||
if ($chunk === false) break;
|
||||
if ($chunk !== '') fwrite($fh, $chunk);
|
||||
}
|
||||
fflush($fh);
|
||||
fclose($in);
|
||||
@unlink($tmp);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 入口:日志写入后调用。任一条件超标(>5MB 或 含 3 天前的行)即清理,
|
||||
* 清理到两个条件同时满足为止(最多 3 轮,正常第 1 轮后复查即通过)。
|
||||
*
|
||||
* @param string $logFile 日志文件绝对路径
|
||||
* @param int $maxBytes 大小上限(默认 5MB)
|
||||
* @param int $keepDays 保留天数(默认 3 天)
|
||||
* @return bool true=干净(无需清理或已清理完成);false=清理失败
|
||||
*/
|
||||
function logMaintain($logFile, $maxBytes = 5242880, $keepDays = 3) {
|
||||
if (!is_string($logFile) || $logFile === '') return true;
|
||||
clearstatcache(true, $logFile);
|
||||
if (!is_file($logFile)) return true;
|
||||
$size = @filesize($logFile);
|
||||
if ($size === false || $size <= 0) return true;
|
||||
|
||||
$cutoff = time() - $keepDays * 86400;
|
||||
|
||||
// ---- 廉价预检:两条都没超标就直接返回(绝大多数调用在这里结束)----
|
||||
$need = ($size > $maxBytes);
|
||||
if (!$need) {
|
||||
$fh = @fopen($logFile, 'rb');
|
||||
if ($fh) {
|
||||
$oldest = logMaintainOldestTs($fh);
|
||||
fclose($fh);
|
||||
$need = ($oldest > 0 && $oldest < $cutoff);
|
||||
}
|
||||
}
|
||||
if (!$need) return true;
|
||||
|
||||
// ---- 清理:排他锁内执行(写入方 file_put_contents(LOCK_EX) 短暂等待)----
|
||||
$fh = @fopen($logFile, 'c+');
|
||||
if (!$fh) return false;
|
||||
if (!flock($fh, LOCK_EX)) { fclose($fh); return false; }
|
||||
|
||||
$ok = false;
|
||||
for ($round = 0; $round < 3; $round++) {
|
||||
// 锁内复查:可能别的进程刚清理完
|
||||
$stat = fstat($fh);
|
||||
$size = $stat ? (int) $stat['size'] : 0;
|
||||
$oldest = logMaintainOldestTs($fh);
|
||||
if ($size <= $maxBytes && !($oldest > 0 && $oldest < $cutoff)) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
if (!logMaintainRebuild($fh, $logFile, $maxBytes, $cutoff)) {
|
||||
break; // 重建失败(如目录不可写)→ 解锁退出,下次写入重试
|
||||
}
|
||||
}
|
||||
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
clearstatcache(true, $logFile);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
} // end if (!function_exists('logMaintain'))
|
||||
+7
-1
@@ -15,7 +15,13 @@ set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
||||
// core.php may not be loaded yet, define minimal debugLog
|
||||
if (!function_exists('debugLog')) {
|
||||
function debugLog($msg) {
|
||||
@file_put_contents(__DIR__ . '/db/debug.log', "[" . date('Y-m-d H:i:s') . "] [orders.php] $msg\n", FILE_APPEND | LOCK_EX);
|
||||
$lf = __DIR__ . '/db/debug.log';
|
||||
@file_put_contents($lf, "[" . date('Y-m-d H:i:s') . "] [orders.php] $msg\n", FILE_APPEND | LOCK_EX);
|
||||
// ★ 保留策略(2026-10-05):只留最近3天 + ≤5MB,任一超标即清理到两条件同时满足
|
||||
if (is_file(__DIR__ . '/log_maintain.php')) {
|
||||
@require_once __DIR__ . '/log_maintain.php';
|
||||
if (function_exists('logMaintain')) { @logMaintain($lf); }
|
||||
}
|
||||
}
|
||||
}
|
||||
debugLog("[orders.php] PHP {$type}: {$errstr} in {$errfile}:{$errline}");
|
||||
|
||||
Reference in New Issue
Block a user