Grafana Loki LogCLI 实战教程:用命令行查询日志、执行元查询与分析静态日志文件
2026/9/12 14:18:26 网站建设 项目流程

Grafana Loki LogCLI 实战教程:用命令行查询日志、执行元查询与分析静态日志文件

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

本篇教程以 Grafana Loki 的官方 LogCLI 教程(docs/sources/query/logcli/logcli-tutorial.md)为骨架,结合 LogCLI 源码与入门参考文档进行深度扩充。LogCLI 是 Loki 的命令行客户端,可以运行 LogQL 查询、对 Loki 实例执行"元查询"(series、stats、volume、detected-fields 等),甚至可以直接查询静态日志文件——非常适合在只有控制台、没有 Grafana 可视化面板的环境中完成日志检索、基线与容量评估、数据卫生检查等管理任务。读完本文,你将掌握 LogCLI 的安装与连接配置、日志查询与指标查询、基线与性能分析,以及离线日志文件查询等完整实战技能。

场景设定:一家物流公司的包裹日志

假设你是一家新成立的物流公司的站点管理员。公司使用结构化日志记录每一件包裹的发出与接收情况,日志负载格式如下:

{"timestamp": "2024-11-22T13:22:56.377884", "state": "New York", "city": "Buffalo", "package_id": "PKG34245", "package_type": "Documents", "package_size": "Medium", "package_status": "error", "note": "Out for delivery", "sender": {"name": "Sender27", "address": "144 Elm St, Buffalo, New York"}, "receiver": {"name": "Receiver4", "address": "260 Cedar Blvd, New York City, New York"}}

这些日志由 Grafana Alloy 处理:在写入 Loki 之前会先抽取标签(labels)和结构化元数据(structured metadata)。你的任务是用 LogCLI 监控这些日志,并产出一份关于包裹整体健康状况的报告——全程只有一台控制台,无法使用 Grafana 可视化。

前置条件与环境搭建

开始之前,你需要准备:

  • Docker
  • Docker-compose
  • 本机已安装 LogCLI(安装方式见下文)

安装 LogCLI

从 Loki releases 页面 中定义了logcli构建目标:

git clone https://github.com/grafana/loki.git cd loki make logcli

可选地把二进制放入$PATH

cp cmd/logcli/logcli /usr/local/bin/logcli

从源码结构看,LogCLI 的入口是 cmd/logcli/main.go,它基于 kingpin 框架注册了queryinstant-querylabelsseriesfmtstatsvolumevolume_rangedetected-fieldsdelete等子命令;核心查询逻辑分布在 pkg/logcli 下的queryclientoutputprint等子包中。

启动演示环境

克隆 Alloy 场景仓库并启动 mail-house 示例:

git clone https://github.com/grafana/alloy-scenarios.git docker compose -f alloy-scenarios/mail-house/docker-compose.yml up -d

