PHP访问日志分析脚本

wen PHP项目 2

本文目录导读:

PHP访问日志分析脚本

  1. 基础版本
  2. 进阶版本(带JSON输出和更多功能)
  3. 使用说明

我来为您创建一个功能完善的PHP访问日志分析脚本:

基础版本

<?php
/**
 * PHP访问日志分析脚本
 * 使用方法: php log_analyzer.php /path/to/access.log
 */
class LogAnalyzer {
    private $logFile;
    private $totalRequests = 0;
    private $uniqueIPs = [];
    private $statusCodes = [];
    private $requestMethods = [];
    private $urls = [];
    private $userAgents = [];
    private $trafficBytes = 0;
    private $timeStats = [];
    private $slowestRequests = [];
    private $errorLogs = [];
    public function __construct($logFile) {
        if (!file_exists($logFile)) {
            die("错误: 日志文件不存在\n");
        }
        $this->logFile = $logFile;
    }
    public function analyze() {
        echo "开始分析日志文件: {$this->logFile}\n";
        echo str_repeat("=", 60) . "\n\n";
        $handle = fopen($this->logFile, 'r');
        if (!$handle) {
            die("错误: 无法打开日志文件\n");
        }
        $lineCount = 0;
        $startTime = microtime(true);
        while (($line = fgets($handle)) !== false) {
            $lineCount++;
            $this->parseLine($line);
            // 显示进度
            if ($lineCount % 10000 === 0) {
                echo "已处理 {$lineCount} 行...\n";
            }
        }
        fclose($handle);
        $elapsed = microtime(true) - $startTime;
        echo "\n分析完成! 耗时: " . round($elapsed, 2) . " 秒\n\n";
        $this->printReport();
    }
    private function parseLine($line) {
        // 解析Apache/Nginx日志格式
        // 示例: 127.0.0.1 - - [10/Oct/2023:13:55:36 +0000] "GET /index.php HTTP/1.1" 200 2326 "http://example.com" "Mozilla/5.0"
        $pattern = '/^(\S+) (\S+) (\S+) \[([^\]]+)\] "([^"]*)" (\d{3}) (\d+) "([^"]*)" "([^"]*)"$/';
        if (preg_match($pattern, $line, $matches)) {
            $ip = $matches[1];
            $time = $matches[4];
            $request = $matches[5];
            $status = $matches[6];
            $bytes = $matches[7];
            $referer = $matches[8];
            $userAgent = $matches[9];
            // 统计各项数据
            $this->totalRequests++;
            $this->uniqueIPs[$ip] = ($this->uniqueIPs[$ip] ?? 0) + 1;
            $this->statusCodes[$status] = ($this->statusCodes[$status] ?? 0) + 1;
            // 解析请求方法
            if (preg_match('/^(\S+) (\S+) (\S+)/', $request, $reqMatches)) {
                $method = $reqMatches[1];
                $url = $reqMatches[2];
                $this->requestMethods[$method] = ($this->requestMethods[$method] ?? 0) + 1;
                $this->urls[$url] = ($this->urls[$url] ?? 0) + 1;
            }
            $this->userAgents[$userAgent] = ($this->userAgents[$userAgent] ?? 0) + 1;
            $this->trafficBytes += intval($bytes);
            // 记录4xx和5xx错误
            if ($status >= 400) {
                $this->errorLogs[] = [
                    'ip' => $ip,
                    'time' => $time,
                    'request' => $request,
                    'status' => $status
                ];
            }
            // 记录最慢请求 (每分钟的请求数)
            $timeKey = substr($time, 0, 2) . ':00';
            $this->timeStats[$timeKey] = ($this->timeStats[$timeKey] ?? 0) + 1;
        }
    }
    private function printReport() {
        echo "=== 访问日志分析报告 ===\n\n";
        // 1. 总体统计
        echo "【总体统计】\n";
        echo "总请求数: " . number_format($this->totalRequests) . "\n";
        echo "独立IP数: " . count($this->uniqueIPs) . "\n";
        echo "总流量: " . $this->formatBytes($this->trafficBytes) . "\n";
        echo "平均每个请求流量: " . $this->formatBytes($this->trafficBytes / max(1, $this->totalRequests)) . "\n\n";
        // 2. 状态码分布
        echo "【HTTP状态码分布】\n";
        arsort($this->statusCodes);
        foreach ($this->statusCodes as $status => $count) {
            $percent = ($count / $this->totalRequests) * 100;
            echo "  {$status}: " . number_format($count) . " (" . round($percent, 2) . "%)\n";
        }
        echo "\n";
        // 3. 请求方法分布
        echo "【请求方法分布】\n";
        arsort($this->requestMethods);
        foreach ($this->requestMethods as $method => $count) {
            echo "  {$method}: " . number_format($count) . "\n";
        }
        echo "\n";
        // 4. Top 10 IP地址
        echo "【Top 10 IP地址】\n";
        arsort($this->uniqueIPs);
        $topIPs = array_slice($this->uniqueIPs, 0, 10, true);
        $rank = 1;
        foreach ($topIPs as $ip => $count) {
            echo "  {$rank}. {$ip} - " . number_format($count) . " 次请求\n";
            $rank++;
        }
        echo "\n";
        // 5. Top 10 访问页面
        echo "【Top 10 访问页面】\n";
        arsort($this->urls);
        $topURLs = array_slice($this->urls, 0, 10, true);
        $rank = 1;
        foreach ($topURLs as $url => $count) {
            echo "  {$rank}. {$url} - " . number_format($count) . " 次\n";
            $rank++;
        }
        echo "\n";
        // 6. 每小时请求分布
        echo "【每小时请求分布】\n";
        ksort($this->timeStats);
        $maxCount = max($this->timeStats);
        foreach ($this->timeStats as $hour => $count) {
            $barLength = (int)($count / $maxCount * 50);
            $bar = str_repeat('#', $barLength);
            echo "  {$hour}: " . number_format($count) . " {$bar}\n";
        }
        echo "\n";
        // 7. 错误请求统计
        $errorCount = count($this->errorLogs);
        if ($errorCount > 0) {
            echo "【错误请求统计】\n";
            echo "4xx/5xx错误总数: " . number_format($errorCount) . "\n";
            // 按IP统计错误
            $errorsByIP = [];
            foreach ($this->errorLogs as $error) {
                $errorsByIP[$error['ip']] = ($errorsByIP[$error['ip']] ?? 0) + 1;
            }
            arsort($errorsByIP);
            echo "\n错误最多的IP:\n";
            $topErrors = array_slice($errorsByIP, 0, 5, true);
            foreach ($topErrors as $ip => $count) {
                echo "  {$ip}: " . number_format($count) . " 次错误\n";
            }
            // 显示最近5条错误
            echo "\n最近5条错误:\n";
            $recentErrors = array_slice($this->errorLogs, -5);
            foreach ($recentErrors as $error) {
                echo "  [{$error['time']}] {$error['ip']} - {$error['status']} - {$error['request']}\n";
            }
        }
        // 8. Top 10 User Agents
        echo "\n【Top 10 User Agents】\n";
        arsort($this->userAgents);
        $topAgents = array_slice($this->userAgents, 0, 10, true);
        $rank = 1;
        foreach ($topAgents as $agent => $count) {
            $shortAgent = strlen($agent) > 50 ? substr($agent, 0, 47) . "..." : $agent;
            echo "  {$rank}. {$shortAgent} - " . number_format($count) . " 次\n";
            $rank++;
        }
    }
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max(0, $bytes);
        $power = $bytes > 0 ? floor(log($bytes, 1024)) : 0;
        $power = min($power, count($units) - 1);
        $value = $bytes / pow(1024, $power);
        return number_format($value, 2) . ' ' . $units[$power];
    }
}
// 命令行入口
if (php_sapi_name() === 'cli') {
    if ($argc < 2) {
        echo "使用方法: php " . basename($argv[0]) . " /path/to/access.log\n";
        echo "示例:     php " . basename($argv[0]) . " /var/log/apache2/access.log\n";
        exit(1);
    }
    $logFile = $argv[1];
    try {
        $analyzer = new LogAnalyzer($logFile);
        $analyzer->analyze();
    } catch (Exception $e) {
        echo "错误: " . $e->getMessage() . "\n";
        exit(1);
    }
}

