本文目录导读:

我来介绍几种 PHP 远程日志的实现方式:
使用 UDP 发送日志(推荐)
服务端 (日志收集器)
// log_server.php
$socket = stream_socket_server("udp://0.0.0.0:9999", $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
die("$errstr ($errno)");
}
while (true) {
$pkt = stream_socket_recvfrom($socket, 65535, 0, $peer);
$logData = json_decode($pkt, true);
// 写入日志文件
$logLine = sprintf(
"[%s] [%s] %s - %s\n",
$logData['timestamp'],
$logData['level'],
$logData['message'],
json_encode($logData['context'] ?? [])
);
file_put_contents('/var/log/remote_app.log', $logLine, FILE_APPEND);
echo "Received from: $peer\n";
}
客户端 (应用端)
// log_client.php
class RemoteLogger {
private $host;
private $port;
private $appName;
public function __construct($host, $port, $appName = 'app') {
$this->host = $host;
$this->port = $port;
$this->appName = $appName;
}
public function log($level, $message, array $context = []) {
$data = [
'timestamp' => date('Y-m-d H:i:s'),
'app' => $this->appName,
'level' => $level,
'message' => $message,
'context' => $context,
'server_ip' => $_SERVER['SERVER_ADDR'] ?? 'cli',
'request_id' => uniqid()
];
$socket = stream_socket_client("udp://{$this->host}:{$this->port}",
$errno, $errstr, 3);
if ($socket) {
stream_socket_sendto($socket, json_encode($data), 0,
"{$this->host}:{$this->port}");
fclose($socket);
}
}
}
// 使用示例
$logger = new RemoteLogger('192.168.1.100', 9999, 'myapp');
$logger->log('ERROR', '数据库连接失败', [
'db_host' => 'localhost',
'error_code' => 1045
]);
$logger->log('INFO', '用户登录成功', ['user_id' => 123]);
使用 HTTP API 方式
服务端 (REST API)
// log_api.php
<?php
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['message'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid request']);
exit;
}
$logEntry = sprintf(
"[%s] [%s] %s | App: %s | IP: %s | %s\n",
date('Y-m-d H:i:s'),
$input['level'] ?? 'INFO',
$input['message'],
$input['app'] ?? 'unknown',
$_SERVER['REMOTE_ADDR'],
json_encode($input['context'] ?? [])
);
file_put_contents('/var/log/remote_api.log', $logEntry, FILE_APPEND | LOCK_EX);
echo json_encode(['status' => 'ok']);
客户端
// http_logger.php
class HttpRemoteLogger {
private $endpoint;
private $apiKey;
public function __construct($endpoint, $apiKey = null) {
$this->endpoint = $endpoint;
$this->apiKey = $apiKey;
}
public function log($level, $message, array $context = []) {
$data = [
'level' => $level,
'message' => $message,
'context' => $context,
'app' => 'myapp',
'timestamp' => microtime(true)
];
$ch = curl_init($this->endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
$this->apiKey ? "X-API-Key: {$this->apiKey}" : ''
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode === 200;
}
}
// 使用
$logger = new HttpRemoteLogger('http://log-server.example.com/log.php', 'my-secret-key');
$logger->log('WARNING', '磁盘空间不足', ['free_space' => '500MB']);
使用 Redis 作为中间件
// 发送端
class RedisRemoteLogger {
private $redis;
private $listKey;
public function __construct($redisHost, $redisPort, $listKey = 'app_logs') {
$this->redis = new Redis();
$this->redis->connect($redisHost, $redisPort);
$this->listKey = $listKey;
}
public function log($level, $message, array $context = []) {
$logEntry = [
'timestamp' => microtime(true),
'level' => $level,
'message' => $message,
'context' => $context,
'hostname' => gethostname(),
'pid' => getmypid()
];
$this->redis->rPush($this->listKey, json_encode($logEntry));
// 可选:设置过期时间,防止日志堆积
$this->redis->expire($this->listKey, 86400); // 24小时过期
}
}
// 接收端 - 可以单独进程消费
class LogConsumer {
private $redis;
private $listKey;
public function __construct($redisHost, $redisPort, $listKey = 'app_logs') {
$this->redis = new Redis();
$this->redis->connect($redisHost, $redisPort);
$this->listKey = $listKey;
}
public function consume() {
while (true) {
// 阻塞读取,等待新日志
$log = $this->redis->blPop($this->listKey, 10);
if ($log) {
$logData = json_decode($log[1], true);
$this->writeToFile($logData);
$this->sendToElasticsearch($logData); // 可选
}
}
}
private function writeToFile($data) {
$line = sprintf(
"[%s] [%s] %s [%s] %s\n",
date('Y-m-d H:i:s', $data['timestamp']),
$data['level'],
$data['hostname'],
$data['message'],
json_encode($data['context'])
);
file_put_contents('/var/log/redis_logs.log', $line, FILE_APPEND);
}
}
使用成熟日志库
Monolog + Socket Handler
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\SocketHandler;
use Monolog\Formatter\JsonFormatter;
// 创建日志通道
$log = new Logger('myapp');
// 创建Socket处理器
$handler = new SocketHandler("tcp://192.168.1.100:9090");
$handler->setFormatter(new JsonFormatter());
$log->pushHandler($handler);
// 使用
$log->error('数据库错误', [
'sql' => 'SELECT * FROM users',
'error_code' => 1054
]);
$log->info('用户操作', ['action' => 'login', 'user_id' => 456]);
完整的最佳实践示例
<?php
// RemoteLogger.php - 完整的远程日志类
class RemoteLogger {
private static $instance = null;
private $config;
private $buffer = [];
private $bufferSize = 10;
public function __construct(array $config = []) {
$this->config = array_merge([
'driver' => 'udp', // udp, http, redis
'host' => '127.0.0.1',
'port' => 9999,
'app_name' => 'default',
'buffer_size' => 10,
'async' => true
], $config);
$this->bufferSize = $this->config['buffer_size'];
if ($this->config['async']) {
register_shutdown_function([$this, 'flush']);
}
}
public static function getInstance(array $config = []) {
if (self::$instance === null) {
self::$instance = new self($config);
}
return self::$instance;
}
public function log($level, $message, array $context = []) {
$entry = [
'timestamp' => date('Y-m-d\TH:i:s.u'),
'level' => strtoupper($level),
'message' => $message,
'app' => $this->config['app_name'],
'server' => [
'ip' => $_SERVER['SERVER_ADDR'] ?? gethostbyname(gethostname()),
'hostname' => gethostname(),
'pid' => getmypid()
],
'context' => $context,
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3)
];
if ($this->config['async']) {
$this->buffer[] = $entry;
if (count($this->buffer) >= $this->bufferSize) {
$this->flush();
}
} else {
$this->send($entry);
}
return $this;
}
public function flush() {
if (empty($this->buffer)) {
return;
}
$entries = $this->buffer;
$this->buffer = [];
// 批量发送
foreach (array_chunk($entries, 5) as $batch) {
$this->sendBatch($batch);
}
}
private function send(array $entry) {
switch ($this->config['driver']) {
case 'udp':
$this->sendUdp(json_encode($entry));
break;
case 'http':
$this->sendHttp([$entry]);
break;
case 'redis':
$this->sendRedis(json_encode($entry));
break;
}
}
private function sendBatch(array $entries) {
$data = json_encode($entries);
switch ($this->config['driver']) {
case 'udp':
// UDP 适合发送单个小包
foreach ($entries as $entry) {
$this->sendUdp(json_encode($entry));
}
break;
case 'http':
$this->sendHttp($entries);
break;
case 'redis':
foreach ($entries as $entry) {
$this->sendRedis(json_encode($entry));
}
break;
}
}
private function sendUdp($data) {
$socket = @stream_socket_client(
"udp://{$this->config['host']}:{$this->config['port']}",
$errno, $errstr, 2
);
if ($socket) {
@stream_socket_sendto($socket, $data, 0,
"{$this->config['host']}:{$this->config['port']}");
@fclose($socket);
}
}
private function sendHttp(array $entries) {
$ch = curl_init("http://{$this->config['host']}:{$this->config['port']}/log");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['logs' => $entries]),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 1,
CURLOPT_CONNECTTIMEOUT => 1
]);
curl_exec($ch);
curl_close($ch);
}
private function sendRedis($data) {
try {
$redis = new Redis();
$redis->connect($this->config['host'], $this->config['port'] ?? 6379, 1);
$redis->rPush('app_logs:' . $this->config['app_name'], $data);
$redis->close();
} catch (\Exception $e) {
// Redis 连接失败,静默处理
}
}
// 便捷方法
public function debug($message, array $context = []) {
return $this->log('debug', $message, $context);
}
public function info($message, array $context = []) {
return $this->log('info', $message, $context);
}
public function warning($message, array $context = []) {
return $this->log('warning', $message, $context);
}
public function error($message, array $context = []) {
return $this->log('error', $message, $context);
}
public function critical($message, array $context = []) {
return $this->log('critical', $message, $context);
}
}
// 全局函数
function remote_log($level, $message, array $context = []) {
return RemoteLogger::getInstance()->log($level, $message, $context);
}
// 使用示例
// 初始化
$logger = RemoteLogger::getInstance([
'driver' => 'udp',
'host' => '192.168.1.100',
'port' => 9999,
'app_name' => 'ecommerce',
'async' => true
]);
// 记录日志
$logger->info('用户注册成功', ['user_id' => 12345, 'email' => 'user@example.com']);
$logger->error('支付失败', ['order_id' => 'ORD-2024-001', 'amount' => 99.99]);
// 或者使用全局函数
remote_log('alert', '服务器负载过高', ['cpu' => '95%', 'memory' => '80%']);
使用建议
- UDP方式:高性能,但不保证送达,适合大量日志
- HTTP方式:可靠性高,但性能稍差,适合重要日志
- Redis方式:平衡性能和可靠性,适合中等规模
- 建议使用异步发送,避免影响主业务
- 建议使用缓冲机制,减少网络请求次数
- 记录关键上下文,便于问题排查
选择哪种方式取决于你的具体需求:性能要求、可靠性要求、日志量等。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播