MENU

Nginx 日志分析平台搭建

• June 10, 2026 • Read: 52 • 编码👨🏻‍💻

Nginx 日志采集与分析:Vector + ClickHouse + Grafana 全流程实战

本文介绍如何通过 Nginx + Vector + ClickHouse + Grafana 搭建一套高性能的 Nginx 日志采集、存储与可视化分析平台。架构上由 Nginx 生成 JSON 格式的访问日志,Vector 实时采集并解析、补充 GeoIP 与 UA 信息,写入 ClickHouse 进行存储与聚合分析,最终由 Grafana 呈现可视化看板。

整体架构

Nginx (生成 JSON 格式日志)
    ↓
Vector (采集、解析、GeoIP、UA 解析)
    ↓
ClickHouse (存储与分析)
    ↓
Grafana (可视化看板)

一、Nginx 配置

Nginx 安装步骤省略

1.1 自定义 JSON 日志格式

Nginx 默认的 combined 日志格式不易被结构化解析,因此我们自定义 main 格式,将每一行访问日志输出为一条 JSON 字符串。日志中包含毫秒级时间戳、服务器/客户端 IP、域名、URL、响应耗时、状态码、UA 等关键字段。

1.2 配置 nginx.conf

vim /home/application/nginx/conf/nginx.conf

http {} 块中增加 maplog_format 配置(建议放在已有的 http {} 顶部位置,避免覆盖其他站点使用的格式):

map "$time_iso8601 # $msec" $time_iso8601_ms { "~(^[^+]+)(\+[0-9:]+) # \d+\.(\d+)$" $1.$3$2; }
log_format main
    '{"timestamp":"$time_iso8601_ms",'
    '"server_ip":"$server_addr",'
    '"remote_ip":"$remote_addr",'
    '"xff":"$http_x_forwarded_for",'
    '"remote_user":"$remote_user",'
    '"domain":"$host",'
    '"url":"$request_uri",'
    '"referer":"$http_referer",'
    '"upstreamtime":"$upstream_response_time",'
    '"responsetime":"$request_time",'
    '"request_method":"$request_method",'
    '"status":"$status",'
    '"response_length":"$bytes_sent",'
    '"request_length":"$request_length",'
    '"protocol":"$server_protocol",'
    '"upstreamhost":"$upstream_addr",'
    '"http_user_agent":"$http_user_agent"'
    '}';
access_log  /home/application/nginx/logs/access.log  main;

说明

  • $time_iso8601_ms 是通过 map 指令拼接出来的时间戳(带毫秒、含时区),后续 Vector 会按此格式解析。
  • $http_x_forwarded_for 多个代理 IP 时是逗号分隔字符串,Vector 会进一步取第一个作为真实客户端 IP。

1.3 重载配置

# 检查配置语法
nginx -t

# 重载配置
nginx -s reload

# 或者使用 systemd
systemctl reload nginx

重载后访问一次站点,确认 /home/application/nginx/logs/access.log 中已经输出 JSON 格式的日志。


二、安装 Docker

Docker 安装步骤省略

三、ClickHouse 部署

3.1 创建部署目录与 docker-compose

mkdir -p /home/application/Database/clickhouse/{data,log}
vim /home/application/Database/clickhouse/docker-compose.yml

docker-compose.yml 内容:

  • 需定义 clickhouse 数据库的密码
services:
  clickhouse:
    #image: clickhouse:24.8.14
    image: docker.cnb.cool/srebro/docker-images-chrom/clickhouse:24.8.14_amd64
    container_name: clickhouse
    restart: always
    environment:
      TZ: Asia/Shanghai
      CLICKHOUSE_USER: 'default'
      CLICKHOUSE_PASSWORD: 'xxxx'
      CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: '1'
    networks:
      - srebro
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - /home/application/Database/clickhouse/log:/var/log/clickhouse-server
      - /home/application/Database/clickhouse/data:/var/lib/clickhouse
      - /etc/localtime:/etc/localtime:ro

networks:
  srebro:
    external: true

3.2 启动 ClickHouse

cd /home/application/Database/clickhouse
docker-compose up -d

3.3 创建数据库与表

进入 ClickHouse 客户端

docker container exec -it clickhouse clickhouse-client --user default --password xxxx

执行创建语句

CREATE DATABASE IF NOT EXISTS nginxlogs ENGINE=Atomic;

