PHP 怎么PHP 远程日志

wen PHP项目 2

本文目录导读:

PHP 怎么PHP 远程日志

  1. 使用 UDP 发送日志(推荐)
  2. 使用 HTTP API 方式
  3. 使用 Redis 作为中间件
  4. 使用成熟日志库
  5. 完整的最佳实践示例
  6. 使用建议

我来介绍几种 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%']);

使用建议

  1. UDP方式:高性能,但不保证送达,适合大量日志
  2. HTTP方式:可靠性高,但性能稍差,适合重要日志
  3. Redis方式:平衡性能和可靠性,适合中等规模
  4. 建议使用异步发送,避免影响主业务
  5. 建议使用缓冲机制,减少网络请求次数
  6. 记录关键上下文,便于问题排查

选择哪种方式取决于你的具体需求:性能要求、可靠性要求、日志量等。

抱歉,评论功能暂时关闭!