PHP通过正则表达式解析Fortinet日志生成键值对数组问题咨询
PHP解析Fortinet日志正则匹配问题修复
问题背景
我正在尝试使用PHP解析Fortinet日志,使用的日志示例取自Fortinet官方cookbook手册。
目标是解析日志后生成以日志字段名为索引、对应字段值为元素值的数组,预期格式例如[date]=>2019-05-10 [time]=>11:50:48 ... [srcip]=>172.16.200.254。当前编写的正则代码运行后无法得到预期匹配结果,需要排查正则写法问题,实现正确的字段提取。
原有问题代码
$regex = '/[a-zA-Z]+=[0-9]{4}-[0-9]{2}-[0-9]{2} [a-zA-Z]+=[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,3})? [a-zA-Z]+="[^"]*" [a-zA-Z]+="[a-zA-Z]+" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+=[0-9]+ [a-zA-Z]+=\\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\\b [a-zA-Z]+=[0-9]+ [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+=\\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\\b [a-zA-Z]+=[0-9]+ [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+=[0-9]+ [a-zA-Z]+=[0-9]+ [a-zA-Z]+="[^"]*" [a-zA-Z]+=[0-9]+ [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+="[^"]*" [a-zA-Z]+=[0-9]+ [a-zA-Z]+=[0-9]+ [a-zA-Z]+=[0-9]+ [a-zA-Z]+=[0-9]+ [a-zA-Z]+=[0-9]+ [a-zA-Z]+="[^"]*"/i'; $str = 'date=2019-05-10 time=11:50:48 logid="0001000014" type="traffic" subtype="local" level="notice" vd="vdom1" eventtime=1557514248379911176 srcip=172.16.200.254 srcport=62024 srcintf="port11" srcintfrole="undefined" dstip=172.16.200.2 dstport=443 dstintf="vdom1" dstintfrole="undefined" sessionid=107478 proto=6 action="server-rst" policyid=0 policytype="local-in-policy" service="HTTPS" dstcountry="Reserved" srccountry="Reserved" trandisp="noop" app="Web Management(HTTPS)" duration=5 sentbyte=1247 rcvdbyte=1719 sentpkt=5 rcvdpkt=6 appcat="unscanned"'; preg_match_all($regex, $str, $matches, PREG_SET_ORDER, 0); var_dump($matches);
问题原因
原有正则存在两个核心问题:
- 硬编码了所有字段的出现顺序、值匹配规则,只要日志字段顺序调整、值包含特殊字符(比如
server-rst中的横杠、Web Management(HTTPS)中的空格和括号)就会整体匹配失败,完全不具备通用性 - 没有设置捕获分组,即使匹配成功也无法单独提取字段名和字段值,无法直接组装成目标关联数组
修复方案
Fortinet日志的字段统一遵循字段名=值的格式,值仅分两类:带双引号的字符串、不带引号的数字/IP,不需要针对单个字段写匹配规则,用通用键值对正则即可完成提取。
修复后的可运行代码:
<?php // 通用键值对匹配正则,自动兼容带引号/不带引号的字段值 $regex = '/(\w+)=(".*?"|\S+)/i'; $str = 'date=2019-05-10 time=11:50:48 logid="0001000014" type="traffic" subtype="local" level="notice" vd="vdom1" eventtime=1557514248379911176 srcip=172.16.200.254 srcport=62024 srcintf="port11" srcintfrole="undefined" dstip=172.16.200.2 dstport=443 dstintf="vdom1" dstintfrole="undefined" sessionid=107478 proto=6 action="server-rst" policyid=0 policytype="local-in-policy" service="HTTPS" dstcountry="Reserved" srccountry="Reserved" trandisp="noop" app="Web Management(HTTPS)" duration=5 sentbyte=1247 rcvdbyte=1719 sentpkt=5 rcvdpkt=6 appcat="unscanned"'; preg_match_all($regex, $str, $matches); // 直接将捕获到的键名、值数组组合为关联数组 $log = array_combine($matches[1], $matches[2]); // 去除字符串值首尾的双引号,得到干净的字段值 foreach ($log as $key => $val) { $log[$key] = trim($val, '"'); } var_dump($log);
正则逻辑说明
(\w+):第一捕获组,匹配由字母、数字、下划线组成的字段名(".*?"|\S+):第二捕获组,优先匹配双引号包裹的字符串内容(支持含空格、特殊字符的值),匹配不到时匹配连续非空白字符,适配数字、IP这类不带引号的值- 该正则不依赖字段顺序、不限制字段数量,兼容不同型号、不同版本Fortinet设备输出的日志格式
内容的提问来源于stack exchange,提问作者toolost
相关产品推荐
相关产品推荐