CREATE TABLE nginxlogs.nginx_access
(
    `timestamp` DateTime64(3, 'Asia/Shanghai'),
    `server_ip` String,
    `domain` String,
    `request_method` String,
    `status` Int32,
    `top_path` String,
    `path` String,
    `query` String,
    `protocol` String,
    `referer` String,
    `upstreamhost` String,
    `responsetime` Float32,
    `upstreamtime` Float32,
    `duration` Float32,
    `request_length` Int32,
    `response_length` Int32,
    `client_ip` String,
    `client_latitude` Float32,
    `client_longitude` Float32,
    `remote_user` String,
    `remote_ip` String,
    `xff` String,
    `client_city` String,
    `client_region` String,
    `client_country` String,
    `http_user_agent` String,
    `client_browser_family` String,
    `client_browser_major` String,
    `client_os_family` String,
    `client_os_major` String,
    `client_device_brand` String,
    `client_device_model` String,
    `createdtime` DateTime64(3, 'Asia/Shanghai')
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp)
PRIMARY KEY (timestamp,
 server_ip,
 status,
 top_path,
 domain,
 upstreamhost,
 client_ip,
 remote_user,
 request_method,
 protocol,
 responsetime,
 upstreamtime,
 duration,
 request_length,
 response_length,
 path,
 referer,
 client_city,
 client_region,
 client_country,
 client_browser_family,
 client_browser_major,
 client_os_family,
 client_os_major,
 client_device_brand,
 client_device_model
)
TTL toDateTime(timestamp) + toIntervalDay(30)
SETTINGS index_granularity = 8192;

设计要点

  • 引擎:MergeTree,适合大规模日志写入与聚合查询。
  • 分区:按天 toYYYYMMDD(timestamp),便于按天清理和查询。
  • TTL:30 天自动过期,避免日志无限增长。
  • 主键:使用多个常用过滤字段组合,兼顾写入和典型查询。

四、部署 Vector 采集日志

Vector 是一款高性能、可观测性数据管道(由 Datadog/Timber 开发)。这里使用它读取 Nginx 日志,解析 JSON、提取字段、补全 GeoIP 与 UA,然后写入 ClickHouse。

4.1 Vector 部署

创建部署目录与 docker-compose

#创建Vector工作目录
mkdir -p /home/application/vector


#下载最新的GeoLite2库文件
cd /home/application/vector/
wget -O GeoLite2-City.mmdb https://github.com/P3TERX/GeoLite.mmdb/releases/download/2026.06.07/GeoLite2-City.mmdb


vim /home/application/vector/docker-compose.yml

docker-compose.yaml 内容:

services:
  vector:
    #image: timberio/vector:0.56.0-debian
    image: docker.cnb.cool/srebro/docker-images-chrom/timberio-vector:0.56.0-debian_amd64
    container_name: vector
    hostname: vector
    restart: always
    entrypoint: vector --config-dir /etc/vector/conf 
    ports:
      - 8686:8686
    networks:
      - srebro
    volumes:
      - /home/application/nginx/logs:/nginx_logs  # 这是需要采集的日志的路径需要挂载到容器内
      - /home/application/vector/GeoLite2-City.mmdb:/etc/vector/GeoLite2-City.mmdb
      - /home/application/vector/conf:/etc/vector/conf
      - /etc/localtime:/etc/localtime
networks:
  srebro:
    external: true

注意

  • /var/log/nginx 是需要采集的 Nginx 日志目录,请根据实际环境修改。
  • access_vector_error.log 用来记录解析失败的日志,便于排错。
  • GeoLite2-City.mmdb 是 MaxMind 提供的 GeoIP 离线库,用于客户端 IP 城市定位。

4.2 Vector 配置

主配置文件

#创建Vector配置文件目录
mkdir -p /home/application/vector/conf

cat <<-EOF > /home/application/vector/conf/vector.yaml
timezone: "Asia/Shanghai"
api:
  enabled: true
  address: "0.0.0.0:8686"
EOF

Nginx 访问日志解析配置

cat <<-EOF > /home/application/vector/conf/nginx-access.yaml
sources:
  01_file_nginx_access:
    type: file
    include:
      - /nginx_logs/access.log  # 容器内 Nginx 访问日志路径

