XLucas Logger 日志组件说明书
一、概述
XLucas Logger 是一款轻量级 PHP 日志记录组件,提供灵活的日志管理功能,包括多级别日志记录、自动文件轮转、过期日志清理、配置自定义等特性,适用于各类 PHP 应用的日志跟踪与管理。
二、功能特性
-
多级别日志记录
支持 5 个日志等级(从低到高):DEBUG(调试):用于开发阶段的详细调试信息INFO(信息):记录程序运行的常规状态信息SUCCESS(成功):标记操作成功的事件(如"数据保存成功")WARNING(警告):记录非致命性异常(如"参数格式不规范")ERROR(错误):记录致命性错误(如"数据库连接失败")
-
自动日志文件轮转
当当前日志文件大小超过配置的MaxFileSize时,自动将旧日志文件重命名为带时间戳的归档文件(如app_2024-05-20_15-30-45.log),并创建新文件继续记录,避免单文件过大。 -
自动清理过期日志
当日志文件总数超过MaxLogFiles配置时,自动删除最早创建的日志文件,确保磁盘空间不被过度占用。 -
灵活的配置管理
支持通过配置文件或代码动态修改日志参数(如存储路径、日志等级阈值、文件大小限制等)。 -
日志操作工具
提供日志查看(读取最后N行或全部内容)、日志清除(删除所有日志文件)、日志信息统计(文件数量、总大小等)功能。 -
控制台输出支持
可配置是否同时将日志输出到控制台,方便开发环境调试。
三、配置说明
日志组件的行为通过配置项控制,支持默认配置、配置文件自定义和代码动态修改三种方式。
1. 配置项说明
| 配置项 | 类型 | 说明 | 默认值 |
|---|---|---|---|
LogPath |
string | 日志文件存储目录 | 优先使用storage_path('logs')(ThinkPHP 环境),否则为../runtime/logs |
LogFileName |
string | 主日志文件名(未轮转的当前日志) | app.log |
MaxFileSize |
int | 单个日志文件最大大小(字节) | 10MB(1010241024) |
MaxLogFiles |
int | 最多保留的日志文件总数(含当前日志和归档日志) | 10 |
MinLevel |
int | 最小记录等级(低于该等级的日志不记录):0=DEBUG,1=INFO,2=SUCCESS,3=WARNING,4=ERROR | 0(记录所有等级) |
DateFormat |
string | 日志中的时间格式(遵循 PHP date() 函数格式) |
Y-m-d H:i:s |
EnableConsole |
bool | 是否同时将日志输出到控制台 | false |
2. 配置方式
(1)配置文件方式
在组件目录下创建LogConfig.php(默认已提供),返回配置数组即可生效。示例:
<?php
return [
'LogPath' => __DIR__ . '/../../../runtime/logs',
'LogFileName' => 'XLucas_0554H.log',
'MaxFileSize' => 2 * 1024 * 1024, // 2MB
'MaxLogFiles' => 30,
'MinLevel' => 1, // 不记录DEBUG
'DateFormat' => 'Y-m-d H:i:s',
'EnableConsole' => true,
];
(2)代码动态修改
通过SetConfig()方法动态修改配置,示例:
use XLucas\Logger\Log;
// 动态设置最大文件大小为5MB,启用控制台输出
Log::SetConfig([
'MaxFileSize' => 5 * 1024 * 1024,
'EnableConsole' => true
]);
四、使用方法
1. 引入组件
确保命名空间正确,通过use语句引入:
use XLucas\Logger\Log;
2. 记录日志
通过静态方法直接记录不同等级的日志,参数为日志消息字符串:
// 记录调试日志
Log::Debug('用户ID=123的请求参数:' . json_encode($_POST));
// 记录信息日志
Log::Info('系统启动完成,环境:production');
// 记录成功日志
Log::Success('订单ID=456支付成功');
// 记录警告日志
Log::Warning('用户ID=789的手机号格式不正确:12345');
// 记录错误日志
Log::Error('数据库连接失败:' . mysqli_error($conn));
3. 查看日志
通过Read()方法查看日志内容:
// 查看全部日志
$allLogs = Log::Read();
echo $allLogs;
// 查看最后100行日志
$last100Lines = Log::Read(100);
echo $last100Lines;
4. 清除日志
通过Clear()方法删除所有日志文件(含当前日志和归档日志):
$isSuccess = Log::Clear();
if ($isSuccess) {
echo "日志清除成功";
} else {
echo "日志清除失败";
}
5. 获取日志信息
通过GetInfo()方法获取日志统计信息(文件数量、大小、路径等):
$logInfo = Log::GetInfo();
print_r($logInfo);
返回结果示例:
[
'LogPath' => '/path/to/logs',
'CurrentLogFile' => '/path/to/logs/app.log',
'TotalFiles' => 5,
'TotalSize' => 2048000,
'TotalSizeFormatted' => '2 MB',
'MaxFileSize' => 10485760,
'MaxFileSizeFormatted' => '10 MB',
'MaxLogFiles' => 10,
'MinLevel' => 'DEBUG',
'Files' => [
[
'Name' => 'app.log',
'Path' => '/path/to/logs/app.log',
'Size' => 512000,
'SizeFormatted' => '500 KB',
'ModifiedTime' => '2024-05-20 16:30:00',
'IsCurrent' => true
],
// ... 其他日志文件信息
]
]
6. 获取当前配置
通过GetConfig()方法获取当前生效的配置:
$config = Log::GetConfig();
print_r($config);
五、日志文件管理机制
-
文件轮转规则
当当前日志文件(如app.log)大小达到MaxFileSize时,自动重命名为app_YYYY-MM-DD_HH-ii-ss.log(带时间戳),并创建新的app.log继续记录。 -
过期清理规则
每次写入日志时检查文件总数,若超过MaxLogFiles,则按修改时间排序,删除最早的文件,保留最新的N个文件(N=MaxLogFiles)。 -
日志格式
每条日志格式为:[时间] [等级] 消息,例如:[2024-05-20 15:30:45] [INFO] 系统启动完成 [2024-05-20 15:31:00] [ERROR] 数据库连接失败
六、注意事项
-
目录权限
确保LogPath配置的目录具有写入权限(建议权限0777),否则日志无法写入。 -
配置加载顺序
优先加载LogConfig.php,若文件不存在则使用默认配置;通过SetConfig()设置的配置会覆盖上述两种方式。 -
日志等级控制
生产环境建议将MinLevel设置为INFO或更高,避免DEBUG日志占用过多磁盘空间。 -
性能影响
日志写入涉及文件操作,高频日志记录可能影响性能,建议核心业务按需记录关键日志。
七、版本信息
- 版本:1.0.0
- 作者:XLucas
八、程序代码
配置文件
/**
* 日志配置文件
*
* 配置说明:
* - LogPath: 日志文件存储目录
* - LogFileName: 日志文件名
* - MaxFileSize: 单个日志文件最大大小(字节)
* - MaxLogFiles: 最多保留的日志文件数量
* - MinLevel: 最小记录等级(0=DEBUG, 1=INFO, 2=SUCCESS, 3=WARNING, 4=ERROR)
* - DateFormat: 日期时间格式
* - EnableConsole: 是否启用控制台输出
*/
return [
// 日志文件存储目录
// 注意:如果在 ThinkPHP 中使用,可以改为 storage_path('logs')
'LogPath' => __DIR__ . '/../../../runtime/logs',
// 日志文件名
'LogFileName' => 'XLucas_0554H.log',
// 单个日志文件最大大小(字节)
// 10MB = 10 * 1024 * 1024
'MaxFileSize' => 2 * 1024 * 1024 ,
// 最多保留的日志文件数量
'MaxLogFiles' => 30,
// 最小记录等级
// 0 = DEBUG(记录所有)
// 1 = INFO(不记录DEBUG)
// 2 = SUCCESS(不记录DEBUG、INFO)
// 3 = WARNING(只记录WARNING、ERROR)
// 4 = ERROR(只记录ERROR)
'MinLevel' => 0,
// 日期时间格式
'DateFormat' => 'Y-m-d H:i:s',
// 是否启用控制台输出
'EnableConsole' => false,
];
主程序
namespace XLucas\Logger;
/**
* 日志记录扩展程序
*
* 功能特性:
* - 支持5个日志等级(Debug、Info、Success、Warning、Error)
* - 自动轮转日志文件(超过最大大小时重命名旧日志)
* - 自动清理过期日志(超过最大数量时删除最早的日志)
* - 可配置的日志等级、最大大小、最大数量
* - 支持查看和清除日志
*
* @author XLucas
* @version 1.0.0
*/
class Log
{
// 日志等级常量
const LEVEL_DEBUG = 0;
const LEVEL_INFO = 1;
const LEVEL_SUCCESS = 2;
const LEVEL_WARNING = 3;
const LEVEL_ERROR = 4;
// 日志等级名称
private static $LevelNames = [
self::LEVEL_DEBUG => 'DEBUG',
self::LEVEL_INFO => 'INFO',
self::LEVEL_SUCCESS => 'SUCCESS',
self::LEVEL_WARNING => 'WARNING',
self::LEVEL_ERROR => 'ERROR',
];
// 日志等级颜色(用于控制台输出)
private static $LevelColors = [
self::LEVEL_DEBUG => '[DEBUG]',
self::LEVEL_INFO => '[INFO]',
self::LEVEL_SUCCESS => '[SUCCESS]',
self::LEVEL_WARNING => '[WARNING]',
self::LEVEL_ERROR => '[ERROR]',
];
// 配置信息
private static $Config = null;
// 当前日志文件路径
private static $CurrentLogFile = null;
/**
* 初始化配置
*/
private static function InitConfig()
{
if (self::$Config !== null) {
return;
}
// 加载配置
$ConfigFile = __DIR__ . '/LogConfig.php';
if (file_exists($ConfigFile)) {
self::$Config = require $ConfigFile;
} else {
// 默认配置
// 尝试使用 ThinkPHP 的 storage_path(),如果不存在则使用相对路径
$LogPath = function_exists('storage_path')
? storage_path('logs')
: __DIR__ . '/../../runtime/logs';
self::$Config = [
'LogPath' => $LogPath,
'LogFileName' => 'app.log',
'MaxFileSize' => 10 * 1024 * 1024, // 10MB
'MaxLogFiles' => 10,
'MinLevel' => self::LEVEL_DEBUG,
'DateFormat' => 'Y-m-d H:i:s',
'EnableConsole' => false,
];
}
// 确保日志目录存在
if (!is_dir(self::$Config['LogPath'])) {
@mkdir(self::$Config['LogPath'], 0777, true);
}
}
/**
* 获取当前日志文件路径
*/
private static function GetLogFilePath()
{
if (self::$CurrentLogFile === null) {
self::InitConfig();
self::$CurrentLogFile = self::$Config['LogPath'] . DIRECTORY_SEPARATOR . self::$Config['LogFileName'];
}
return self::$CurrentLogFile;
}
/**
* 检查并处理日志文件轮转
*/
private static function CheckAndRotateLog()
{
$LogFile = self::GetLogFilePath();
// 检查文件是否存在且超过最大大小
if (file_exists($LogFile) && filesize($LogFile) >= self::$Config['MaxFileSize']) {
self::RotateLogFile($LogFile);
}
// 检查并清理过期日志(每次都检查,确保数量不超过限制)
self::CleanOldLogs();
}
/**
* 轮转日志文件(重命名旧日志,创建新日志)
*/
private static function RotateLogFile($LogFile)
{
$PathInfo = pathinfo($LogFile);
$Directory = $PathInfo['dirname'];
$FileName = $PathInfo['filename'];
$Extension = isset($PathInfo['extension']) ? '.' . $PathInfo['extension'] : '';
// 生成新的文件名(带时间戳)
$Timestamp = date('Y-m-d_H-i-s');
$NewFileName = $FileName . '_' . $Timestamp . $Extension;
$NewFilePath = $Directory . DIRECTORY_SEPARATOR . $NewFileName;
// 重命名旧日志文件
if (file_exists($LogFile)) {
@rename($LogFile, $NewFilePath);
}
}
/**
* 清理过期的日志文件
*/
private static function CleanOldLogs()
{
$LogPath = self::$Config['LogPath'];
$LogFileName = self::$Config['LogFileName'];
$MaxLogFiles = self::$Config['MaxLogFiles'];
// 检查目录是否存在
if (!is_dir($LogPath)) {
return;
}
// 获取所有日志文件
$Files = [];
$DirContents = @scandir($LogPath);
if ($DirContents === false) {
return;
}
// 获取日志文件的基础名称(不包括扩展名)
$PathInfo = pathinfo($LogFileName);
$BaseFileName = $PathInfo['filename'];
$Extension = isset($PathInfo['extension']) ? '.' . $PathInfo['extension'] : '';
foreach ($DirContents as $File) {
if ($File === '.' || $File === '..') {
continue;
}
$FilePath = $LogPath . DIRECTORY_SEPARATOR . $File;
if (is_file($FilePath)) {
// 匹配日志文件(当前日志和归档日志)
// 当前日志: test.log
// 归档日志: test_YYYY-MM-DD_HH-ii-ss.log
if ($File === $LogFileName || strpos($File, $BaseFileName . '_') === 0 && substr($File, -strlen($Extension)) === $Extension) {
$Files[$FilePath] = filemtime($FilePath);
}
}
}
// 如果日志文件数量超过最大数量,删除最早的
if (count($Files) > $MaxLogFiles) {
// 按修改时间排序(最早的在前)
asort($Files);
// 计算需要删除的文件数量
$DeleteCount = count($Files) - $MaxLogFiles;
// 删除最早的日志文件
$Index = 0;
foreach ($Files as $FilePath => $Time) {
if ($Index >= $DeleteCount) {
break;
}
// 尝试删除文件
if (@unlink($FilePath)) {
$Index++;
}
}
}
}
/**
* 记录日志
*/
private static function WriteLog($Level, $Message)
{
self::InitConfig();
// 检查日志等级是否满足最小等级要求
if ($Level < self::$Config['MinLevel']) {
return;
}
// 确保日志目录存在
if (!is_dir(self::$Config['LogPath'])) {
@mkdir(self::$Config['LogPath'], 0777, true);
}
// 检查并处理日志文件轮转
self::CheckAndRotateLog();
// 获取日志文件路径
$LogFile = self::GetLogFilePath();
// 格式化日志消息
$Timestamp = date(self::$Config['DateFormat']);
$LevelName = self::$LevelNames[$Level];
$FormattedMessage = "[{$Timestamp}] [{$LevelName}] {$Message}" . PHP_EOL;
// 写入日志文件
$Handle = @fopen($LogFile, 'a');
if ($Handle) {
fwrite($Handle, $FormattedMessage);
fclose($Handle);
}
// 如果启用控制台输出,输出到控制台
if (self::$Config['EnableConsole']) {
echo self::$LevelColors[$Level] . ' ' . $FormattedMessage;
}
}
/**
* 记录调试日志
*
* @param string $Message 日志消息
*/
public static function Debug($Message)
{
self::WriteLog(self::LEVEL_DEBUG, $Message);
}
/**
* 记录信息日志
*
* @param string $Message 日志消息
*/
public static function Info($Message)
{
self::WriteLog(self::LEVEL_INFO, $Message);
}
/**
* 记录成功日志
*
* @param string $Message 日志消息
*/
public static function Success($Message)
{
self::WriteLog(self::LEVEL_SUCCESS, $Message);
}
/**
* 记录警告日志
*
* @param string $Message 日志消息
*/
public static function Warning($Message)
{
self::WriteLog(self::LEVEL_WARNING, $Message);
}
/**
* 记录错误日志
*
* @param string $Message 日志消息
*/
public static function Error($Message)
{
self::WriteLog(self::LEVEL_ERROR, $Message);
}
/**
* 清除所有日志文件
*
* @return bool 是否清除成功
*/
public static function Clear()
{
self::InitConfig();
$LogPath = self::$Config['LogPath'];
$LogFileName = self::$Config['LogFileName'];
$DeletedCount = 0;
if (is_dir($LogPath)) {
$DirContents = @scandir($LogPath);
if ($DirContents === false) {
return false;
}
// 获取日志文件的基础名称(不包括扩展名)
$PathInfo = pathinfo($LogFileName);
$BaseFileName = $PathInfo['filename'];
$Extension = isset($PathInfo['extension']) ? '.' . $PathInfo['extension'] : '';
foreach ($DirContents as $File) {
if ($File === '.' || $File === '..') {
continue;
}
$FilePath = $LogPath . DIRECTORY_SEPARATOR . $File;
if (is_file($FilePath)) {
// 匹配日志文件(当前日志和归档日志)
if ($File === $LogFileName || strpos($File, $BaseFileName . '_') === 0 && substr($File, -strlen($Extension)) === $Extension) {
if (@unlink($FilePath)) {
$DeletedCount++;
}
}
}
}
}
return $DeletedCount > 0;
}
/**
* 查看日志内容
*
* @param int $Lines 查看最后N行(0表示查看全部)
* @return string 日志内容
*/
public static function Read($Lines = 0)
{
self::InitConfig();
$LogFile = self::GetLogFilePath();
if (!file_exists($LogFile)) {
return '日志文件不存在';
}
$Content = file_get_contents($LogFile);
// 如果指定了行数,只返回最后N行
if ($Lines > 0) {
$LogLines = explode(PHP_EOL, trim($Content));
$LogLines = array_slice($LogLines, -$Lines);
$Content = implode(PHP_EOL, $LogLines);
}
return $Content;
}
/**
* 获取日志文件信息
*
* @return array 日志文件信息
*/
public static function GetInfo()
{
self::InitConfig();
$LogPath = self::$Config['LogPath'];
$LogFileName = self::$Config['LogFileName'];
$CurrentLogFile = self::GetLogFilePath();
$Files = [];
$TotalSize = 0;
if (is_dir($LogPath)) {
$DirContents = @scandir($LogPath);
if ($DirContents === false) {
return [
'LogPath' => $LogPath,
'CurrentLogFile' => $CurrentLogFile,
'TotalFiles' => 0,
'TotalSize' => 0,
'TotalSizeFormatted' => '0 B',
'MaxFileSize' => self::$Config['MaxFileSize'],
'MaxFileSizeFormatted' => self::FormatBytes(self::$Config['MaxFileSize']),
'MaxLogFiles' => self::$Config['MaxLogFiles'],
'MinLevel' => self::$LevelNames[self::$Config['MinLevel']],
'Files' => [],
];
}
// 获取日志文件的基础名称(不包括扩展名)
$PathInfo = pathinfo($LogFileName);
$BaseFileName = $PathInfo['filename'];
$Extension = isset($PathInfo['extension']) ? '.' . $PathInfo['extension'] : '';
foreach ($DirContents as $File) {
if ($File === '.' || $File === '..') {
continue;
}
$FilePath = $LogPath . DIRECTORY_SEPARATOR . $File;
if (is_file($FilePath)) {
// 匹配日志文件(当前日志和归档日志)
// 当前日志: test.log
// 归档日志: test_YYYY-MM-DD_HH-ii-ss.log
if ($File === $LogFileName || strpos($File, $BaseFileName . '_') === 0 && substr($File, -strlen($Extension)) === $Extension) {
$FileSize = filesize($FilePath);
$TotalSize += $FileSize;
$Files[] = [
'Name' => $File,
'Path' => $FilePath,
'Size' => $FileSize,
'SizeFormatted' => self::FormatBytes($FileSize),
'ModifiedTime' => date('Y-m-d H:i:s', filemtime($FilePath)),
'IsCurrent' => $FilePath === $CurrentLogFile,
];
}
}
}
}
// 按修改时间排序(最新的在前)
usort($Files, function ($A, $B) {
return filemtime($B['Path']) - filemtime($A['Path']);
});
return [
'LogPath' => $LogPath,
'CurrentLogFile' => $CurrentLogFile,
'TotalFiles' => count($Files),
'TotalSize' => $TotalSize,
'TotalSizeFormatted' => self::FormatBytes($TotalSize),
'MaxFileSize' => self::$Config['MaxFileSize'],
'MaxFileSizeFormatted' => self::FormatBytes(self::$Config['MaxFileSize']),
'MaxLogFiles' => self::$Config['MaxLogFiles'],
'MinLevel' => self::$LevelNames[self::$Config['MinLevel']],
'Files' => $Files,
];
}
/**
* 格式化字节大小
*/
private static function FormatBytes($Bytes)
{
$Units = ['B', 'KB', 'MB', 'GB'];
$Bytes = max($Bytes, 0);
$Pow = floor(($Bytes ? log($Bytes) : 0) / log(1024));
$Pow = min($Pow, count($Units) - 1);
$Bytes /= (1 << (10 * $Pow));
return round($Bytes, 2) . ' ' . $Units[$Pow];
}
/**
* 设置配置
*
* @param array $Config 配置数组
*/
public static function SetConfig($Config)
{
self::InitConfig();
self::$Config = array_merge(self::$Config, $Config);
}
/**
* 获取配置
*
* @return array 配置数组
*/
public static function GetConfig()
{
self::InitConfig();
return self::$Config;
}
}



李枭龙10 个月前
AI生成文章:请以上所有知识进行深入分析,确定主要知识点,为每个知识点撰写详细说明并附上具有代表性且带有清晰注释的代码示例,接着根据内容拟定一个准确反映文档核心的标题,最后严格按照 Markdown 格式进行排版,确保文档规范美观,以满足初学者学习使用的需求。
李枭龙1 年前
X Lucas