本文目录导读:

在PHP项目中实现日志检索,通常有以下几种主流方案,按复杂度从低到高排列:
基础方案:文件搜索
使用PHP原生函数
<?php
class SimpleLogSearcher {
private $logDir;
public function __construct($logDir) {
$this->logDir = rtrim($logDir, '/');
}
/**
* 搜索日志文件
*/
public function search($keyword, $options = []) {
$results = [];
$dateFrom = $options['date_from'] ?? null;
$dateTo = $options['date_to'] ?? null;
$logLevel = $options['level'] ?? null;
$limit = $options['limit'] ?? 100;
// 获取日志文件列表
$files = glob($this->logDir . '/*.log');
foreach ($files as $file) {
// 日期过滤
if ($dateFrom || $dateTo) {
$fileDate = basename($file, '.log');
if ($dateFrom && $fileDate < $dateFrom) continue;
if ($dateTo && $fileDate > $dateTo) continue;
}
$handle = fopen($file, 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
// 关键词匹配
if (strpos($line, $keyword) !== false) {
// 日志级别过滤
if ($logLevel && !preg_match("/\[$logLevel\]/i", $line)) {
continue;
}
$results[] = [
'file' => basename($file),
'line' => $line,
'time' => $this->extractTime($line)
];
if (count($results) >= $limit) {
break 2;
}
}
}
fclose($handle);
}
}
return $results;
}
private function extractTime($line) {
preg_match('/\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/', $line, $matches);
return $matches[1] ?? '';
}
}
// 使用示例
$searcher = new SimpleLogSearcher('/var/log/app');
$results = $searcher->search('ERROR', [
'date_from' => '2024-01-01',
'level' => 'ERROR',
'limit' => 50
]);
使用grep命令(推荐大文件)
<?php
function grepSearch($keyword, $logFile, $options = []) {
$cmd = "grep -i '{$keyword}' {$logFile}";
if (!empty($options['before'])) {
$cmd .= " -B {$options['before']}";
}
if (!empty($options['after'])) {
$cmd .= " -A {$options['after']}";
}
if (!empty($options['max_count'])) {
$cmd .= " -m {$options['max_count']}";
}
$output = [];
exec($cmd, $output);
return $output;
}
结构化日志方案(推荐)
Monolog + Elasticsearch
<?php
// composer require elasticsearch/elasticsearch
// composer require monolog/monolog
use Monolog\Logger;
use Monolog\Handler\ElasticSearchHandler;
// 日志写入
$logger = new Logger('app');
$handler = new ElasticSearchHandler([
'hosts' => ['localhost:9200'],
'index' => 'app_logs_' . date('Y_m_d'),
]);
$logger->pushHandler($handler);
$logger->error('User login failed', [
'user_id' => 123,
'ip' => '192.168.1.1',
'attempt_time' => date('Y-m-d H:i:s')
]);
// 日志检索
class ElasticsearchLogSearcher {
private $client;
public function __construct() {
$this->client = Elasticsearch\ClientBuilder::create()
->setHosts(['localhost:9200'])
->build();
}
public function search($query, $page = 1, $perPage = 20) {
$params = [
'index' => 'app_logs_*',
'body' => [
'query' => [
'bool' => [
'must' => [
['match' => ['message' => $query]],
// 可以添加更多过滤条件
// ['term' => ['level' => 'ERROR']]
]
]
],
'sort' => ['@timestamp' => 'desc'],
'from' => ($page - 1) * $perPage,
'size' => $perPage
]
];
$response = $this->client->search($params);
return [
'total' => $response['hits']['total']['value'],
'hits' => array_map(function($hit) {
return $hit['_source'];
}, $response['hits']['hits'])
];
}
}
使用日志数据库表
<?php
// 数据库表结构
// CREATE TABLE `logs` (
// `id` bigint(20) NOT NULL AUTO_INCREMENT,
// `level` varchar(20) NOT NULL,
// `message` text NOT NULL,
// `context` json DEFAULT NULL,
// `created_at` datetime NOT NULL,
// PRIMARY KEY (`id`),
// KEY `idx_level` (`level`),
// KEY `idx_created_at` (`created_at`),
// FULLTEXT KEY `ft_message` (`message`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
class DatabaseLogSearcher {
private $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function search($keyword, $filters = []) {
$sql = "SELECT * FROM logs WHERE 1=1";
$params = [];
// 关键词搜索(使用LIKE或FULLTEXT)
if (!empty($keyword)) {
$sql .= " AND message LIKE :keyword";
$params[':keyword'] = "%{$keyword}%";
}
// 级别过滤
if (!empty($filters['level'])) {
$sql .= " AND level = :level";
$params[':level'] = $filters['level'];
}
// 时间范围
if (!empty($filters['date_from'])) {
$sql .= " AND created_at >= :date_from";
$params[':date_from'] = $filters['date_from'];
}
if (!empty($filters['date_to'])) {
$sql .= " AND created_at <= :date_to";
$params[':date_to'] = $filters['date_to'];
}
// 分页
$page = $filters['page'] ?? 1;
$perPage = $filters['per_page'] ?? 20;
$offset = ($page - 1) * $perPage;
$sql .= " ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
$params[':limit'] = $perPage;
$params[':offset'] = $offset;
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
高级方案:日志管理系统
使用成熟工具
<?php
// 集成ELK Stack
class ElkLogViewer {
private $elasticsearch;
private $kibanaUrl;
public function __construct($elasticsearchHost, $kibanaUrl) {
$this->elasticsearch = Elasticsearch\ClientBuilder::create()
->setHosts([$elasticsearchHost])
->build();
$this->kibanaUrl = $kibanaUrl;
}
// 构建Kibana查询URL
public function getKibanaQueryUrl($query) {
$baseUrl = $this->kibanaUrl . '/app/discover#/?_a=(query:(language:kuery,query:\'' . urlencode($query) . '\'))';
return $baseUrl;
}
// 通过API查询日志
public function searchLogs($query, $from = 'now-24h', $to = 'now') {
$params = [
'index' => 'app-logs-*',
'body' => [
'size' => 100,
'query' => [
'bool' => [
'must' => [
['query_string' => ['query' => $query]],
['range' => ['@timestamp' => ['gte' => $from, 'lte' => $to]]]
]
]
]
]
];
return $this->elasticsearch->search($params);
}
}
自定义日志管理系统
<?php
class LogManagementSystem {
private $config;
public function __construct($config) {
$this->config = $config;
}
/**
* 多维度日志检索
*/
public function advancedSearch($criteria) {
$logs = [];
// 1. 解析查询语法
$parsedQuery = $this->parseQuery($criteria['query']);
// 2. 选择数据源
$dataSource = $this->selectDataSource($criteria);
// 3. 执行搜索
switch ($dataSource) {
case 'elasticsearch':
$logs = $this->searchElasticsearch($parsedQuery);
break;
case 'database':
$logs = $this->searchDatabase($parsedQuery);
break;
case 'file':
$logs = $this->searchFiles($parsedQuery);
break;
}
// 4. 后处理
$logs = $this->postProcess($logs, $criteria);
return $logs;
}
private function parseQuery($query) {
// 支持语法: ERROR, level:ERROR, user.id:123, time>2024-01-01
$parsed = [
'keywords' => [],
'filters' => []
];
$parts = preg_split('/\s+/', $query);
foreach ($parts as $part) {
if (strpos($part, ':') !== false) {
list($field, $value) = explode(':', $part, 2);
$parsed['filters'][$field] = $value;
} else {
$parsed['keywords'][] = $part;
}
}
return $parsed;
}
}
性能优化建议
索引优化
<?php
// 为日志文件创建索引
class LogIndexer {
public function createIndex($logDir) {
$indexDir = $logDir . '/index';
if (!is_dir($indexDir)) {
mkdir($indexDir, 0755, true);
}
// 使用SplFileObject逐行读取
$files = new DirectoryIterator($logDir);
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'log') {
$this->indexFile($file->getPathname(), $indexDir);
}
}
}
private function indexFile($filePath, $indexDir) {
// 创建倒排索引
$index = [];
$file = new SplFileObject($filePath);
foreach ($file as $lineNumber => $line) {
$words = str_word_count($line, 1);
foreach ($words as $word) {
$word = strtolower($word);
if (!isset($index[$word])) {
$index[$word] = [];
}
$index[$word][] = [
'file' => basename($filePath),
'line' => $lineNumber + 1
];
}
}
// 保存索引
file_put_contents(
$indexDir . '/' . basename($filePath) . '.idx',
serialize($index)
);
}
}
缓存策略
<?php
class CachedLogSearcher {
private $cache;
private $searcher;
public function __construct($cache, $searcher) {
$this->cache = $cache;
$this->searcher = $searcher;
}
public function search($query, $filters = []) {
$cacheKey = $this->buildCacheKey($query, $filters);
// 尝试从缓存获取
if ($cached = $this->cache->get($cacheKey)) {
return $cached;
}
// 执行搜索
$results = $this->searcher->search($query, $filters);
// 缓存结果(1分钟)
$this->cache->set($cacheKey, $results, 60);
return $results;
}
private function buildCacheKey($query, $filters) {
return 'log_search_' . md5($query . serialize($filters));
}
}
最佳实践建议
- 日志格式标准化:使用JSON格式,便于解析和搜索
- 日志轮转:按日期/大小自动轮转,避免单个文件过大
- 异步写入:使用消息队列(如RabbitMQ)异步处理日志
- 分级存储:热日志(最近7天)存SSD,冷日志存廉价存储
- 监控告警:设置搜索规则,异常时自动告警
根据项目规模和需求,选择适合的方案,小项目可以使用简单的文件搜索,中大型项目推荐使用ELK或自定义数据库方案。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播