transforms:
  02_parse_nginx_access:
    drop_on_error: true
    reroute_dropped: true
    type: remap
    inputs:
      - 01_file_nginx_access
    source: |
      .message = string!(.message)
      if contains(.message,"\\x") { .message = replace(.message, "\\x", "\\\\x") }
      . = parse_json!(.message)
      .createdtime = to_unix_timestamp(now(), unit: "milliseconds")
      .timestamp = to_unix_timestamp(parse_timestamp!(.timestamp , format: "%+"), unit: "milliseconds")
      .url_list = split!(.url, "?", 2)
      .path = .url_list[0]
      .query = .url_list[1]
      .path_list = split!(.path, "/", 3)
      if length(.path_list) > 2 {.top_path = join!(["/", .path_list[1]])} else {.top_path = "/"}
      .duration = round(((to_float(.responsetime) ?? 0) - (to_float(.upstreamtime) ?? 0)) ?? 0,3)
      if .xff == "-" { .xff = .remote_ip }
      .client_ip = split!(.xff, ",", 2)[0]
      .ua = parse_user_agent!(.http_user_agent , mode: "enriched")
      .client_browser_family = .ua.browser.family
      .client_browser_major = .ua.browser.major
      .client_os_family = .ua.os.family
      .client_os_major = .ua.os.major
      .client_device_brand = .ua.device.brand
      .client_device_model = .ua.device.model
      .geoip = get_enrichment_table_record("geoip_table", {"ip": .client_ip}) ?? {"city_name":"unknown","region_name":"unknown","country_name":"unknown"}
      .client_city = .geoip.city_name
      .client_region = .geoip.region_name
      .client_country = .geoip.country_name
      .client_latitude = .geoip.latitude
      .client_longitude = .geoip.longitude
      del(.path_list)
      del(.url_list)
      del(.ua)
      del(.geoip)
      del(.url)

sinks:
  03_ck_nginx_access:
    type: clickhouse
    inputs:
      - 02_parse_nginx_access
    endpoint: http://<clickhouse_host>:8123   # ClickHouse HTTP 接口
    database: nginxlogs                         # ClickHouse 库
    table: nginx_access                         # ClickHouse 表
    auth:
      strategy: basic
      user: default
      password: xxxxxxxx                           # ClickHouse 密码
    compression: gzip

enrichment_tables:
  geoip_table:
    path: "/etc/vector/GeoLite2-City.mmdb"
    type: geoip
    locale: "zh-CN"
EOF

关键点说明

  • 01_file_nginx_access 监听文件,Vector 会自动按行读取并保存 offset,重启不会丢数据。
  • 02_parse_nginx_access 使用 VRL 解析 JSON、拆分 URL、计算 duration(自身处理耗时)、补全 UA 与 GeoIP。
  • drop_on_error: true + reroute_dropped: true 让解析失败的日志输出到 04_out_nginx_dropped,不会污染主流程。
  • 03_ck_nginx_access 通过 HTTP 接口将结构化日志写入 ClickHouse,使用 gzip 压缩降低带宽。
  • geoip_table 是 enrichment table,在 VRL 中通过 get_enrichment_table_record 引用。

4.3 运行 Vector

cd /home/application/vector
docker compose up -d
docker logs -f vector

Vector 启动成功后。稍等片刻即可在 ClickHouse 中查到数据:

SELECT count() FROM nginxlogs.nginx_access;

五、Grafana 配置

Grafana安装步骤省略,本节介绍如何安装 ClickHouse 数据源插件、配置数据源以及导入可视化看板。

5.1 安装 ClickHouse 插件

Grafana 官方仓库的 grafana/grafana 镜像默认没有集成 ClickHouse 数据源,需要安装第三方插件 grafana-clickhouse-datasource

# 进入 Grafana 容器
docker container exec -it grafana bash

# 安装 ClickHouse 数据源插件
grafana cli plugins install grafana-clickhouse-datasource

# 退出容器并重启 Grafana
exit
docker restart grafana
注意:部分较新的 Grafana 版本会要求插件带签名,需使用 grafana cli plugins install <plugin> --pluginUrl <url> 或将插件放进 GF_PATHS_PLUGINS 指定目录;如安装失败请检查网络/镜像源。

5.2 增加数据源

登录 Grafana,配置数据源,添加新的数据源

image-20260610155249835

主要填写以下内容:

  • Server addresshttp://<clickhouse_host>:9000
  • Auth:Basic auth

    • Userdefault
    • Password<clickhouse_password>
  • Default databasenginxlogs 默认数据库,一定要填写!!!

image-20260610155359083

填写完成后点击 Save & test,出现绿色 Data source is working 提示即代表连通。

5.3 导入看板

我们使用准备好的 ClickHouse + Nginx 请求日志分析看板进行导入:

  1. 进入 DashboardsImport
  2. 导入仪表板,id 为:22037
  3. 选择目标数据源(上面新增的 ClickHouse 数据源)。
  4. 点击 Import 完成导入。

image-20260610155753767

5.4 看板预览

该看板基于 ClickHouse + Vector 的 NGINX 请求日志分析看板,包括 请求与耗时分析、异常请求分析、用户分析、地理位置分布图、指定接口分析、请求日志明细。尤其在异常请求分析方面,总结多年异常请求分析经验,从各个角度设计了大量异常请求的分析图表。

image-20260610155927487


image-20260610160009293

参考文档

Archives Tip
QR Code for this page
Tipping QR Code