拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Miller 日志处理实战:用 DKVP 格式对异构日志做临时分析与聚合

CLI数据分析【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址https://gitcode.com/gh_mirrors/mi/miller点击查看免费下载本文基于 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 的典型使用场景之一而这里的主角是 DKVPKey-Value Pair键值对格式。DKVP 之所以在日志场景中大放异彩官方文档给出的理由非常直白每条记录的行内同时携带字段名和字段值因此每一行都自洽独立可以单独被grep、sed、awk等系统工具处理也可以被 Miller 单独解析因为 schema字段名集合内嵌在数据里不是每一行都必须拥有相同的字段名列表——这正是日志数据的常态不同事件类型携带不同属性。在 Miller 源码中DKVP 读取器位于 pkg/input/record_reader_dkvp_nidx.go它与 NIDX 读取器共用同一套骨架RecordReaderDKVPNIDX唯一差异是行的切分方式DKVP 按keyvalue对切分并解析为MlrmapNIDX 则按位置取序号字段。从源码结构可以推断DKVP 读取器解析一行后直接把keyvalue对还原成记录的字段集合因此字段名天然随行而异。生成与聚合日志输出一个完整的实战流水线官方文档设想了一个非常贴近现实的场景你可以在任何语言的程序里边运行边打印日志行把不同事件的条目混在一起输出。程序跑完后再用 Miller 把混杂的数据分拣出来、过滤、分析、从中学习。假设你的程序打印出了类似 docs/src/log.txt 的内容openter,time1472819681 opcache,typeA9,hit0 opcache,typeA4,hit1 time1472819690,batch_size100,num_filtered237 opcache,typeA1,hit1 opcache,typeA9,hit0 ...注意这里共出现了三类不同形状的记录事件类型携带字段含义openterop,time程序进入记录起始时间戳Unix 秒opcacheop,type,hit缓存访问事件记录缓存类型与是否命中批处理汇总time,batch_size,num_filtered每批数据处理完后的统计信息每条 print 语句只包含局部信息当前时间戳、某个缓存是否命中、本批处理了多少条等等。这正是日志分析的起点——日志行无需统一 schemaMiller 负责在事后把它们整理成可分析的结构。按需挑选记录grep、having-fields 与 is_present 三选一要从混杂的记录中挑出我们关心的部分官方文档给出了三种手段各有适用场景系统grep命令最简单直接适合按文本内容粗筛例如grep opcache log.txtMiller 的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 opcache 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 opcache文本级粗筛只保留缓存事件--idkvp告诉 Miller 输入是 DKVP 格式i input--opprint输出采用漂亮的表格pprint格式stats1 -a mean -f hit -g type按type分组对hit计算均值then sort -f typethen是 Miller 的链式动词分隔符这里按type排序输出。注意此处的关键点混入批处理汇总行batch_size等并不影响分析——grep已把它们过滤掉且 DKVP 读取器对每行独立解析字段不同也无妨。实战二用 is_present 过滤 step 计算时间/数量增量官方文档给出的第二组命令是只保留含batch_size字段的记录即批处理汇总行然后计算time与num_filtered相对上一条记录的增量最后把 Unix 时间戳换算成 GMTmlr --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.txtMiller 的主标志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_filteredstep动词计算依赖前后记录的值。-a delta表示求相邻记录差值-f time,num_filtered指定参与计算的两个数值字段输出自动追加time_delta、num_filtered_delta后缀列。第一行time_delta0是因为首条记录没有前置记录。step的完整选项可从 pkg/transformers/step.go 的stepOptions看到选项取值说明-acounter,delta,ewma,from-first,ratio,rprod,rsum,shift,shift_lag,shift_lead,slwinstepper 名称可逗号分隔或多个-a-f字段名列表参与计算的数值字段-g字段名列表可选分组字段如按主机名分组求增量-d权重列表EWMA 平滑权重1表示不平滑默认0.5-o后缀列表自定义 EWMA 输出字段后缀须与-d数量一致-F布尔兼容 Miller 5 的 no-op 标志Miller 6 起整数计算自动按浮点处理值得注意的是step的 stepper 名单里有shiftshift_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字段换算成 GMTmlr --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作用于三条批openter行的time字段1472819681 → 2016-09-02T12:34:41Z与批处理汇总行的time字段都被正确换算而op,type,hit批没有time字段行原样保留——这再次印证了 Miller 对异构记录各取所需、缺字段不报错的处理哲学。解析日志输出grep/sed 预处理后再交给 Miller生成与聚合一节讨论的是结构化程度较高的keyvalue日志但现实中的日志往往长这样官方文档给出的示例2015-10-08 08:29:09,445 INFO com.company.path.to.ClassName [sometext] various/sorts/of data { punctuation} hits1 status0 time2.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/.*} //贪婪删除到最后一个}为止的文本只保留后面的hits1 status0 time2.378等键值对--fs space将字段分隔符设为空格此时行内是hits1 status0 time2.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结构化、支持正则字段值层用filteris_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.mdDSL 内建函数含is_present的完整说明。参考本文用到的示例数据与源码位置示例日志数据docs/src/log.txtopenter/opcache/ 批处理汇总三类异构记录官方文档原文docs/src/log-processing-examples.mdDKVP 读取器实现pkg/input/record_reader_dkvp_nidx.gogroup-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数据分析【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址https://gitcode.com/gh_mirrors/mi/miller点击查看免费下载相关推荐SigNoz日志管理日志聚合与实时分析技术SigNoz日志管理日志聚合与实时分析技术 概述现代微服务架构下的日志挑战 在当今的微服务架构中应用通常由数十甚至数百个服务组成每个服务都会产生大量的日可观测性指标监控链路追踪日志分析后端告警Flexile日志分析结构化日志与日志聚合Flexile日志分析结构化日志与日志聚合 引言现代SaaS应用的日志挑战 在复杂的SaaS应用如Flexile中日志管理不再是简单的调试工具而是系统可洛雪音乐音源配置终极指南5分钟搞定全网无损音乐免费听洛雪音乐音源配置终极指南5分钟搞定全网无损音乐免费听 洛雪音乐音源lxmusic 是一个专为洛雪音乐播放器设计的开源音源聚合项目为音乐爱好者提供免费获取音视频上一篇终极C排序算法指南20种排序方法深度对比与实战应用下一篇高级问题解决contribute-to-scroll的复杂集成挑战与方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门