本文目录导读:

我来为你设计PHP项目中的日志接口与多通道实现方案。
日志接口设计
<?php
// 日志级别常量
interface LogLevel {
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
// 日志记录器接口
interface LoggerInterface {
public function emergency($message, array $context = []);
public function alert($message, array $context = []);
public function critical($message, array $context = []);
public function error($message, array $context = []);
public function warning($message, array $context = []);
public function notice($message, array $context = []);
public function info($message, array $context = []);
public function debug($message, array $context = []);
public function log($level, $message, array $context = []);
}
// 格式化器接口
interface FormatterInterface {
public function format(array $record);
}
// 处理器接口
interface HandlerInterface {
public function handle(array $record);
public function setFormatter(FormatterInterface $formatter);
public function getFormatter();
}
基础日志类实现
<?php
// 基础日志记录器
class Logger implements LoggerInterface {
protected $name;
protected $handlers = [];
protected $processors = [];
protected $timezone;
public function __construct($name, array $handlers = [], array $processors = []) {
$this->name = $name;
$this->handlers = $handlers;
$this->processors = $processors;
$this->timezone = new \DateTimeZone('Asia/Shanghai');
}
public function addHandler(HandlerInterface $handler) {
$this->handlers[] = $handler;
}
public function addProcessor(callable $processor) {
$this->processors[] = $processor;
}
public function emergency($message, array $context = []) {
$this->log(LogLevel::EMERGENCY, $message, $context);
}
public function alert($message, array $context = []) {
$this->log(LogLevel::ALERT, $message, $context);
}
public function critical($message, array $context = []) {
$this->log(LogLevel::CRITICAL, $message, $context);
}
public function error($message, array $context = []) {
$this->log(LogLevel::ERROR, $message, $context);
}
public function warning($message, array $context = []) {
$this->log(LogLevel::WARNING, $message, $context);
}
public function notice($message, array $context = []) {
$this->log(LogLevel::NOTICE, $message, $context);
}
public function info($message, array $context = []) {
$this->log(LogLevel::INFO, $message, $context);
}
public function debug($message, array $context = []) {
$this->log(LogLevel::DEBUG, $message, $context);
}
public function log($level, $message, array $context = []) {
if (empty($this->handlers)) {
throw new \RuntimeException('No handlers configured');
}
$record = $this->createRecord($level, $message, $context);
// 应用处理器
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
// 传递到所有处理器
foreach ($this->handlers as $handler) {
$handler->handle($record);
}
}
protected function createRecord($level, $message, array $context = []) {
return [
'message' => $this->interpolate($message, $context),
'level' => $level,
'level_name' => strtoupper($level),
'context' => $context,
'channel' => $this->name,
'datetime' => new \DateTime('now', $this->timezone),
'extra' => [],
];
}
protected function interpolate($message, array $context = []) {
// 替换消息中的占位符
$replace = [];
foreach ($context as $key => $val) {
if (is_string($val) || method_exists($val, '__toString')) {
$replace['{' . $key . '}'] = $val;
}
}
return strtr($message, $replace);
}
}
处理器实现
<?php
// 文件处理器
class FileHandler implements HandlerInterface {
protected $formatter;
protected $filename;
protected $maxFiles = 30;
public function __construct($filename, $level = LogLevel::DEBUG) {
$this->filename = $filename;
$this->level = $level;
$this->ensureDirectoryExists();
}
public function handle(array $record) {
// 检查日志级别
if (!$this->isHandling($record['level'])) {
return false;
}
$formatted = $this->formatter->format($record);
$this->write($formatted);
return true;
}
public function setFormatter(FormatterInterface $formatter) {
$this->formatter = $formatter;
}
public function getFormatter() {
if (!$this->formatter) {
$this->formatter = new LineFormatter();
}
return $this->formatter;
}
protected function isHandling($level) {
$levels = [
LogLevel::DEBUG => 100,
LogLevel::INFO => 200,
LogLevel::NOTICE => 250,
LogLevel::WARNING => 300,
LogLevel::ERROR => 400,
LogLevel::CRITICAL => 500,
LogLevel::ALERT => 550,
LogLevel::EMERGENCY => 600,
];
return $levels[$level] >= $levels[$this->level];
}
protected function write($message) {
$this->rotateLogs();
file_put_contents($this->filename, $message, FILE_APPEND | LOCK_EX);
}
protected function rotateLogs() {
if (!file_exists($this->filename)) {
return;
}
for ($i = $this->maxFiles - 1; $i > 0; $i--) {
$oldFile = $this->filename . '.' . $i;
$newFile = $this->filename . '.' . ($i + 1);
if (file_exists($oldFile)) {
if ($i == $this->maxFiles - 1) {
unlink($oldFile);
} else {
rename($oldFile, $newFile);
}
}
}
rename($this->filename, $this->filename . '.1');
}
protected function ensureDirectoryExists() {
$directory = dirname($this->filename);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
}
}
// 数据库处理器
class DatabaseHandler implements HandlerInterface {
protected $db;
protected $table;
protected $formatter;
public function __construct(PDO $db, $table = 'logs') {
$this->db = $db;
$this->table = $table;
$this->initTable();
}
public function handle(array $record) {
$formatted = $this->formatter->format($record);
$stmt = $this->db->prepare(
"INSERT INTO {$this->table} (level, message, context, created_at)
VALUES (:level, :message, :context, :created_at)"
);
return $stmt->execute([
':level' => $record['level_name'],
':message' => $formatted,
':context' => json_encode($record['context']),
':created_at' => $record['datetime']->format('Y-m-d H:i:s')
]);
}
public function setFormatter(FormatterInterface $formatter) {
$this->formatter = $formatter;
}
public function getFormatter() {
if (!$this->formatter) {
$this->formatter = new LineFormatter();
}
return $this->formatter;
}
protected function initTable() {
$sql = "CREATE TABLE IF NOT EXISTS {$this->table} (
id INT AUTO_INCREMENT PRIMARY KEY,
level VARCHAR(20) NOT NULL,
message TEXT NOT NULL,
context TEXT,
created_at DATETIME NOT NULL,
INDEX idx_level (level),
INDEX idx_created_at (created_at)
)";
$this->db->exec($sql);
}
}
// 邮件处理器
class MailHandler implements HandlerInterface {
protected $to;
protected $subject;
protected $from;
protected $formatter;
public function __construct($to, $subject = 'Log Alert', $from = 'noreply@example.com') {
$this->to = $to;
$this->subject = $subject;
$this->from = $from;
}
public function handle(array $record) {
// 只发送ERROR及以上级别的日志
$criticalLevels = [LogLevel::ERROR, LogLevel::CRITICAL, LogLevel::ALERT, LogLevel::EMERGENCY];
if (!in_array($record['level'], $criticalLevels)) {
return false;
}
$formatted = $this->formatter->format($record);
$headers = "From: {$this->from}\r\n";
$headers .= "Content-Type: text/html; charset=UTF-8\r\n";
return mail($this->to, $this->subject, nl2br($formatted), $headers);
}
public function setFormatter(FormatterInterface $formatter) {
$this->formatter = $formatter;
}
public function getFormatter() {
if (!$this->formatter) {
$this->formatter = new HtmlFormatter();
}
return $this->formatter;
}
}
// Elasticsearch处理器
class ElasticsearchHandler implements HandlerInterface {
protected $client;
protected $index;
protected $formatter;
public function __construct($hosts = ['localhost:9200'], $index = 'logs') {
$this->client = new \Elasticsearch\Client([
'hosts' => $hosts
]);
$this->index = $index . '_' . date('Y.m.d');
}
public function handle(array $record) {
$params = [
'index' => $this->index,
'type' => '_doc',
'body' => [
'level' => $record['level_name'],
'message' => $record['message'],
'channel' => $record['channel'],
'context' => $record['context'],
'extra' => $record['extra'],
'timestamp' => $record['datetime']->format('c')
]
];
try {
$this->client->index($params);
return true;
} catch (\Exception $e) {
return false;
}
}
public function setFormatter(FormatterInterface $formatter) {
$this->formatter = $formatter;
}
public function getFormatter() {
if (!$this->formatter) {
$this->formatter = new JsonFormatter();
}
return $this->formatter;
}
}
格式化器实现
<?php
// 行格式化器
class LineFormatter implements FormatterInterface {
protected $format;
public function __construct($format = "[%datetime%] %channel%.%level_name%: %message% %context%\n") {
$this->format = $format;
}
public function format(array $record) {
$output = $this->format;
foreach ($record as $key => $val) {
if ($key === 'datetime') {
$val = $val->format('Y-m-d H:i:s');
} elseif (is_array($val)) {
$val = !empty($val) ? json_encode($val, JSON_UNESCAPED_UNICODE) : '';
}
$output = str_replace('%' . $key . '%', $val, $output);
}
return $output;
}
}
// JSON格式化器
class JsonFormatter implements FormatterInterface {
public function format(array $record) {
$record['datetime'] = $record['datetime']->format('Y-m-d H:i:s');
return json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
}
}
// HTML格式化器
class HtmlFormatter implements FormatterInterface {
public function format(array $record) {
$levelColors = [
'DEBUG' => '#808080',
'INFO' => '#0000FF',
'NOTICE' => '#90EE90',
'WARNING' => '#FFA500',
'ERROR' => '#FF0000',
'CRITICAL' => '#8B0000',
'ALERT' => '#FF00FF',
'EMERGENCY' => '#FF1493',
];
$color = isset($levelColors[$record['level_name']]) ? $levelColors[$record['level_name']] : '#000000';
return sprintf(
'<div style="color: %s; margin: 5px 0; padding: 10px; border: 1px solid %s;">
<strong>[%s]</strong> %s.%s: %s<br>
<small>Context: %s</small>
</div>',
$color,
$color,
$record['datetime']->format('Y-m-d H:i:s'),
$record['channel'],
$record['level_name'],
htmlspecialchars($record['message']),
!empty($record['context']) ? json_encode($record['context']) : 'None'
);
}
}
使用示例
<?php
// 创建日志记录器
$logger = new Logger('my_app');
// 添加文件处理器
$fileHandler = new FileHandler('/var/log/my_app.log', LogLevel::DEBUG);
$fileHandler->setFormatter(new LineFormatter());
$logger->addHandler($fileHandler);
// 添加数据库处理器
$db = new PDO('mysql:host=localhost;dbname=logs', 'user', 'password');
$dbHandler = new DatabaseHandler($db, 'application_logs');
$dbHandler->setFormatter(new JsonFormatter());
$logger->addHandler($dbHandler);
// 添加邮件处理器(仅错误级别)
$mailHandler = new MailHandler(
'admin@example.com',
'Application Error Alert',
'error-noreply@example.com'
);
$mailHandler->setFormatter(new HtmlFormatter());
$logger->addHandler($mailHandler);
// 添加Elasticsearch处理器
$esHandler = new ElasticsearchHandler(
['localhost:9200'],
'application_logs'
);
$esHandler->setFormatter(new JsonFormatter());
$logger->addHandler($esHandler);
// 添加处理器(上下文信息)
$logger->addProcessor(function ($record) {
$record['extra']['ip'] = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$record['extra']['user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
$record['extra']['request_uri'] = $_SERVER['REQUEST_URI'] ?? '/';
return $record;
});
// 使用日志记录器
$logger->info('用户登录成功', [
'user_id' => 12345,
'username' => 'john_doe',
'ip' => '192.168.1.1'
]);
$logger->error('数据库连接失败', [
'error_code' => 1045,
'error_message' => 'Access denied for user'
]);
try {
// 某些操作
throw new \Exception('测试异常');
} catch (\Exception $e) {
$logger->critical('系统异常', [
'exception' => [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
]
]);
}
工厂类简化配置
<?php
class LoggerFactory {
protected static $config = [
'default' => 'stack',
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'daily']
],
'single' => [
'driver' => 'single',
'path' => '/var/log/app.log',
'level' => LogLevel::DEBUG
],
'daily' => [
'driver' => 'daily',
'path' => '/var/log/app',
'level' => LogLevel::DEBUG,
'days' => 14
],
'slack' => [
'driver' => 'slack',
'url' => 'https://hooks.slack.com/services/...',
'level' => LogLevel::ERROR
],
'syslog' => [
'driver' => 'syslog',
'level' => LogLevel::DEBUG
],
'errorlog' => [
'driver' => 'errorlog',
'level' => LogLevel::DEBUG
],
'mail' => [
'driver' => 'mail',
'to' => 'admin@example.com',
'level' => LogLevel::ERROR
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'level' => LogLevel::DEBUG
]
]
];
public static function createLogger($channel = null) {
$channel = $channel ?: self::$config['default'];
if (!isset(self::$config['channels'][$channel])) {
throw new \InvalidArgumentException("Logger channel '{$channel}' not found");
}
$config = self::$config['channels'][$channel];
switch ($config['driver']) {
case 'stack':
return self::createStackLogger($config['channels']);
case 'single':
return self::createSingleLogger($config);
case 'daily':
return self::createDailyLogger($config);
case 'slack':
return self::createSlackLogger($config);
case 'syslog':
return self::createSyslogLogger($config);
case 'errorlog':
return self::createErrorlogLogger($config);
default:
throw new \InvalidArgumentException("Unknown driver: {$config['driver']}");
}
}
protected static function createStackLogger(array $channels) {
$logger = new Logger('app');
foreach ($channels as $channelName) {
$channelLogger = self::createLogger($channelName);
$logger->addHandler(... $channelLogger->handlers); // 简化处理
}
return $logger;
}
// ... 其他创建方法
}
最佳实践
日志级别使用建议
- DEBUG: 开发调试信息
- INFO: 正常业务流程信息
- NOTICE: 正常但重要的事件
- WARNING: 可能出现问题的状况
- ERROR: 运行时错误
- CRITICAL: 临界错误
- ALERT: 需要立即修复的问题
- EMERGENCY: 系统不可用
性能优化
// 在记录前检查级别
if ($logger->isHandling(LogLevel::INFO)) {
$logger->info('记录信息');
}
// 批量日志处理
class BufferedHandler extends AbstractHandler {
protected $buffer = [];
protected $bufferSize = 100;
public function handle(array $record) {
$this->buffer[] = $record;
if (count($this->buffer) >= $this->bufferSize) {
$this->flush();
}
}
public function flush() {
// 批量写入数据库或文件
}
public function __destruct() {
$this->flush();
}
}
这个系统设计实现了多通道日志记录,支持文件、数据库、邮件、Elasticsearch等多种输出方式,具有良好的扩展性和灵活性。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播