- CLI
- 数据分析
【免费下载链接】miller
Miller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON
本文基于 Miller 官方文档《Log-processing examples》(对应仓库文档 docs/src/log-processing-examples.md),围绕"临时性(ad-hoc)处理日志文件数据"这一场景,系统讲解如何利用 Miller 的 DKVP 输入格式、filter/step/sec2gmt/group-like/stats1等动词(verb)与is_present等 DSL 函数,对字段结构不一致的异构日志进行提取、过滤、聚合、时间换算与分组重排。读完本文,你将掌握一条完整的"程序打日志 → grep 预处理 → Miller 分析"流水线,并能直接套用到自己的日志分析任务中。
DKVP:让每条日志行独立自洽的数据格式
日志处理是 Miller 的典型使用场景之一,而这里的主角是 DKVP(Key-Value Pair,键值对)格式。DKVP 之所以在日志场景中"大放异彩",官方文档给出的理由非常直白:
- 每条记录的行内同时携带字段名和字段值,因此每一行都自洽独立,可以单独被
grep、sed、awk等系统工具处理,也可以被 Miller 单独解析; - 因为 schema(字段名集合)内嵌在数据里,不是每一行都必须拥有相同的字段名列表——这正是日志数据的常态:不同事件类型携带不同属性。
在 Miller 源码中,DKVP 读取器位于 pkg/input/record_reader_dkvp_nidx.go,它与 NIDX 读取器共用同一套骨架(RecordReaderDKVPNIDX),唯一差异是行的切分方式:DKVP 按key=value对切分并解析为Mlrmap,NIDX 则按位置取序号字段。从源码结构可以推断,DKVP 读取器解析一行后,直接把key=value对还原成记录的字段集合,因此字段名天然随行而异。
生成与聚合日志输出:一个完整的实战流水线
官方文档设想了一个非常贴近现实的场景:你可以在任何语言的程序里边运行边打印日志行,把不同事件的条目混在一起输出。程序跑完后,再用 Miller 把混杂的数据"分拣"出来、过滤、分析、从中学习。
假设你的程序打印出了类似 docs/src/log.txt 的内容:
op=enter,time=1472819681 op=cache,type=A9,hit=0 op=cache,type=A4,hit=1 time=1472819690,batch_size=100,num_filtered=237 op=cache,type=A1,hit=1 op=cache,type=A9,hit=0 ...注意这里共出现了三类不同"形状"的记录:
| 事件类型 | 携带字段 | 含义 |
|---|---|---|
op=enter | op,time | 程序进入,记录起始时间戳(Unix 秒) |
op=cache | op,type,hit | 缓存访问事件,记录缓存类型与是否命中 |
| 批处理汇总 | time,batch_size,num_filtered | 每批数据处理完后的统计信息 |
每条 print 语句只包含局部信息:当前时间戳、某个缓存是否命中、本批处理了多少条等等。这正是日志分析的起点——日志行无需统一 schema,Miller 负责在事后把它们整理成可分析的结构。
按需挑选记录:grep、having-fields 与 is_present 三选一
要从混杂的记录中挑出我们关心的部分,官方文档给出了三种手段,各有适用场景:
- 系统
grep命令:最简单直接,适合按文本内容粗筛,例如grep op=cache log.txt; - Miller 的
having-fields动词:按记录的字段名做结构化筛选; - DSL 的
is_present函数:在put/filter表达式中判断某字段是否存在。
having-fields的实现位于 pkg/transformers/having_fields.go,它支持六种判定模式:--at-least(至少包含这些字段)、--which-are(字段名恰好是这些)、--at-most(至多包含这些字段)、--all-matching(全部字段名匹配正则)、--any-matching(任一字段名匹配正则)、--none-matching(没有字段名匹配正则)。源码中每种模式对应一个独立的transformHavingFieldsXxx方法,例如--any-matching通过遍历记录头指针逐一MatchString判定,命中即放行。
is_present定义于 pkg/bifs/types.go,实现为FromBool(!input1.IsAbsent())——即只要字段值不是"缺席(absent)"状态即为真。它与is_null的区别在于:is_null在字段缺席、空(void)、null 三种情况下都返回真,而is_present只关心字段是否存在。
实战一:按缓存类型统计命中率
下面的命令先用系统grep筛出所有缓存访问行,再用 Miller 以 DKVP 格式读入、按type分组统计hit字段的均值:
grep op=cache log.txt \ | mlr --idkvp --opprint stats1 -a mean -f hit -g type then sort -f type输出:
type hit_mean A1 0.8571428571428571 A4 0.7142857142857143 A9 0.09090909090909091这条命令把三种能力串成了一条链:
grep op=cache:文本级粗筛,只保留缓存事件;--idkvp:告诉 Miller 输入是 DKVP 格式("i" = input);--opprint:输出采用漂亮的表格(pprint)格式;stats1 -a mean -f hit -g type:按type分组,对hit计算均值;then sort -f type:then是 Miller 的链式动词分隔符,这里按type排序输出。
注意此处的关键点:混入批处理汇总行(batch_size等)并不影响分析——grep已把它们过滤掉,且 DKVP 读取器对每行独立解析,字段不同也无妨。
实战二:用 is_present 过滤 + step 计算时间/数量增量
官方文档给出的第二组命令是:只保留含batch_size字段的记录(即批处理汇总行),然后计算time与num_filtered相对上一条记录的增量,最后把 Unix 时间戳换算成 GMT:
mlr --from log.txt --opprint \ filter 'is_present($batch_size)' \ then step -a delta -f time,num_filtered \ then sec2gmt time输出:
time batch_size num_filtered time_delta num_filtered_delta 2016-09-02T12:34:50Z 100 237 0 0 2016-09-02T12:35:05Z 100 348 15 111 2016-09-02T12:35:13Z 100 493 8 145 2016-09-02T12:35:20Z 100 554 7 61 2016-09-02T12:35:36Z 100 612 16 58 2016-09-02T12:35:42Z 100 728 6 116这里出现了三个重要语法与动词,逐一拆解:
--from log.txt:Miller 的主标志(main flag),直接指定输入文件,等价于把文件重定向到 stdin。
filter 'is_present($batch_size)':filter动词按 DSL 表达式逐记录判定。由于异构日志中大量行没有batch_size字段,直接引用$batch_size会得到 absent 值,因此必须用is_present先做存在性检查。从 pkg/bifs/types.go 的实现看,它返回"字段非 absent"的布尔值,正好充当过滤条件。
step -a delta -f time,num_filtered:step动词计算"依赖前后记录的值"。-a delta表示求相邻记录差值,-f time,num_filtered指定参与计算的两个数值字段,输出自动追加time_delta、num_filtered_delta后缀列。第一行time_delta=0是因为首条记录没有前置记录。
step的完整选项可从 pkg/transformers/step.go 的stepOptions看到:
| 选项 | 取值 | 说明 |
|---|---|---|
-a | counter,delta,ewma,from-first,ratio,rprod,rsum,shift,shift_lag,shift_lead,slwin | stepper 名称,可逗号分隔或多个-a |
-f | 字段名列表 | 参与计算的数值字段 |
-g | 字段名列表 | 可选分组字段(如按主机名分组求增量) |
-d | 权重列表 | EWMA 平滑权重,1表示不平滑,默认0.5 |
-o | 后缀列表 | 自定义 EWMA 输出字段后缀,须与-d数量一致 |
-F | 布尔 | 兼容 Miller 5 的 no-op 标志(Miller 6 起整数计算自动按浮点处理) |
值得注意的是,step的 stepper 名单里有shift(shift_lag的别名)、shift_lag、shift_lead、slwin(滑动窗口均值)等,并支持带计数后缀的形式如shift_lag_12(回溯 12 条记录)、slwin_7_2(回溯 7 条、前瞻 2 条)。从 pkg/transformers/step.go 的STEPPER_LOOKUP_TABLE可以看到全部内置 stepper 及其语义。
sec2gmt time:把time字段从 Unix 秒换算为 GMT 时间戳(ISO 8601 格式)。这本质上是 pkg/transformers/sec2gmt.go 中sec2gmt动词对 DSLsec2gmt函数的"按键少字"封装——源码注释明确说明mlr sec2gmt time1,time2等价于mlr put '$time1 = sec2gmt($time1); $time2 = sec2gmt($time2)'。该动词还支持-1到-9(保留 1~9 位小数)以及--millis/--micros/--nanos(输入为毫秒/微秒/纳秒时间戳)等选项。
异构数据分组重排:group-like 动词
前面的例子靠"先过滤再分析"回避了 schema 不一致问题。但官方文档还展示了一个更"偷懒"的思路:把相似形状的记录聚拢到一起,直接观察——这正是group-like动词的用途。
mlr --opprint group-like log.txt输出(节选):
op time enter 1472819681 op type hit cache A9 0 cache A4 1 cache A1 1 ... time batch_size num_filtered 1472819690 100 237 1472819705 100 348 ...可以看到,group-like把异构记录按字段名集合是否相同分成三批:op,time批、op,type,hit批、time,batch_size,num_filtered批,批间用空行分隔。
从源码 pkg/transformers/group_like.go 看,group-like的实现思路是:用inrec.GetKeysJoined()把每条记录的字段名拼成分组键,存入一个lib.OrderedMap(键为字段名串,值为记录列表);在流结束时(EndOfStream)按插入顺序把所有分组依次输出。使用有序映射而非普通哈希表,保证了输出分组的顺序确定性。
group-like动词没有任何选项参数,但它同样可以与then链式组合。官方文档紧接着给出了一个组合示例:先分组、再顺便把time字段换算成 GMT:
mlr --opprint group-like then sec2gmt time log.txt输出(节选):
op time enter 2016-09-02T12:34:41Z op type hit cache A9 0 ... time batch_size num_filtered 2016-09-02T12:34:50Z 100 237 2016-09-02T12:35:05Z 100 348 ...注意sec2gmt time作用于三条批:op=enter行的time字段(1472819681 → 2016-09-02T12:34:41Z)与批处理汇总行的time字段都被正确换算,而op,type,hit批没有time字段,行原样保留——这再次印证了 Miller 对异构记录"各取所需、缺字段不报错"的处理哲学。
解析日志输出:grep/sed 预处理后再交给 Miller
"生成与聚合"一节讨论的是结构化程度较高的key=value日志;但现实中的日志往往长这样(官方文档给出的示例):
2015-10-08 08:29:09,445 INFO com.company.path.to.ClassName @ [sometext] various/sorts/of data {& punctuation} hits=1 status=0 time=2.378这是一行典型的 Java/Log4j 风格日志:时间戳、级别、类名、自由文本和结构化键值对混在一起。Miller 官方文档给出的处理策略是:先用grep和/或sed做文本级预处理,把结构化部分提取出来,再交给 Miller。示例命令:
grep 'various sorts' *.log \ | sed 's/.*} //' \ | mlr --fs space --repifs --oxtab stats1 -a min,p10,p50,p90,max -f time -g status这条流水线包含几个值得拆解的要点:
grep 'various sorts' *.log:按关键字粗筛日志文件;sed 's/.*} //':贪婪删除到最后一个}为止的文本,只保留后面的hits=1 status=0 time=2.378等键值对;--fs space:将字段分隔符设为空格(此时行内是hits=1 status=0 time=2.378这类空格分隔的键值对);--repifs:允许"重复的字段分隔符"——多个连续空格被视为一个分隔符,这对日志文本中不规则的空白至关重要;--oxtab:以 XTAB(交叉表)格式输出,每条记录纵向展开为key: value多行,便于阅读;stats1 -a min,p10,p50,p90,max -f time -g status:按status分组,对time字段求最小值、10/50/90 百分位和最大值——典型的延迟分析。
文档在此处给出的输出为... output here ...,即结果取决于读者日志的实际内容;但命令结构本身即是可复用的模板:任何"一行里既有自由文本又有键值对"的日志,都可以用这条"grep → sed → Miller"三段式流水线转成结构化数据做统计。
方法论小结:异构日志分析的三板斧
通读官方文档,可以提炼出 Miller 日志分析的三条核心方法论:
- 日志行自带 schema,天然适合 DKVP:写日志时无需约束所有事件使用同一组字段,每条日志"各说各话",事后统一交给 Miller 解析即可。这消除了传统 CSV 日志对字段顺序和表头的强约束。
- 三层筛选手段按需选用:文本层用系统
grep/sed(简单、快、可处理自由文本);字段名层用having-fields(结构化、支持正则);字段值层用filter+is_present等 DSL 表达式(最灵活,可叠加任意逻辑)。 - 用 then 链把动词组装成流水线:
stats1聚合、step增量、sec2gmt时间换算、group-like分组重排,都可以用then自由组合;输出格式通过--o*系列标志(--opprint、--oxtab等)切换,同一份数据可以"看"出多种形态。
更进一步,上述命令均可用 Miller 的回归测试体系复现与验证:例如group-like的最小复现命令记录在 test/cases/verb-group-like/0001/cmd,内容是mlr group-like test/input/het.dkvp——test/input/het.dkvp正是一个字段形状混杂的 DKVP 样本,其预期输出与该动词"按相同字段名集合分批输出"的语义完全一致。相关动词的更多使用细节可参考仓库文档 docs/src/reference-verbs.md(动词总览)与 docs/src/reference-dsl-builtin-functions.md(DSL 内建函数,含is_present的完整说明)。
参考:本文用到的示例数据与源码位置
- 示例日志数据:docs/src/log.txt(
op=enter/op=cache/ 批处理汇总三类异构记录) - 官方文档原文:docs/src/log-processing-examples.md
- DKVP 读取器实现:pkg/input/record_reader_dkvp_nidx.go
group-like动词实现:pkg/transformers/group_like.gostep动词实现与 stepper 全表:pkg/transformers/step.gosec2gmt动词实现:pkg/transformers/sec2gmt.gohaving-fields动词实现:pkg/transformers/having_fields.gois_present函数实现:pkg/bifs/types.go- 回归测试样例:test/cases/verb-group-like/0001/cmd
- CLI
- 数据分析
【免费下载链接】miller
Miller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON
相关推荐
SigNoz日志管理:日志聚合与实时分析技术
SigNoz日志管理:日志聚合与实时分析技术 概述:现代微服务架构下的日志挑战 在当今的微服务架构中,应用通常由数十甚至数百个服务组成,每个服务都会产生大量的日
可观测性指标监控链路追踪日志分析后端告警Flexile日志分析:结构化日志与日志聚合
Flexile日志分析:结构化日志与日志聚合 引言:现代SaaS应用的日志挑战 在复杂的SaaS应用如Flexile中,日志管理不再是简单的调试工具,而是系统可
洛雪音乐音源配置终极指南:5分钟搞定全网无损音乐免费听
洛雪音乐音源配置终极指南:5分钟搞定全网无损音乐免费听 洛雪音乐音源(lxmusic )是一个专为洛雪音乐播放器设计的开源音源聚合项目,为音乐爱好者提供免费获取
音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考