123 lines
3.5 KiB
PHP
123 lines
3.5 KiB
PHP
<?php
|
|
/**
|
|
* 系统日志类
|
|
*
|
|
* 将日志写入 storage/logs/ 目录,按日期自动分文件。
|
|
* 支持级别:DEBUG / INFO / WARN / ERROR
|
|
*
|
|
* 用法:
|
|
* Logger::info('用户登录', ['user_id' => 123]);
|
|
* Logger::error('支付失败', ['order_id' => 456, 'err' => $e->getMessage()]);
|
|
*/
|
|
if (!defined('IN_APP')) exit('Forbidden');
|
|
|
|
class Logger
|
|
{
|
|
/** @var string 日志根目录 */
|
|
private static $dir;
|
|
|
|
/** @var bool 是否启用(受 DEBUG 配置控制) */
|
|
private static $enabled = true;
|
|
|
|
/** @var int 最小记录级别 (DEBUG=0, INFO=1, WARN=2, ERROR=3) */
|
|
private static $minLevel = 0; // DEBUG 模式下记录全部
|
|
|
|
const DEBUG = 0;
|
|
const INFO = 1;
|
|
const WARN = 2;
|
|
const ERROR = 3;
|
|
|
|
/**
|
|
* 初始化日志系统。
|
|
* @param string $dir 日志目录路径
|
|
* @param bool $enabled 是否启用
|
|
* @param int $minLevel 最小级别
|
|
*/
|
|
public static function init($dir, $enabled = true, $minLevel = 0)
|
|
{
|
|
self::$dir = rtrim($dir, '/\\');
|
|
self::$enabled = $enabled;
|
|
self::$minLevel = $minLevel;
|
|
if (!is_dir(self::$dir)) {
|
|
@mkdir(self::$dir, 0755, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 记录 DEBUG 级别日志。
|
|
*/
|
|
public static function debug($message, array $context = [])
|
|
{
|
|
self::write(self::DEBUG, $message, $context);
|
|
}
|
|
|
|
/**
|
|
* 记录 INFO 级别日志。
|
|
*/
|
|
public static function info($message, array $context = [])
|
|
{
|
|
self::write(self::INFO, $message, $context);
|
|
}
|
|
|
|
/**
|
|
* 记录 WARN 级别日志。
|
|
*/
|
|
public static function warn($message, array $context = [])
|
|
{
|
|
self::write(self::WARN, $message, $context);
|
|
}
|
|
|
|
/**
|
|
* 记录 ERROR 级别日志。
|
|
*/
|
|
public static function error($message, array $context = [])
|
|
{
|
|
self::write(self::ERROR, $message, $context);
|
|
}
|
|
|
|
// ─── 内部实现 ───
|
|
|
|
private static function write($level, $message, array $context)
|
|
{
|
|
if (!self::$enabled || $level < self::$minLevel) return;
|
|
|
|
$levelNames = [self::DEBUG => 'DEBUG', self::INFO => 'INFO', self::WARN => 'WARN', self::ERROR => 'ERROR'];
|
|
$date = date('Y-m-d');
|
|
$time = date('Y-m-d H:i:s');
|
|
$levelName = $levelNames[$level] ?? 'LOG';
|
|
|
|
// 格式化上下文
|
|
$ctxStr = '';
|
|
if (!empty($context)) {
|
|
$parts = [];
|
|
foreach ($context as $k => $v) {
|
|
if (is_array($v) || is_object($v)) {
|
|
$v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
}
|
|
$parts[] = $k . '=' . $v;
|
|
}
|
|
$ctxStr = ' [' . implode(', ', $parts) . ']';
|
|
}
|
|
|
|
// 获取调用者信息(跳过 Logger 内部调用)
|
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
|
$caller = '';
|
|
if (isset($trace[2])) {
|
|
$file = basename($trace[2]['file'] ?? '');
|
|
$line = $trace[2]['line'] ?? '';
|
|
$caller = " [{$file}:{$line}]";
|
|
}
|
|
|
|
$logLine = "[{$time}] [{$levelName}]{$caller} {$message}{$ctxStr}" . PHP_EOL;
|
|
|
|
$file = self::$dir . DIRECTORY_SEPARATOR . $date . '.log';
|
|
@file_put_contents($file, $logLine, FILE_APPEND | LOCK_EX);
|
|
|
|
// 单文件最大 10MB,超出轮转
|
|
if (file_exists($file) && filesize($file) > 10 * 1024 * 1024) {
|
|
$rotated = self::$dir . DIRECTORY_SEPARATOR . $date . '.log.1';
|
|
@rename($file, $rotated);
|
|
}
|
|
}
|
|
}
|