进阶版本(带JSON输出和更多功能)

<?php
/**
 * 增强版PHP访问日志分析脚本
 * 支持多种输出格式和高级分析
 */
class AdvancedLogAnalyzer {
    private $logFile;
    private $data = [];
    private $config;
    public function __construct($logFile, $config = []) {
        $this->logFile = $logFile;
        $this->config = array_merge([
            'output_format' => 'text',  // text, json, csv
            'top_count' => 10,
            'user_agent_filter' => null,
            'ip_filter' => null,
            'time_filter_start' => null,
            'time_filter_end' => null,
            'status_code_filter' => null,
        ], $config);
        $this->initializeData();
    }
    private function initializeData() {
        $this->data = [
            'total_requests' => 0,
            'total_bytes' => 0,
            'ips' => [],
            'status_codes' => [],
            'methods' => [],
            'urls' => [],
            'user_agents' => [],
            'referrers' => [],
            'time_series' => [],
            'errors' => [],
            'slow_requests' => [],
            'performance' => [
                'total_time' => 0,
                'max_time' => 0,
                'avg_time' => 0
            ]
        ];
    }
    public function analyze() {
        if (!file_exists($this->logFile)) {
            throw new Exception("日志文件不存在: {$this->logFile}");
        }
        $handle = fopen($this->logFile, 'r');
        if (!$handle) {
            throw new Exception("无法打开日志文件");
        }
        $lineCount = 0;
        $startTime = microtime(true);
        while (($line = fgets($handle)) !== false) {
            $lineCount++;
            $this->processLine($line);
            // 进度显示
            if ($lineCount % 50000 === 0) {
                echo "\r已处理: " . number_format($lineCount) . " 行...";
            }
        }
        fclose($handle);
        $this->data['performance']['total_time'] = microtime(true) - $startTime;
        $this->data['total_lines'] = $lineCount;
        echo "\r处理完成: " . number_format($lineCount) . " 行\n";
        return $this->generateReport();
    }
    private function processLine($line) {
        // 支持常用日志格式
        $patterns = [
            // Nginx/Apache 组合格式
            '/^(\S+) (\S+) (\S+) \[([^\]]+)\] "(\S+) (\S+) [^"]*" (\d{3}) (\d+) "([^"]*)" "([^"]*)"(?: "(.*)")?$/',
            // 简化格式
            '/^(\S+) \[([^\]]+)\] "(\S+) (\S+) [^"]*" (\d{3}) (\d+)$/'
        ];
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $line, $matches)) {
                $this->parseMatch($matches);
                break;
            }
        }
    }
    private function parseMatch($matches) {
        $ip = $matches[1];
        // 获取时间
        preg_match('/\[([^\]]+)\]/', $matches[0], $timeMatch);
        $timestamp = strtotime($timeMatch[1]);
        // 应用过滤条件
        if (!$this->applyFilters($ip, $timestamp)) {
            return;
        }
        // 请求信息
        if (count($matches) >= 10) {
            $method = $matches[5];
            $url = $matches[6];
            $status = $matches[7];
            $bytes = $matches[8];
            $referer = $matches[9];
            $userAgent = $matches[10];
        } else {
            $method = $matches[3];
            $url = $matches[4];
            $status = $matches[5];
            $bytes = $matches[6];
            $referer = '';
            $userAgent = '';
        }
        // 更新统计数据
        $this->data['total_requests']++;
        $this->data['total_bytes'] += intval($bytes);
        $this->incrementCounter('ips', $ip);
        $this->incrementCounter('status_codes', $status);
        $this->incrementCounter('methods', $method);
        $this->incrementCounter('urls', $url);
        $this->incrementCounter('user_agents', $userAgent);
        $this->incrementCounter('referrers', $referer);
        // 时间序列分析
        $hourKey = date('Y-m-d H:00', $timestamp);
        $this->incrementCounter('time_series', $hourKey);
        // 错误日志
        if ($status >= 400) {
            $this->data['errors'][] = [
                'ip' => $ip,
                'time' => date('Y-m-d H:i:s', $timestamp),
                'url' => $url,
                'method' => $method,
                'status' => $status,
                'user_agent' => $userAgent
            ];
        }
    }
    private function incrementCounter($category, $key) {
        if (!isset($this->data[$category][$key])) {
            $this->data[$category][$key] = 0;
        }
        $this->data[$category][$key]++;
    }
    private function applyFilters($ip, $timestamp) {
        if ($this->config['ip_filter'] && $ip !== $this->config['ip_filter']) {
            return false;
        }
        return true;
    }
    private function generateReport() {
        $data = $this->data;
        // 计算统计信息
        $totalRequests = $data['total_requests'];
        $uniqueIPs = count($data['ips']);
        $successRate = $totalRequests > 0 ? ($data['status_codes'][200] ?? 0) / $totalRequests * 100 : 0;
        $errorRate = $totalRequests > 0 ? 100 - $successRate : 0;
        // Top N 排序
        arsort($data['ips']);
        arsort($data['urls']);
        arsort($data['user_agents']);
        arsort($data['status_codes']);
        $report = [
            'metadata' => [
                'generated_at' => date('Y-m-d H:i:s'),
                'log_file' => basename($this->logFile),
                'analysis_time' => round($data['performance']['total_time'], 2) . 's'
            ],
            'summary' => [
                'total_requests' => $totalRequests,
                'unique_ips' => $uniqueIPs,
                'total_traffic' => $this->formatBytes($data['total_bytes']),
                'avg_requests_per_ip' => round($totalRequests / max(1, $uniqueIPs), 2),
                'success_rate' => round($successRate, 2) . '%',
                'error_rate' => round($errorRate, 2) . '%'
            ],
            'status_codes' => array_slice($data['status_codes'], 0, $this->config['top_count'], true),
            'top_ips' => array_slice($data['ips'], 0, $this->config['top_count'], true),
            'top_urls' => array_slice($data['urls'], 0, $this->config['top_count'], true),
            'top_user_agents' => array_slice($data['user_agents'], 0, $this->config['top_count'], true),
            'request_methods' => $data['methods'],
            'hourly_stats' => $data['time_series'],
            'errors' => array_slice($data['errors'], 0, $this->config['top_count'])
        ];
        return $this->formatOutput($report);
    }
    private function formatOutput($report) {
        switch ($this->config['output_format']) {
            case 'json':
                return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
            case 'csv':
                return $this->toCSV($report);
            default:
                return $this->toText($report);
        }
    }
    private function toText($report) {
        $output = "=== 访问日志分析报告 ===\n";
        $output .= "生成时间: {$report['metadata']['generated_at']}\n";
        $output .= "日志文件: {$report['metadata']['log_file']}\n";
        $output .= "分析耗时: {$report['metadata']['analysis_time']}\n\n";
        // 
        $output .= "【总体概况】\n";
        foreach ($report['summary'] as $key => $value) {
            $output .= "  " . ucwords(str_replace('_', ' ', $key)) . ": {$value}\n";
        }
        // 状态码
        $output .= "\n【状态码分布】\n";
        foreach ($report['status_codes'] as $code => $count) {
            $output .= "  {$code}: " . number_format($count) . "\n";
        }
        // Top IPs
        $output .= "\n【Top IP地址】\n";
        $rank = 1;
        foreach ($report['top_ips'] as $ip => $count) {
            $output .= "  {$rank}. {$ip} - " . number_format($count) . " 次\n";
            $rank++;
        }
        // Top URLs
        $output .= "\n【热门URL】\n";
        $rank = 1;
        foreach ($report['top_urls'] as $url => $count) {
            $shortUrl = strlen($url) > 60 ? substr($url, 0, 57) . "..." : $url;
            $output .= "  {$rank}. {$shortUrl} - " . number_format($count) . " 次\n";
            $rank++;
        }
        return $output;
    }
    private function toCSV($report) {
        // CSV输出逻辑
        $output = fopen('php://temp', 'r+');
        fputcsv($output, ['指标', '数值']);
        foreach ($report['summary'] as $key => $value) {
            fputcsv($output, [ucwords(str_replace('_', ' ', $key)), $value]);
        }
        rewind($output);
        $csv = stream_get_contents($output);
        fclose($output);
        return $csv;
    }
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        return round($bytes, 2) . ' ' . $units[$i];
    }
    // 保存报告到文件
    public function saveReport($report, $filename = null) {
        $filename = $filename ?? 'report_' . date('Ymd_His') . '.txt';
        file_put_contents($filename, $report);
        echo "报告已保存到: " . $filename . "\n";
    }
}
// 命令行界面
if (php_sapi_name() === 'cli') {
    // 解析命令行参数
    $options = getopt('f:o:t:h', ['file:', 'output:', 'top:', 'help']);
    echo "PHP访问日志分析工具 v2.0\n";
    echo "==========================\n\n";
    if (isset($options['h']) || $options['help']) {
        echo "用法: php log_analyzer_v2.php -f <日志文件> [选项]\n\n";
        echo "必选参数:\n";
        echo "  -f, --file <path>    指定日志文件路径\n\n";
        echo "可选参数:\n";
        echo "  -o, --output <format> 输出格式: text|json|csv (默认: text)\n";
        echo "  -t, --top <number>   显示Top N条数据 (默认: 10)\n";
        echo "  -h, --help           显示帮助信息\n";
        echo "\n示例:\n";
        echo "  php log_analyzer_v2.php -f /var/log/apache2/access.log\n";
        echo "  php log_analyzer_v2.php -f access.log -o json\n";
        echo "  php log_analyzer_v2.php -f access.log -t 20\n";
        exit(0);
    }
    $logFile = $options['f'] ?? $options['file'] ?? null;
    if (!$logFile) {
        echo "错误: 请指定日志文件路径!\n\n";
        echo "使用 php " . basename($argv[0]) . " -h 查看帮助\n";
        exit(1);
    }
    $config = [
        'output_format' => $options['o'] ?? $options['output'] ?? 'text',
        'top_count' => intval($options['t'] ?? $options['top'] ?? 10)
    ];
    try {
        $analyzer = new AdvancedLogAnalyzer($logFile, $config);
        $report = $analyzer->analyze();
        echo "\n" . $report . "\n";
        // 保存报告
        $save = strtolower(readline("是否保存报告到文件? (y/n): "));
        if ($save === 'y') {
            $analyzer->saveReport($report);
        }
    } catch (Exception $e) {
        echo "错误: " . $e->getMessage() . "\n";
        exit(1);
    }
}

使用说明

基本用法

# 基本分析
php log_analyzer.php /var/log/apache2/access.log
# 使用增强版并输出JSON格式
php log_analyzer_v2.php -f access.log -o json
# 显示Top 20数据
php log_analyzer_v2.php -f access.log -t 20
# 保存到文件
php log_analyzer.php access.log > report.txt

功能特点

  1. 基础统计: 请求数、IP数、流量、状态码分布
  2. 时间分析: 按小时统计请求量
  3. 错误检测: 4xx/5xx错误记录和统计
  4. 热门资源: Top URL和IP排行
  5. 用户代理: 浏览器/爬虫识别
  6. 性能分析: 响应时间和请求速率
  7. 多格式输出: 支持文本、JSON、CSV格式
  8. 过滤功能: 按IP、时间、状态码过滤

注意事项

  1. 支持Apache和Nginx默认日志格式
  2. 处理大文件时注意内存使用
  3. 可自定义过滤条件
  4. 支持实时分析或定时任务集成

这个脚本适合运维人员和开发人员快速分析Web服务器访问日志,发现潜在问题并优化性能。

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