最小實現
<?php
function log_write($message, $file = 'app.log') {
$line = date('Y-m-d H:i:s') . ' ' . $message . PHP_EOL;
file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
}
// 使用
log_write('User login: uid=123');
log_write('API request: /order/create', 'api.log');
?>
關鍵參數解析
| 常量 | 作用 |
FILE_APPEND追加模式,不覆蓋原文件 |
LOCK_EX獨佔鎖,防止多進程並發寫入導致數據交織/丟失 |
PHP_EOL跨平台換行符(Windows \r\n, Linux/Unix \n) |
進階:分級日志類
<?php
class Logger {
private $path;
private $minLevel;
private $levels = ['debug'=>0, 'info'=>1, 'notice'=>2, 'warning'=>3, 'error'=>4, 'critical'=>5, 'alert'=>6, 'emergency'=>7];
public function __construct(string $path, string $minLevel = 'info') {
$this->path = $path;
$this->minLevel = $this->levels[$minLevel] ?? 1;
}
public function log(string $level, string $message, array $context = []) {
if (($this->levels[$level] ?? 1) < $this->minLevel) return;
$time = date('c'); // ISO 8601
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[$time] {$level}: {$message}{$contextStr}" . PHP_EOL;
file_put_contents($this->path, $line, FILE_APPEND | LOCK_EX);
}
// 魔術方法快捷調用
public function __call($level, $args) {
if (isset($this->levels[$level])) {
$this->log($level, $args[0], $args[1] ?? []);
}
}
}
// 使用
$log = new Logger(__DIR__ . '/storage/logs/app.log', 'debug');
$log->info('User registered', ['uid' => 123, 'email' => 'a@b.com']);
$log->error('Payment failed', ['order_id' => 999, 'reason' => 'insufficient_funds']);
?>
生產環境建議
- 日志輪轉:
logrotate 每日切割、壓縮、保留 30 天
- 結構化日志:JSON 格式,接入 ELK/EFK/Loki 分析
- 異步寫入:高併發下用
syslog、Redis LIST、Kafka 解耦
- 敏感信息過濾:自動脫敏手機、身份證、銀行卡、密碼字段
- 標準化:遵循 PSR-3 LoggerInterface,可替換 Monolog 等成熟庫
Monolog 快速接入
composer require monolog/monolog
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
$log = new Logger('app');
$log->pushHandler(new RotatingFileHandler(__DIR__.'/logs/app.log', 30, Logger::DEBUG));
$log->info('Monolog ready', ['version' => '2.x']);