在处理服务器日志文件时,我们经常会遇到格式不统一的问题。比如 IP 地址可能有前导零、时间戳格式可能不一致、请求路径可能带有各种参数。为了统一处理这些字段,preg_replace_callback_array 是一个非常强大的工具,它允许我们对多个正则表达式分别绑定不同的回调函数,在一次遍历中完成复杂的替换逻辑。
本文将介绍如何使用 preg_replace_callback_array 来统一格式化日志中的 IP、时间字段。
假设我们有如下日志内容(简化版):
127.000.000.001 - - [21/Apr/2025:15:32:01 +0000] "GET /index.php?id=123 HTTP/1.1" 200
192.168.1.10 - - [21-Apr-2025 15:32:01] "POST /submit.php HTTP/1.1" 404
我们希望:
将 IP 地址标准化(去除前导零);
将时间统一为 YYYY-MM-DD HH:MM:SS 格式;
可选:屏蔽掉路径中的参数,例如 /index.php?id=123 → /index.php
<?php
$log = <<<LOG
127.000.000.001 - - [21/Apr/2025:15:32:01 +0000] "GET /index.php?id=123 HTTP/1.1" 200
192.168.1.10 - - [21-Apr-2025 15:32:01] "POST /submit.php HTTP/1.1" 404
LOG;
// 定义正则与对应的处理回调
$patterns = [
// IP 地址格式化:去掉前导零
'/\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/' => function ($matches) {
return implode('.', array_map('intval', array_slice($matches, 1, 4)));
},
// Apache 风格时间戳 [21/Apr/2025:15:32:01 +0000]
'/\[(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2}) [+\-]\d{4}\]/' => function ($matches) {
$monthMap = [
'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04',
'May' => '05', 'Jun' => '06', 'Jul' => '07', 'Aug' => '08',
'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12'
];
return sprintf('%s-%s-%s %s:%s:%s',
$matches[3], // 年
$monthMap[$matches[2]] ?? '01', // 月
$matches[1], // 日
$matches[4], $matches[5], $matches[6]
);
},
// 日志格式中另一种时间 [21-Apr-2025 15:32:01]
'/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2}):(\d{2})/' => function ($matches) {
$monthMap = [
'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04',
'May' => '05', 'Jun' => '06', 'Jul' => '07', 'Aug' => '08',
'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12'
];
return sprintf('%s-%s-%s %s:%s:%s',
$matches[3],
$monthMap[$matches[2]] ?? '01',
$matches[1],
$matches[4], $matches[5], $matches[6]
);
},
// 去掉 URL 参数(如 /index.php?id=123 → /index.php)
'#(GET|POST|PUT|DELETE|HEAD) (/[\w\-\/\.]+)(\?[^\s"]*)?#' => function ($matches) {
return $matches[1] . ' ' . $matches[2];
}
];
// 应用替换
$formatted = preg_replace_callback_array($patterns, $log);
// 输出结果
echo nl2br(htmlspecialchars($formatted));
?>
运行上述脚本后,日志内容将被格式化为:
127.0.0.1 - - 2025-04-21 15:32:01 "GET /index.php HTTP/1.1" 200
192.168.1.10 - - 2025-04-21 15:32:01 "POST /submit.php HTTP/1.1" 404
通过 preg_replace_callback_array,我们能够用非常优雅的方式处理日志中多个不同格式的字段。它的优势在于一次性处理多个模式,每个模式独立拥有自己的回调函数,逻辑清晰,维护方便。