印度机房用 jq 清洗日志,字段顺序和编码把我坑了

印度节点上跑的是采集脚本,从一台边缘机拉 Nginx access log,再转成 JSON 投给下游。数据量不大,每天大概 120 万行,机器是 4C8G 的 Ubuntu 20.04,jq 版本 1.6。现象是:同一个脚本在本地跑结果正常,到印度这台机器上,中文 User-Agent 会被截断成乱码,偶尔还出现整条记录丢字段。

我的做法分两步。先确认编码,再看 jq 的构造方式:

locale
# LANG=  LANG 是空的,只有 LC_CTYPE=POSIX

于是把脚本里的环境变量固定下来,在 crontab 和执行入口都加上:

export LANG=C.UTF-8
export LC_ALL=C.UTF-8

第二处是 jq 的字段拼接。原来我图省事用了字符串插值:

jq -R -c 'split(" ") | {ip: .[0], ua: .[11]}'

Nginx 的日志里 User-Agent 带空格,split(" ") 直接把它切碎,后面取到的字段就错位了。改成按固定前缀解析,用 capture 配正则:

jq -R -c 'capture("^(?<ip>\\S+) .* \"(?<ua>[^\"]*)\"$") | {ip, ua}'

跑之前先抽 200 行验证,再全量。

为什么这样做:LC_ALL=POSIX 下 jq 按字节处理输入,正则里的非贪婪匹配和 split 的边界都按单字节算,多字节字符被切开就回不来了。显式设成 C.UTF-8 只是把行为拉回预期,代价是 jq 处理长行时内存涨得明显一些,120 万行我拆成了 20 批跑。用 capture 而不是 split,是因为日志本身字段分隔符不唯一,靠正则锚定行首和行尾更稳,代价是正则写错的时候整条记录会被静默丢掉,所以我加了 select(.ua != null) 做兜底。

哪里会失效:如果日志里 User-Agent 本身带未转义的双引号,这个正则会把行尾锚点吃掉,结果整条记录匹配失败;另外 jq 1.6 的 capture 对超长行(我这边超过 8KB 的请求行)会报错退出,脚本得用 || true 接住,不然一批数据全断。我第一次跑就是没接住,丢了半天的量,后来从源机重新捞了一遍。

你们在印度节点上处理多字节日志时,是统一在入口设 locale,还是干脆绕开 jq 换别的解析器?

0
赞同度 100% · 0 条评论 · 6 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。