1. Nginx原始日志让我睡不着的那几个晚上
搞了七年运维,最怕的不是服务器半夜宕机,而是 Nginx 日志堆成山之后,定位一个问题要花掉大半个晚上。刚带团队那会儿,大家都觉得 grep 够用,出问题就 ssh 到网关机器上 tail -f 硬看。说实话,单机、低 QPS 的阶段这套确实能顶。但当流量上来、Nginx 前面还有多层负载、后端又有几十个微服务节点的时候,原始日志的短板会被无限放大。
VictoriaLogs、Grafana 这两个词我关注了很久,真正促使我落地的是一次线上事故:某个凌晨接口超时飙到 90%,我们需要同时查 8 台 Nginx access log,还要跨多个实例对比同一条 trace_id。结果每台机器 grep 一次要二三十分钟,等我把日志碎片拼起来,业务已经恢复了。那次复盘会开得很难看,但让我下了决心:必须建一套正经的应用日志分析平台,把 Nginx 日志从"服务器上的文本文件"变成"随时能检索、能统计、能出图的数据资产"。
如果你也在靠 grep、tail、scp 处理 Nginx 日志,这篇实战记录应该能帮你少踩几个坑。我讲的不只是安装步骤,而是从日志格式改造、采集链路设计、查询语法到 Grafana 面板搭建的完整闭环。方案选型上我权衡过 ELK、Loki,最终还是落在 VictoriaLogs + Grafana + Promtail 这套组合上,接下来把为什么这么选、怎么搭、踩了哪些坑,一次性讲透。
1.1 grep排查在日志量上来之后会断崖式失效
不少人觉得"日志查询不就是 grep 吗",这句话在小流量下没毛病,甚至我自己也这么干过很久。但日志量上到每天几十 GB、上百 GB 之后,几个现实问题会接连砸过来:
- 跨节点检索靠手动拼凑:Nginx 多实例部署后,一条请求可能落在任意一台机器的 access.log 里。你要先确定哪台机器,再上去 grep,如果没命中还要换下一台。时间全浪费在"找日志在哪"而不是"看日志里有什么"上。
- 时间线对不齐:每台机器系统时间、时区、Nginx 编译参数可能有差异,日志里的 $time_local 是本地时间,多台机器放在一起看,前后顺序是乱的。
- logrotate 一切割,历史日志就"断片":默认配置下日志按天切割,一旦某天忘记处理或者压缩失败,想查三天前的日志还要先去解压、拼接、再 grep,效率极低。
- 检索语法太原始:grep 能匹配文本,但做不了"统计 5xx 状态码占比""算接口 P95 响应时间""按域名分组看访问量"这类分析。真要算,只能把日志导出来用 Python 脚本处理,每次都是临时工。
这些痛点叠加在一起,就不是"忍一忍"能解决的了。日志本质上是有时间序列属性的结构化数据,应该交给专门的存储和查询引擎,而不是躺在磁盘上等人用 shell 去啃。
1.2 企业级日志平台不是炫技,而是事故复盘的基本盘
所谓"企业级",不是说功能越多越好,而是必须满足几个底线要求:
- 统一采集:不管 Nginx 部署在物理机、虚拟机还是容器里,日志都能自动汇聚到一个地方。
- 结构化解析:日志进入平台后是字段化的,比如 status、request_uri、request_time 都能单独查询,而不是一整行字符串。
- 快速检索:面对几十 GB 一天的日志量,按关键字、按时间范围、按状态码组合检索,必须秒级返回,而不是每次等一个离线任务。
- 可视化分析:能直接生成趋势图、占比图、Top N 列表,让开发、运维在同一个面板上看到问题。
- 告警能力:配合 Grafana Alerting,可以在 5xx 比例突增、P95 超阈值时及时通知,而不是等用户反馈。
这套能力以前用 ELK 也能搭,但 ELK 的组件多、资源占用高、维护成本重,中小团队往往搭得起养不起。我身边不少同事折腾完 ELK 最后退回 grep,就是因为被 Elasticsearch 的 JVM 调到崩溃、Logstash 的 pipeline 吞日志等问题劝退。所以这次选型,我的核心诉求很明确:资源占用要轻、部署要简单、查询要快,最好不引入太多新组件。
1.3 为什么挑中 VictoriaLogs 而不是继续用 ELK/Loki
先说我试过的几个方案:
- ELK(Elasticsearch + Logstash + Kibana):功能确实强,但一个三节点的 Elasticsearch 集群起步就是十几 GB 内存,Logstash 还要单独吃资源。对于只查 Nginx 访问日志这个场景,属于杀鸡用牛刀,而且 Find 排查问题的时候,Kibana 查询语法对不熟的人有点劝退。
- Loki:轻量、和 Grafana 集成好,但 LogQL 的标签设计把日志拆得很细,查询长日志或者做统计分析时,语法写起来总觉得别扭。我们团队用了一段时间,P95 响应时间这类统计需求很难用 LogQL 直观地写出来。
- VictoriaLogs:VictoriaMetrics 团队出的日志存储引擎,主打单二进制、低资源占用、高压缩率,查询语言 LogsQL 上手快。更关键的是它兼容 Loki、Elasticsearch 的采集协议,Promtail、Fluent Bit 这些现成客户端可以直接对接,不用为日志接入重新发明轮子。
我选型时在测试环境做了一轮对比:同样灌入一天约 20GB 的 Nginx 访问日志,VictoriaLogs 单机内存占用 1GB 左右,磁盘压缩后约 2GB 不到;相同数据量如果塞进 Elasticsearch,即便场景调优过,磁盘占用也远高于这个数。对于绝大多数中小团队,这个差距足够改变决策了。
2. VictoriaLogs的底层设计和查询语法,为什么敢说吃日志很香
2.1 单二进制、列式存储、字段即索引
VictoriaLogs 最吸引我的一点就是部署极其简单。它不像 Elasticsearch 那样要起多个角色节点,也不像 Loki 那样要把 chunk 和 index 分开管理,默认情况下一个二进制文件跑起来就能用。我第一次启动它的时候甚至有点不真实感——就一个进程,连配置文件都不强制要。
底层存储上,VictoriaLogs 用的是列式日志存储,数据按时间组织成段,并且按字段单独存储。这个设计和 ClickHouse 的思路有点像:日志写入后,每一列独立压缩,查询时只扫描命中的字段,而不是把整行数据都读出来。对于访问日志这种场景——每一行都是结构化的 JSON,字段可预期、可枚举——列式存储的压缩率和扫描性能都非常好看。
另一个关键设计是"字段即索引"。VictoriaLogs 不是靠预先定义 mapping 来建立倒排索引,而是把每条日志里的字段自动识别出来。查询的时候,LogsQL 可以直接用字段名过滤,比如 status:=500、request_uri:/api/order,不需要在摄入阶段先建索引模板。这也意味着,如果后面想给 Nginx 日志增加一个新字段,直接在 log_format 里加就行,平台侧什么都不用改。
2.2 流、字段、时间线:先建立三个心智模型
用 VictoriaLogs 之前,我建议先理解三个核心概念:
- 时间线(_time):每条日志都有一个时间戳,默认是摄入时间,也可以从日志内容中解析。检索时第一件事就是圈定时间范围,这既是习惯,也是性能关键——VictoriaLogs 会先在对应的时间分区里扫描。
- 流(_stream):日志在采集时会打上标签,比如 job="nginx-access"、host="web-01",这些标签组合起来就是一条日志流。理解成"日志来自哪个来源"就行了。查询时可以用流过滤条件快速缩小范围。
- 字段(_fields):日志正文里解析出来的键值对,比如 status、request_time、upstream_addr。字段是查询和统计的主要对象。
这三个概念搞明白,LogsQL 的很多语法就能猜个大概了。比如我想看最近 10 分钟所有 500 错误,第一时间想到的就是"时间范围 + 字段过滤"的组合,这也是 LogsQL 最常用的写法。
2.3 兼容多种采集协议:不逼你换客户端
很多人担心引入新日志平台后要重写采集器。VictoriaLogs 这点做得很到位,它直接兼容 Loki 的 push API、Elasticsearch 的 bulk API,也支持 Syslog、Journald 等输入方式。这意味着:
- 已经在用Promtail采集 Loki 日志的,把 clients 里的 URL 指向 VictoriaLogs 就行;
- 已经在用Fluent Bit的,可以通过 HTTP 输出插件往 Loki 端点推送;
- 已经在用Filebeat的,可以走 Elasticsearch 兼容接口;
- 想快速测试的,直接 curl 往里塞一条 JSON 就能验证。
这个特性在落地时帮了大忙。当时我们团队日志采集已经有一部分用了 Promtail,切换 VictoriaLogs 时只需要改配置文件里的 endpoint,不需要对日志采集链路做大手术。下面我会把 Promtail 的具体配置贴出来,你可以直接抄。
3. 部署:VictoriaLogs + Grafana 从零跑到能查日志
3.1 硬件规划和目录
我建议的起步配置是 4C8G 的虚拟机,按一天 20~50GB 日志量、保留 14 天来算基本够用。如果你峰值流量特别高,可以加到 8C16G,VictoriaLogs 本身对硬件比较克制,最吃资源的是查询时的内存,日常写入阶段 2C4G 也能跑,但查询大时间范围时会吃力。
磁盘方面,有条件就上 SSD,日志检索本质是 IO 密集操作,机械盘在扫几十 GB 数据时会明显变慢。目录结构我习惯这样规划:
/opt/victoria-logs/bin # 二进制 /opt/victoria-logs/conf # 配置文件(如果需要) /var/lib/victoria-logs/data # 日志数据目录 /var/log/victoria-logs/ # 运行日志生产环境建议单独建一个系统用户跑 VictoriaLogs,不要用 root。后续如果做权限收敛,或者接审计系统,独立的运行账号会省很多事。
3.2 用二进制部署 VictoriaLogs
先去 GitHub 的 VictoriaMetrics Releases 页面下载最新版victoria-logs-linux-amd64压缩包,解压后把victoria-logs-prod(或解压出来的二进制名)放到 /usr/local/bin 下。我习惯把版本固定住,不追最新,稳定优先。
解压后先手动跑一下确认版本:
./victoria-logs-prod -version确认没问题后,创建运行用户和数据目录:
useradd -r -s /sbin/nologin victorialogs mkdir -p /var/lib/victoria-logs/data chown -R victorialogs:victorialogs /var/lib/victoria-logs chown -R victorialogs:victorialogs /opt/victoria-logs然后写一个 systemd unit,让服务可以开机自启、异常重启:
[Unit] Description=VictoriaLogs single-node log service After=network-online.target Wants=network-online.target [Service] User=victorialogs Group=victorialogs ExecStart=/usr/local/bin/victoria-logs-prod \ -storageDataPath=/var/lib/victoria-logs/data \ -retentionPeriod=14d \ -httpListenAddr=0.0.0.0:9428 \ -loggerOutput=stdout Restart=always RestartSec=10 LimitNOFILE=1048576 [Install] WantedBy=multi-user.target这里解释几个关键参数:
- -storageDataPath:数据存放路径,一定要放在磁盘空间充足、监控覆盖到的目录。后续容量管理围绕这个目录做。
- -retentionPeriod:保留周期,我设的是 14d。日志平台不是数据仓库,没必要永久存访问日志,设短一点既能控制磁盘成本,也能减少查询扫过的数据量。个别需要长留的,可以后面在采集端做二次归档。
- -httpListenAddr:默认就是 9428 端口,一般不用改。如果 VictoriaLogs 和 Grafana 不在同一台机器,注意防火墙要放行这个端口。
- LimitNOFILE:进程能打开的句柄数。高并发写入时,如果句柄数不够会出现 "too many open files" 的问题,所以提前放开。
启动后看一眼日志输出,确认没有报错:
systemctl daemon-reload systemctl enable --now victorialogs systemctl status victorialogs接着验证 HTTP 接口有没有起来:
curl http://127.0.0.1:9428/select/vmui如果浏览器能打开 vmui 界面,说明服务已经正常工作了。vmui 是 VictoriaLogs 自带的查询界面,后续临时查日志、验证数据有没有进来,我都会先用它,比去 Grafana 配面板快得多。
3.3 Grafana 安装和 VictoriaLogs 数据源
Grafana 的安装方式很多,用 DEB/RPM 包、二进制、Docker 都行。我这边是直接用官方 DEB 包装的:
wget https://dl.grafana.com/oss/release/grafana_10.4.0_amd64.deb sudo dpkg -i grafana_10.4.0_amd64.deb sudo systemctl enable --now grafana-server装完先登录 Grafana(默认 3000 端口,初始账号 admin/admin),然后安装 VictoriaLogs 数据源插件。插件 ID 是victoriametrics-logs-datasource:
grafana-cli plugins install victoriametrics-logs-datasource sudo systemctl restart grafana-server在 Grafana 里添加数据源时,选择 "VictoriaLogs" 类型即可,URL 填 VictoriaLogs 的地址,比如http://127.0.0.1:9428。有一个 "Explore mode" 选项,我一般选 "Logs Explorer",这样在 Explore 页面里可以直接用字段浏览器的方式筛选日志,对不熟悉 LogsQL 的同事更友好。保存后点 "Save & test",能连通就说明数据源配置没问题。
如果不想每次在页面上手工点,也可以用 provisioning 的方式把数据源固化下来,顺便把 UID 写死,这个好处到后面踩坑部分你就知道了:
# /etc/grafana/provisioning/datasources/victorialogs.yml apiVersion: 1 datasources: - name: VictoriaLogs uid: victorialogs-prod type: victoriametrics-logs-datasource access: proxy url: http://127.0.0.1:9428 isDefault: false jsonData: exploreMode: logsExplorer3.4 冒烟验证
平台起好之后,先手动塞一条日志确认链路通不通。VictoriaLogs 兼容 Loki 的 push API,我习惯用 curl 模拟:
curl 'http://127.0.0.1:9428/insert/loki/api/v1/push' \ -H 'Content-Type: application/json' \ -d '{"streams":[{"stream":{"job":"smoke-test"},"values":[["'$(date +%s)000000000'","hello victorialogs"]]}]}'然后去 vmui 页面查一下:
_time:now-5m AND job:smoke-test能查到这一条测试日志,就说明 VictoriaLogs 的摄入和查询都正常了。接下来可以放心地进入采集链路改造环节。
4. 日志接入:把 Nginx 访问日志改造成结构化 JSON
4.1 为什么 Nginx 日志要用 JSON 格式
大多数 Nginx 默认访问日志格式是 combined,一行文本用空格和引号分隔。这种格式人眼读还行,但机器解析很痛苦。虽然 Promtail 和 Fluent Bit 都有对应的正则解析器,但正则匹配在字段变化时非常脆弱:只要某个字段里多了一个引号、空格或者奇怪的字符,整行解析就可能失败,日志静默丢弃,问题反而更难查。
所以我强烈建议,如果要把 Nginx 日志接入日志平台,第一步不是配采集器,而是先把 Nginx 的 log_format 改成 JSON。JSON 的好处有几点:
- 天然结构化:进入 VictoriaLogs 后 status、request_time 这些字段自动可用,查询和统计不需要再写正则。
- 解析零失败:只要 JSON 合法,字段就不会错位,不会再出现"日志进来了但关键字段是空的"这类怪问题。
- 采集端更简单:Promtail、Fluent Bit 对这个格式几乎是开箱即用。
4.2 nginx.conf 里的 log_format 改造
在 nginx.conf 的 http 块里加一个自定义 JSON 格式:
log_format json_combined escape=json '{' '"time_iso8601":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"request_method":"$request_method",' '"request_uri":"$request_uri",' '"server_name":"$server_name",' '"status":$status,' '"body_bytes_sent":$body_bytes_sent,' '"request_time":$request_time,' '"http_referer":"$http_referer",' '"http_user_agent":"$http_user_agent",' '"upstream_addr":"$upstream_addr",' '"upstream_response_time":"$upstream_response_time"' '}';注意几个细节:
- escape=json 必须加:不加的话,user_agent 里的引号、特殊字符会把 JSON 结构打坏。
- status 和 request_time 不要用引号包起来:这两个字段我希望 VictoriaLogs 识别成数值类型,这样后面才能做数值比较和统计。字符串类型的 "200" 和数值类型的 200 在日志平台里是两个概念,类型不统一非常痛苦。
- 用 $time_iso8601 而不是 $time_local:前者带时区信息,格式是 RFC3339,Promtail 解析时间戳时不容易出错。这个坑后面专门讲。
然后在要采集的 server 块里指定这个格式,并加上缓冲参数:
access_log /var/log/nginx/access.json.log json_combined buffer=64k flush=5s;buffer 和 flush 的意义是减少磁盘 IO,让日志按块刷盘。代价是如果进程突然崩溃,最后几秒的日志可能会丢。对于访问日志这种非强一致的数据,为了性能做一点妥协是可以接受的。如果你的业务对日志完整性要求特别高,就不要开 buffer,直接access_log /var/log/nginx/access.json.log json_combined;。
改完之后nginx -t检查语法,然后 reload:
nginx -t nginx -s reload去新日志文件里看一眼,确认输出是合法 JSON:
tail -2 /var/log/nginx/access.json.log | jq .4.3 结合 logrotate 做好切割和补偿
Nginx 的 access.log 不会自己切割,需要配 logrotate。这里要给 Promtail 留一个心眼:Promtail 是靠 position 文件记录读取到哪个位置的,如果 logrotate 切割时把原文件内容搬走了、又新建了同名文件,Promtail 需要能感知到文件变化。
我用的 logrotate 配置如下:
/var/log/nginx/access.json.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 www-data adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid) endscript }create 0640 www-data adm保证切割后新文件的属主和权限正确,否则 Promtail 可能没有权限读取新文件。postrotate里向 Nginx 发送 USR1 信号,让 Nginx 重新打开新的日志文件句柄。这个信号很重要,不发的后果就是 Nginx 继续往旧文件写入,而旧文件已经被改名,日志就"丢"了。
4.4 Promtail 配置与 VictoriaLogs 对接
Promtail 是 Grafana Loki 生态的采集器,因为 VictoriaLogs 兼容 Loki push API,所以直接把 Promtail 的 endpoint 指向 VictoriaLogs 就行。安装 Promtail 我一般直接用二进制,下载解压后写一个 config.yml:
server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /var/lib/promtail/positions.yaml clients: - url: http://victoria-log-server:9428/insert/loki/api/v1/push scrape_configs: - job_name: nginx-access static_configs: - targets: [localhost] labels: job: nginx-access host: web-01 __path__: /var/log/nginx/access.json.log pipeline_stages: - json: expressions: time_iso8601: time_iso8601 - timestamp: source: time_iso8601 format: RFC3339Nano这一段配置里有两个地方要解释清楚:
第一,为什么只用 json stage 提取时间戳,没有把所有字段都提取出来?因为 VictoriaLogs 本身能识别 JSON message 里的字段。Promtail 把原始 JSON 整行推送过去之后,VictoriaLogs 在摄入时会自动解析字段,所以不需要在 Promtail 里做一次字段展开。只需要把time_iso8601提取出来用来设置日志时间戳,避免使用"摄入时间"代替"日志真实产生时间"。
第二,为什么 labels 只保留 job 和 host?很多初学 Loki 的人会把 status、request_uri、method 都做成 label。这在 Loki 的原始设计里还能接受,但在 VictoriaLogs 这条链路上,字段本来就是可查询的,没必要全部提成流标签。尤其是 client_ip、request_uri 这类高基数字段,如果做成流标签,会产生海量日志流,严重拖慢写入和查询。日志流标签越稳定越好,job 和 host 这种"来源标识"才是它们该做的活。
启动 Promtail:
./promtail -config.file=config.yml生产上同样建议用 systemd 托管,这里就不重复贴 unit 了,照抄 VictoriaLogs 的模板改一下路径就行。
4.5 验证数据进没进来
Promtail 起来后,先看它的日志有没有报错。确认没有连接异常后,去 vmui 里查一下:
_time:now-10m AND job:nginx-access能看到 Nginx 的 JSON 访问日志,说明采集链路已经通了。再点开一条日志,确认 status、request_time、upstream_addr 这些字段都被正确解析。如果字段缺失,优先检查 Nginx 端 log_format 是不是写错了字段名,或者escape=json有没有生效。
5. LogsQL 实战:检索、统计和慢请求定位
5.1 先搞懂一条 LogsQL 的组成
LogsQL 的查询由两部分组成:过滤条件 + 管道操作。过滤条件负责圈定日志范围,管道操作负责对命中的日志做统计、排序、截取等处理。最简单的查询就是直接打一个关键字:
payment_error这会检索所有 message 里包含 payment_error 的日志。生产环境我一般都会先加时间范围和字段过滤,避免扫全量数据:
_time:now-1h AND status:=500_time:now-1h是时间范围缩写,等价于"最近一小时"。status:=500是字段精确匹配,要求 status 字段的值等于数字 500。这个组合几乎是我每天用最多的查询。
5.2 日常检索举例
下面这些查询都是我在生产环境里实际用过的,字段名和你的 Nginx log_format 保持一致即可:
| 场景 | LogsQL 写法 |
|---|---|
| 最近1小时所有5xx错误 | _time:now-1h AND (status:=500 OR status:=502 OR status:=503 OR status:=504) |
| 某个域名下的404请求 | _time:now-1h AND server_name:=example.com AND status:=404 |
| 慢请求(响应时间超过1秒) | _time:now-1h AND request_time:>1 |
| 某个客户端IP的访问记录 | _time:now-24h AND remote_addr:="1.2.3.4" |
| 消息里包含关键字且状态5xx | _time:now-1h AND status:>499 AND "timeout" |
这里有个小技巧:5xx 状态码可以写成status:>499,相比把 500、502、503、504 一个个列出来,代码更简洁,也不容易漏。前提是 status 字段在日志里是数值类型,这又回到了前面强调的"写成 JSON 时不要给数值字段加引号"。
5.3 用统计管道把日志变成指标
检索只是日志平台的基础,真正体现价值的是统计能力。以前算状态码分布,要导日志用脚本跑;现在一条 LogsQL 直接出数。
按状态码统计请求量:
_time:now-6h AND job:nginx-access | stats by (status) count() as requests这个查询把最近六小时日志按 status 分组计数,结果是一张三列的表:status、requests。可以在 vmui 或 Grafana 里直接看,也可以接到饼图、柱状图上。
算 P95 响应时间:
_time:now-1h AND job:nginx-access | stats quantile(0.95, request_time) as p95如果还想看每个上游节点(upstream_addr)的平均响应时间和最大响应时间:
_time:now-1h AND job:nginx-access | stats by (upstream_addr) avg(request_time) as avg_rt, max(request_time) as max_rt找 Top 20 慢接口:
_time:now-24h AND job:nginx-access AND request_time:>1 | stats by (request_uri) avg(request_time) as avg_rt | sort by (avg_rt) desc | limit 20这些统计查询的使用门槛并不高,核心是先确认字段名、字段类型正确,再决定用哪个聚合函数。真正常见的坑恰恰不是语法,而是字段类型到了查询阶段发现是字符串,导致聚合函数直接报错。这个我在下一节踩坑部分细说。
5.4 实践建议:标签不要滥用
使用 VictoriaLogs 的时候最容易产生的一个错误心态是:"反正日志字段都能查,那我在采集端把所有字段都标上,不是更快吗?"
我的建议是:采集端只要保留稳定、低基数的标签,比如 job、host、environment(环境名)。像 status、request_uri、remote_addr 这类字段,让它们在日志正文里保持 JSON 结构,查询时用 LogsQL 过滤即可。原因有二:
- 写入性能:日志流数量等于标签组合数。标签越多、组合越复杂,流越多,底层需要维护的元数据越大。
- 查询性能:走字段过滤和走流过滤,在 VictoriaLogs 里差不了太多,因为底层列式存储本身做了优化。牺牲写入侧的性能去换查询侧那一点便利,不划算。
6. Grafana 面板:从看日志到看业务
6.1 Explore 还是 Logs Explorer
VictoriaLogs 数据源插件在 Grafana 里有两种使用方式。一个是标准的 Explore 页面,直接写 LogsQL 查询,结果以日志流形式展示,适合定位问题的时候快速翻日志。另一个是 Logs Explorer 模式,本质上是一个可视化的字段浏览器:左边列出日志流和字段,你点选条件,右边自动出日志,不用手写语法。
我的个人习惯是:临时排查问题用 Explore,做固定监控面板时用 Logs Explorer 或者直接把 LogsQL 写进 Panel。不管哪种模式,最终执行的底层都是 LogsQL,只是交互方式不同。
6.2 一套够用的访问日志面板
我做的第一个生产面板就围绕 Nginx 访问日志的核心指标展开,包含这些 Panel:
| 面板 | 数据来源 | 用途 |
|---|---|---|
| 单位时间请求量 | 日志数计数 | 看流量趋势,配合业务发版、活动观察波动 |
| HTTP 状态码分布 | stats by status count() | 快速发现 5xx 抬头 |
| P95 响应时间趋势 | stats quantile(0.95, request_time) | 监控整体服务质量 |
| Top 10 慢接口 | sort by avg(request_time) desc limit 10 | 定位性能瓶颈 |
| 上游节点错误统计 | stats by upstream_addr count if status > 499 | 发现后端异常节点 |
| 热门请求 URI | stats by request_uri count() sort desc limit 10 | 了解流量集中点 |
在 Grafana 里新建 Panel 时,数据源选 VictoriaLogs,查询模式可以用 LogsQL。比如请求量趋势,查询框里直接写:
_time:now-${__range} AND job:nginx-access | stats count() as requests时间变量$__range会自动跟随 Dashboard 右上角的时间选择器,这样同一块面板换个时间范围就能回看历史。这是我比较推荐的做法,不要把时间写死。
6.3 固定数据源 UID 与跨环境迁移
这里要提一个很实际的建议:用 provisioning 方式创建数据源时,给每个数据源固定一个 UID。Grafana 的 Dashboard JSON 里,Panel 是通过 datasource UID 来引用数据源的。如果 UID 随机生成,一旦数据源重新创建或从另一个环境导出导入,旧面板就会集体失效,报出 "datasource was not found" 这类错误。
固定 UID 很简单,看前面 provisioning 配置里的uid: victorialogs-prod。以后不管数据源怎么迁移、重建,只要 UID 不变,面板引用就永远有效。这个习惯真的能帮你省去大量维护 Dashboard 的心力。
7. 生产环境踩坑:升级、时区、字段类型的血泪
7.1 Grafana 报"datasource im7_otuvz was not found"是怎么回事
这个报错我在升级 Grafana 和 VictoriaLogs 插件后遇到过,网上问的人也很多。报错原文是:
failed to upgrade legacy queries datasource im7_otuvz was not found核心原因是:Grafana 在加载 Dashboard 时,发现某个 Panel 引用的数据源 UID 是im7_otuvz,但当前数据源列表里已经找不到这个 UID 了。常见触发场景有:
- 卸载并重装了某个日志数据源插件;
- 把 Grafana 从旧版本升级,旧版的数据源 UID 和插件类型写法不兼容;
- 从其他环境导入了 Dashboard,但原环境的数据源 UID 没同步过来。
解决思路也不是让你去恢复什么遗留数据,而是把面板的引用改到新数据源上。最粗暴但最有效的方式是:
- 在 Grafana 的 Data Sources 页面找到当前可用的 VictoriaLogs 数据源,复制它的 UID(URL 上能看到)。
- 打开报错的 Dashboard,进入 JSON Model。
- 搜索
im7_otuvz,把所有"datasource": {"type": "...", "uid": "im7_otuvz"}替换成新的 UID。 - 保存 Dashboard。
如果面板比较多,直接在 JSON 里全局替换是最快的。如果只有一两个 Panel 坏了,也可以直接删掉旧 Panel 重新建。为了避免以后再犯,记得用 provisioning 把 UID 固定下来,就像 6.3 节说的那样。
7.2 时间戳来源不一致,日志跑到奇奇怪怪的时间线
我有一次查日志,发现明明刚产生的访问日志在 vmui 里搜索不到,但过一阵子又能搜到了。后来发现是时间戳问题:Promtail 没有从日志里提取时间字段,VictoriaLogs 默认用了摄入时间作为日志时间,而 Promtail 的摄入时间又可能因为网络缓冲区、批量推送存在几秒到几十秒的延迟。
如果你希望日志时间反映"请求真实发生的时间",必须让 Promtail 解析日志里的时间字段。这就是 4.4 节配置里那段 timestamp stage 存在的意义。
还有一个容易踩的时区坑:Promtail 的 timestamp stage 在解析$time_local这种不带时区的字符串时,会默认当成 UTC 处理,而 Nginx 的$time_local是服务器本地时间。如果服务器是东八区,解析出来的时间就会慢 8 小时,查询结果看起来很诡异。解决办法就是我在 4.2 节强调的:log_format 里用$time_iso8601,它带完整的时区偏移,Promtail 按 RFC3339 解析就不会错。
7.3 同名字段的类型战争
这个坑最隐蔽,也最折磨人。现象是:同一个字段名 status,在部分日志里是数字 200,在另一部分日志里是字符串 "200",结果查询status:>499时报错,或者统计时结果对不上。
造成这种问题的原因通常有两个:
- Nginx log_format 里某个字段的引号写得不一致:有的 server 块用了旧的 combined 格式,有的用了新的 JSON 格式;
- Promtail/Fluent Bit 在采集端对字段做了处理,比如通过 labels 把值强制变成了字符串。
解决思路也很简单:从源头统一。第一,所有 Nginx 接入平台的日志格式必须一致;第二,采集端不要把数值字段提成标签。如果已经出现一批脏数据,VictoriaLogs 在查询时可以通过类型转换函数处理,但更重要的是把采集链路的规范定住,避免继续产生脏数据。
7.4 VictoriaLogs 自身也要监控
日志平台是给整个团队兜底的,但它自己挂了往往没人及时发现。VictoriaLogs 暴露了/metrics端点,格式兼容 Prometheus/VictoriaMetrics,可以直接接到 Grafana 里监控。我建议至少盯四个指标:
- 进程在线状态:VictoriaLogs 进程挂了要第一时间告警;
- 磁盘使用率:数据目录磁盘满了,写入会直接失败;
- 每日摄入量:看每天的字节数有没有异常突增或突降;
- 查询延迟:监控查询 P99,如果变慢说明要么数据量涨了,要么查询语法不够高效。
如果你已经在跑 Prometheus 或者 VictoriaMetrics,直接加一个 scrape job 指向 VictoriaLogs 的 9428 端口就行。这一步看起来不是"核心功能",但生产环境里的稳定性全靠这些细节点撑住。
最后再分享一个我自己实操中最受益的小习惯:日常临时查日志,我很少先打开 Grafana,而是直接用 VictoriaLogs 自带的 vmui 界面。界面里可以点字段过滤、看时间分布,响应速度快,而且不会占用 Grafana 的并发查询额度。Grafana 更多是留给"固定面板 + 团队共享",vmui 则是个人排障的第一站。这套组合用下来,我们团队查 Nginx 日志的平均耗时从十几分钟降到了一两分钟,这大概就是工具选对了之后的真实体感。