Java中从日志字符串提取KEY=VALUE键值对的正则方案求助
从带转义引号的日志字符串中提取KEY=VALUE键值对到Map
问题背景
有如下格式的日志字符串(注意其中的双引号是转义的""形式):
String s0 = "DC696,\"/xi/ajax/remoting/call/plaincall/adhocReportBuilderControllerProxy.getRortList.dwr\",\"2222-11-10 08:32:22,351 PLV=REQ CIP=9.9.9.7 CMID=syairp CMN=\"\"Dub Airport Corporation Limited\"\" SN=sfv4_APM180885. DPN=dbPool66HFT01 UID=3862D04108 UN=91F6025D47F01D IUID=1931 LOC=en_GB EID=\"\"EVENT-UNKNOWN-UNKNOWN-ob55abe0118-201110083217-396080\"\" AGN=\"\"[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35]\"\" RID=REQ-[7274545] MTD=POST URL=\"\"/xi/ajax/remoting/call/plaincall/adhocRrtBuilderCoollerProxy.getRtList.dwr\"\" RQT=2835 MID=ADIN PID=ADMIN PQ=ADIN_PAGE SUB=0 MEM=2331036 CPU=2410 UCPU=2300 SCPU=110 FRE=10 FWR=0 NRE=2281 NWR=218 SQLC=43 SQLT=142 RPS=200 SID=60826A3FAB005A8A9B930177C5******.pc6bc1029 GID=e262dde6d0e040070b58afd4c8 HSID=ddc665538db779508d3213c0bb63bcb1c49fe8236d5f0884ae975915728e61 CSL=CRITICAL CCON=0 CSUP=0 CLOC=0 CEXT=0 CREM=0 STK={\"\"n\"\":\"\"/xi/ajax/remoting/call/plaincall/adhocReportBuilderControllerProxy.getrtList.dwr\"\",\"\"i\"\":1,\"\"t\"\":2835,\"\"slft\"\":2679,\"\"sub\"\":[{\"\"n\"\":\"\"SQL:select * from sfv4_HOUA180885.REPORT_DEF WHERE REPORT_DEF_ID IN (SELECT REPORT_DEF_ID FROM sfv4_HA80885.REPORT_DTASET WHERE REPORT_ID=?) AND DELETED=? ORDER BY REPORT_DEF_ID asc NULLS LAST\"\",\"\"i\"\":17,\"\"t\"\":40,\"\"slft\"\":40,\"\"st\"\":337,\"\"m\"\":220958,\"\"nr\"\":154,\"\"rt\"\":0,\"\"rn\"\":22,\"\"fs\"\":0}]} \"},\"2022-11-09T21:32:22.351+0000\",p66cf1029,\"dc606_ss_application\",1,\"/app/tomcat/logs/pef.log\",\"perf_log_yxx\",swsskix13";
需要从中提取PLV=REQ、CIP=9.9.9.7、CMN="Dub Airport Corporation Limited"、STK={...}这类KEY=VALUE键值对,转换为Map<String,String>对象。之前尝试用split方法无法正常工作:
String[] str1= str.split("\\s(?=(([^\\"]*\\"))*[^\\"]*$)\\s*"); System.out.println("Value of split string is "+ Arrays.toString(str1));
问题分析
原方案用split的问题在于:
- 日志中的值可能包含空格(比如
AGN对应的浏览器UA字符串),且存在转义双引号""和嵌套大括号(比如STK的JSON结构),单纯按空格分割会破坏完整的键值对。 - 正则表达式的逻辑没有覆盖大括号嵌套的场景,无法正确识别
STK这类包含复杂结构的值边界。
解决方案
推荐使用正则匹配的方式,直接定位所有符合KEY=VALUE格式的片段,其中VALUE可能是:
- 普通无空格字符串
- 被转义双引号
""包裹的内容 - 被大括号
{}包裹的JSON结构(支持嵌套)
实现代码
import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; public class LogParser { public static void main(String[] args) { String s0 = "DC696,\"/xi/ajax/remoting/call/plaincall/adhocReportBuilderControllerProxy.getRortList.dwr\",\"2222-11-10 08:32:22,351 PLV=REQ CIP=9.9.9.7 CMID=syairp CMN=\"\"Dub Airport Corporation Limited\"\" SN=sfv4_APM180885. DPN=dbPool66HFT01 UID=3862D04108 UN=91F6025D47F01D IUID=1931 LOC=en_GB EID=\"\"EVENT-UNKNOWN-UNKNOWN-ob55abe0118-201110083217-396080\"\" AGN=\"\"[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35]\"\" RID=REQ-[7274545] MTD=POST URL=\"\"/xi/ajax/remoting/call/plaincall/adhocRrtBuilderCoollerProxy.getRtList.dwr\"\" RQT=2835 MID=ADIN PID=ADMIN PQ=ADIN_PAGE SUB=0 MEM=2331036 CPU=2410 UCPU=2300 SCPU=110 FRE=10 FWR=0 NRE=2281 NWR=218 SQLC=43 SQLT=142 RPS=200 SID=60826A3FAB005A8A9B930177C5******.pc6bc1029 GID=e262dde6d0e040070b58afd4c8 HSID=ddc665538db779508d3213c0bb63bcb1c49fe8236d5f0884ae975915728e61 CSL=CRITICAL CCON=0 CSUP=0 CLOC=0 CEXT=0 CREM=0 STK={\"\"n\"\":\"\"/xi/ajax/remoting/call/plaincall/adhocReportBuilderControllerProxy.getrtList.dwr\"\",\"\"i\"\":1,\"\"t\"\":2835,\"\"slft\"\":2679,\"\"sub\"\":[{\"\"n\"\":\"\"SQL:select * from sfv4_HOUA180885.REPORT_DEF WHERE REPORT_DEF_ID IN (SELECT REPORT_DEF_ID FROM sfv4_HA80885.REPORT_DTASET WHERE REPORT_ID=?) AND DELETED=? ORDER BY REPORT_DEF_ID asc NULLS LAST\"\",\"\"i\"\":17,\"\"t\"\":40,\"\"slft\"\":40,\"\"st\"\":337,\"\"m\"\":220958,\"\"nr\"\":154,\"\"rt\"\":0,\"\"rn\"\":22,\"\"fs\"\":0}]} \"},\"2022-11-09T21:32:22.351+0000\",p66cf1029,\"dc606_ss_application\",1,\"/app/tomcat/logs/pef.log\",\"perf_log_yxx\",swsskix13"; // 提取日志中包含键值对的核心部分(第三个引号内的内容) String coreLog = s0.split("\",\"")[2]; Map<String, String> keyValueMap = new HashMap<>(); // 正则表达式:匹配KEY=VALUE,覆盖三种VALUE类型 String regex = "(\\w+)=((?:""[^""]*"")|(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})|(?:[^\\s]+))"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(coreLog); while (matcher.find()) { String key = matcher.group(1); String value = matcher.group(2); // 处理转义双引号:把""替换为" value = value.replace("\"\"", "\""); // 去掉包裹内容的首尾引号(如果存在) if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length() - 1); } keyValueMap.put(key, value); } // 输出测试 keyValueMap.forEach((k, v) -> System.out.println(k + " = " + v)); } }
正则表达式说明
(\\w+)=((?:""[^""]*"")|(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\})|(?:[^\\s]+))
(\\w+):匹配键名,由字母数字下划线组成=:匹配等号分隔符- 括号内的三个分支分别匹配三种值类型:
(?:""[^""]*""):匹配被转义双引号""包裹的内容(?:\\{[^{}]*(?:\\{[^{}]*\\}[^{}]*)*\\}):匹配被大括号包裹的嵌套结构(支持一层嵌套)(?:[^\\s]+):匹配无空格的普通字符串
额外处理说明
- 首先通过
split("\",\"")提取日志中包含键值对的核心片段,避免处理前后无关的字段。 - 把转义的
""替换为普通双引号",并去掉包裹内容的首尾引号,让值更符合正常格式。 - 对于
STK这类JSON结构,保留原始大括号格式,后续可以根据需要进一步解析为JSON对象。
内容的提问来源于stack exchange,提问作者Alferd Nobel
相关产品推荐
相关产品推荐