启动后:

  • Loki 实例暴露在http://localhost:3100
  • 附带一个 Grafana 实例(http://localhost:3000)用于交叉验证 LogCLI 的结果

该示例的 Loki 配置刻意让 ingester 每 5 分钟 flush 一次 chunk(生产环境不推荐这样做),目的是让stats等统计命令能够命中对象存储,稍后你会看到它的影响。

连接 LogCLI 与 Loki

设置LOKI_ADDR环境变量指向 Loki 实例:

export LOKI_ADDR=http://localhost:3100

如果连接的是你自己的、配置了认证的 Loki 实例,还需要设置LOKI_USERNAMELOKI_PASSWORD(Grafana Cloud 用户则设置为对应的云实例地址与凭据)。

验证连接:

logcli labels

预期输出类似:

http://localhost:3100/loki/api/v1/labels?end=1732282703894072000&start=1732279103894072000 package_size service_name state

从源码看,这些连接参数在 cmd/logcli/main.go#L574-L619 中注册:--addr(默认http://localhost:3100)、--username--password--org-id(对应X-Scope-OrgID请求头,用于绕过认证网关直接请求指定租户数据)、--bearer-token--ca-cert--tls-skip-verify--proxy-url--retries/--min-backoff/--max-backoff等,每一项都有对应的LOKI_*环境变量,且环境变量优先于命令行参数。labels命令的输出第一行是实际请求的 API URL,其余行是该时间窗口内的标签名列表。

日志中目前有 3 个标签:package_sizeservice_namestate。下面开始真正的查询。

查询日志:从筛选关键包裹到趋势统计

找出所有关键包裹

默认回看窗口是最近 1 小时(对应--since=1h的默认值,见 cmd/logcli/main.go#L711),查询service_nameDelivery Worldpackage_statuscritical的日志:

logcli query '{service_name="Delivery World"} | package_status="critical"'

输出类似:

http://localhost:3100/loki/api/v1/query_range?direction=BACKWARD&end=1732617594381712000&limit=30&query=%7Bservice_name%3D%22Delivery+World%22%7D+%7C+package_status%3D%22critical%22&start=1732613994381712000 Common labels: {package_status="critical", service_name="Delivery World"} 2024-11-26T10:39:52Z {package_id="PKG79755", package_size="Small", state="Texas"} {"timestamp": "2024-11-26T10:39:52.521602Z", "state": "Texas", "city": "Dallas", "package_id": "PKG79755", "package_type": "Clothing", "package_size": "Small", "package_status": "critical", "note": "In transit", "sender": {"name": "Sender38", "address": "906 Maple Ave, Dallas, Texas"}, "receiver": {"name": "Receiver41", "address": "455 Pine Rd, Dallas, Texas"}} 2024-11-26T10:39:50Z {package_id="PKG34018", package_size="Large", state="Illinois"} {"timestamp": "2024-11-26T10:39:50.510841Z", "state": "Illinois", "city": "Chicago", "package_id": "PKG34018", "package_type": "Clothing", "package_size": "Large", "package_status": "critical", "note": "Delayed due to weather", "sender": {"name": "Sender22", "address": "758 Elm St, Chicago, Illinois"}, "receiver": {"name": "Receiver10", "address": "441 Cedar Blvd, Naperville, Illinois"}}

要点:

  • 默认输出模式(default)为时间戳 + 该流的标签 + 原始日志行,并附带Common labels(所有结果共有的标签)等查询元信息,可用--quiet/-q抑制;--output=raw只输出日志行,--output=jsonl输出 Loki API 的 JSON 响应。
  • 默认只返回前 30 条(--limit=30)。

回看 24 小时:

logcli query --since 24h '{service_name="Delivery World"} | package_status="critical"'

增加返回条数上限:

logcli query --since 24h --limit 100 '{service_name="Delivery World"} | package_status="critical"'

其余常用时间参数:--from/--to指定绝对时间范围(RFC3339Nano 格式、不带时区后缀),--step用于指标查询的分辨率步长,--batch控制直到达到 limit 前的每批大小(默认 1000,在 cmd/logcli/main.go#L716 注册)。query命令还支持--tail/-t--follow/-f为别名)实时跟踪日志、--forward正向扫描、--no-labels--exclude-label/--include-label--colored-output等输出控制。

从实现看,范围查询会在 pkg/logcli/query/query.go#L139-L211 中按--batch分批循环调用QueryRange:以上一批最后一条日志的时间戳作为下一批的起点/终点,并处理同时间戳重复条目带来的重叠,直到达到 limit;每次请求后打印统计信息(配合--stats标志)。

指标查询:按 1 小时粒度统计包裹数

统计最近 24 小时加州发出的包裹总数,按 1 小时间隔:

logcli query --since 24h 'sum(count_over_time({state="California"}[1h]))'

返回一个 JSON 对象,包含一组 Unix 时间戳与对应区间的包裹计数。由于是对日志计数做累计求和,总数会随时间单调增长:

[ { "metric": {}, "values": [ [1733913765, "46"], [1733914110, "114"], [1733914455, "179"], [1733914800, "250"], [1733915145, "318"], [1733915490, "392"], [1733915835, "396"] ] } ]

query命令支持指标查询,但输出的是时间段内的多个数据点(类似 Grafana Explore 的 graph 视图)。再进一步,用json解析器抽取package_type字段并过滤出 Documents:

logcli query --since 24h 'sum(count_over_time({state="California"}| json | package_type="Documents" [1h]))'

返回结构类似,但只展示加州发出 Documents 包裹的 1 小时间隔趋势。

即时指标查询:只看当前时刻的聚合值

即时指标查询(instant metric query)返回某个特定时间点上指标的值,适合快速了解日志的聚合状态。查询最近 5 分钟加州发出的包裹数:

logcli instant-query 'sum(count_over_time({state="California"}[5m]))'
[ { "metric": {}, "value": [ 1732702998.725, "58" ] } ]

注意:instant-query相当于 Grafana Explore 的 table 视图,只返回最新数据点;查询日志行时它没有实用输出,应该始终用query命令。即时查询可通过--now指定执行时刻(见 cmd/logcli/main.go#L708)。

把查询结果写入文件:并行下载全量日志

LogCLI 可以把查询结果写入文件,适合下载库存报告等全量数据。先创建目录:

mkdir -p ./inventory

然后使用并行下载参数,把Delivery World最近 24 小时的全部日志写入./inventory目录:

logcli query \ --timezone=UTC \ --output=jsonl \ --parallel-duration="12h" \ --parallel-max-workers="4" \ --part-path-prefix="./inventory/inv" \ --since=24h \ '{service_name="Delivery World"}'

日志会被拆成两个文件,每个文件包含 12 小时数据。注意:指定了--parallel-duration--limit会被忽略(cmd/logcli/main.go#L464-L467 中并行模式下强制把Limit置 0)。

并行下载的实现要点(见 pkg/logcli/query/query.go):

  • --parallel-duration:把时间范围切分成若干长度相同的 job。以 24 小时、12h 为例会生成 2 个 job。
  • --parallel-max-workers:并行 worker 数量;为 1 时不启动并行(走普通路径)。每个 job 通过DoQuery独立执行,startWorkers用带缓冲的 channel 分发任务。
  • --part-path-prefix:每个 job 的结果保存为前缀_UTC起始_UTC结束.part格式的 part 文件;下载过程中文件名带.part后缀,完成后去掉。默认情况下已完成 part 文件会跳过不再下载,可用--overwrite-completed-parts覆盖。
  • 默认按时间倒序(BACKWARD)下载 part,可用--forward改为正向。
  • --merge-parts:按顺序读取 part 文件并输出到 stdout(边下载边输出,读完后默认删除 part 文件),--keep-parts可保留它们。

元查询:理解数据卫生与查询性能

作为站点管理员,保持数据卫生并确保 Loki 高效运行至关重要。元查询不返回日志数据,而是揭示日志的结构与查询性能。以下示例是官方运维中常用的核心元查询。

检查序列基数(series cardinality)

序列(series)是标签组合的集合,高序列基数会导致性能下降和存储成本上升。

列出日志中全部唯一序列:

logcli series '{}'
{package_size="Small", service_name="Delivery World", state="Florida"} {package_size="Medium", service_name="Delivery World", state="Florida"} {package_size="Small", service_name="Delivery World", state="California"} {package_size="Large", service_name="Delivery World", state="New York"} {package_size="Small", service_name="Delivery World", state="Illinois"} {package_size="Large", service_name="Delivery World", state="Florida"} {package_size="Medium", service_name="Delivery World", state="Illinois"} {package_size="Large", service_name="Delivery World", state="Texas"} {package_size="Medium", service_name="Delivery World", state="California"} {package_size="Medium", service_name="Delivery World", state="Texas"} {package_size="Small", service_name="Delivery World", state="Texas"} {package_size="Large", service_name="Delivery World", state="Illinois"} {package_size="Small", service_name="Delivery World", state="New York"} {package_size="Medium", service_name="Delivery World", state="New York"} {package_size="Large", service_name="Delivery World", state="California"}

空匹配器'{}'返回所有流。加上--analyze-labels汇总每个标签的唯一值数量:

logcli series '{}' --analyze-labels
Label Name Unique Values Found In Streams state 5 15 package_size 3 15 service_name 1 15

从实现看(pkg/logcli/seriesquery/series.go#L36-L71),--analyze-labels会遍历每个流,统计每个标签名出现的流数量与唯一值集合,按唯一值数量降序用 tabwriter 打印表格,并额外输出Total StreamsUnique Labels。这是定位高基数标签的利器。

检测字段(Detected fields):判断标签 vs 结构化元数据

detected-fieldsjsonlogfmt解析器对日志行做字段检测,帮你了解日志中存在哪些键,从而决定哪些键适合提升为标签、哪些适合保留在结构化元数据中:

logcli detected-fields --since 24h '{service_name="Delivery World"}'
label: city type: string cardinality: 15 label: detected_level type: string cardinality: 3 label: note type: string cardinality: 7 label: package_id type: string cardinality: 994 label: package_size_extracted type: string cardinality: 3 label: package_status type: string cardinality: 4 label: package_type type: string cardinality: 5 label: receiver_address type: string cardinality: 991 label: receiver_name type: string cardinality: 100 label: sender_address type: string cardinality: 991 label: sender_name type: string cardinality: 100 label: state_extracted type: string cardinality: 5 label: timestamp type: string cardinality: 1000

现在你能理解为什么package_id放在结构化元数据中、而package_size做成标签了:package_id基数高达 994,几乎每个日志条目都不同,将来可能需要按它精确查询,适合作为结构化元数据;package_size基数只有 3,天然适合做标签。detected-fields默认最多返回 100 个字段(--limit)、每个子查询处理 1000 行(--line-limit),可传可选的第二个参数指定单个字段名,默认步长--step=10s(见 cmd/logcli/main.go#L825-L865)。

检查查询性能:stats

保持 Loki 健康还要关注查询性能。stats返回查询所触及的数据量统计:

logcli stats --since 24h '{service_name="Delivery World"}'
http://localhost:3100/loki/api/v1/index/stats?end=1732639430272850000&query=%7Bservice_name%3D%22Delivery+World%22%7D&start=1732553030272850000 { bytes: 12MB chunks: 63 streams: 15 entries: 29529 }

包括查询的字节数、chunk 数、流数与条目数。缩小查询范围(追加第二个标签)可以对比性能:

logcli stats --since 24h '{service_name="Delivery World", package_size="Large"}'
{ bytes: 4.2MB chunks: 22 streams: 5 entries: 10198 }

可见收窄标签后触及的流与条目大幅减少。从实现看(pkg/logcli/index/stats.go、pkg/logcli/client/client.go#L193-L204),stats请求的是 Loki 的/loki/api/v1/index/stats接口,返回的IndexStatsResponse包含 bytes、chunks、streams、entries 四个维度。

注意:stats/volume仅对使用 TSDB 索引格式的 Loki 实例有效;且 LogCLI 只能返回触及对象存储的查询统计。本演示为了让统计可见而把 ingester 的 flush 间隔压到 5 分钟(生产环境不推荐)。如果运行演示时没有看到统计数据,等几分钟再执行一次。

检查日志量:volume 与 volume_range

了解正在写入 Loki 的数据量有助于容量规划。查询Delivery World最近 24 小时的日志总量:

logcli volume --since 24h '{service_name="Delivery World"}'
[ { "metric": { "service_name": "Delivery World" }, "value": [ 1732640292.354, "11669299" ] } ]

结果包含时间戳与日志摄入总数。用volume_range查看日志量随时间的变化:

logcli volume_range --since 24h --step=1h '{service_name="Delivery World"}'

--step把日志量按 1 小时桶聚合;注意:某小时如果没有日志,该小时不会返回值。还可以按特定标签值分桶聚合:

logcli volume_range --since 24h --step=1h --targetLabels='state' '{service_name="Delivery World"}'

volume/volume_range的实现位于 pkg/logcli/index/volume.go,底层请求 Loki 的/loki/api/v1/index/volume/loki/api/v1/index/volume_range接口;--targetLabels指定按哪些标签分组聚合(cmd/logcli/main.go#L815),volume_range的默认--step=1h(cmd/logcli/main.go#L819)。

查询静态日志文件

LogCLI 还支持直接查询不在 Loki 中的静态日志文件。上一节我们把Delivery World的日志存到了./inventory目录,现在用类似命令把结果合并输出到单个文件:

logcli query \ --timezone=UTC \ --parallel-duration="12h" \ --parallel-max-workers="4" \ --part-path-prefix="./inventory/inv" \ --since=24h \ --merge-parts \ --output=raw \ '{service_name="Delivery World"}' > ./inventory/complete.log

--merge-parts会按顺序读取 part 文件并输出到 stdout(原始日志行模式raw),然后通过 shell 重定向写入complete.log

接着对静态文件执行查询:

cat ./inventory/complete.log | logcli --stdin query '{service_name="Delivery World"} | json | package_status="critical"'

注意:查询静态日志文件时标签不会自动识别,因此:

  • {service_name="Delivery World"}在这种情况下是可选的前缀(为了表达清晰建议保留);
  • json是必须的——它把日志行按 JSON 解析,从而提取package_status字段。

例如,省略json过滤器再试:

cat ./inventory/complete.log | logcli --stdin query '{service_name="Delivery World"} | package_status="critical"'

由于没有解析 JSON,package_status字段无法被检测到,查询返回空结果。

从实现看,--stdin标志会把客户端切换为client.NewFileClient(os.Stdin)(cmd/logcli/main.go#L393-L417)。其核心机制在 pkg/logcli/client/file.go:

  • FileClient为输入注入一个固定的虚拟标签source="logcli",并用本地logql.Engine直接对文件内容执行 LogQL(最大读取 20MB,见defaultMaxFileSize)。
  • 如果查询以|!开头(即省略了流选择器),main.go 会自动注入{source="logcli"}作为流选择器,使|="error"这类省略式查询也能工作。
  • FileClientSelectLogs会把每行日志按时间顺序(BACKWARD 为逆序)逐条送入日志管道(pipeline)匹配处理,命中则归入对应流。
  • statsvolumedetected-fieldsdelete等在文件客户端上返回ErrNotSupported——它们依赖 Loki 的索引能力。

常见连接配置速查

下表汇总 LogCLI 与 Loki 连接相关的常用参数与对应环境变量(全部注册于 cmd/logcli/main.go#L574-L619):

参数环境变量说明
--addrLOKI_ADDRLoki 服务地址,默认http://localhost:3100
--username/--passwordLOKI_USERNAME/LOKI_PASSWORDHTTP 基本认证凭据
--org-idLOKI_ORG_ID为请求添加X-Scope-OrgID头,用于指定租户
--bearer-token/--bearer-token-fileLOKI_BEARER_TOKEN/LOKI_BEARER_TOKEN_FILEBearer Token 认证
--ca-cert/--tls-skip-verifyLOKI_CA_CERT_PATH/LOKI_TLS_SKIP_VERIFYTLS 服务端证书校验
--cert/--keyLOKI_CLIENT_CERT_PATH/LOKI_CLIENT_KEY_PATH客户端 mTLS 证书
--retries/--min-backoff/--max-backoffLOKI_CLIENT_RETRIES/LOKI_CLIENT_MIN_BACKOFF/LOKI_CLIENT_MAX_BACKOFF查询失败重试策略
--proxy-url/--envproxyLOKI_HTTP_PROXY_URL/LOKI_ENV_PROXYHTTP 代理
--compressLOKI_HTTP_COMPRESSION请求传输压缩
--nocacheLOKI_NO_CACHE添加Cache-Control: no-cache请求头

结论

在本次教程中,作为物流公司的站点管理员,我们使用 LogCLI 完成了三件事:查询日志并构建包裹健康报告;通过元查询理解数据卫生(基数、检测字段)与查询性能(stats、volume);以及直接查询静态日志文件。LogCLI 是理解日志内容及其在 Loki 中存储方式的强大工具。随着你的解决方案规模扩大,请记得用 LogCLI 持续监控序列基数与查询性能——这往往是 Loki 长期健康运行的关键。更多命令细节可查阅 LogCLI 入门与命令参考,命令的全部参数可通过logcli helplogcli help query等查看。

【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询