nginxpulse 轻量级 Nginx 访问日志分析与可视化面板

轻量级 Nginx 访问日志分析与可视化面板,提供实时统计、PV 过滤、IP 归属地与客户端解析。

项目地址
项目文档

项目开发技术栈

  • 后端:Go 1.24.x · Gin · Logrus
  • 数据:PostgreSQL (pgx)
  • IP 归属地:ip2region(本地库) + ip-api.com(远程批量)
  • 前端:Vue 3 · Vite · TypeScript · PrimeVue · ECharts/Chart.js · Scss
  • 容器:Docker / Docker Compose · Nginx(前端静态部署)

支持本地日志和远端日志
同一站点可接入多个来源(多台机器/多目录/多桶并行)。

远端日志支持三种接入方式:

  1. HTTP 服务暴露日志(自己部署或用 Nginx/Apache)
  2. SFTP 直连拉取(无需额外 HTTP 服务)
  3. 对象存储(S3/OSS)(上传/归档到对象存储

docker-compose 部署

# cat docker-compose.yml 
version: "3.8"
services:
  nginxpulse:
    image: magiccoders/nginxpulse:latest
    container_name: nginxpulse
    ports:
      - "8088:8088"
      - "8089:8089"
    volumes:
      - ./configs:/app/configs
      - ./logs:/share/log/nginx/
      - ./var/nginxpulse_data:/app/var/nginxpulse_data
      - ./var/pgdata:/app/var/pgdata
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped

配置文件仅供参考,可以不配置使用默认的:

# cat configs/nginxpulse_config.json 
{
  "system": {
    "logDestination": "file",
    "taskInterval": "1m",
    "logRetentionDays": 30,
    "parseBatchSize": 100,
    "ipGeoCacheLimit": 1000000,
    "ipGeoApiUrl": "http://ip-api.com/batch",
    "demoMode": false,
    "accessKeys": [],
    "language": "zh-CN"
  },
  "server": {
    "Port": ":8089"
  },
  "database": {
    "driver": "postgres",
    "dsn": "postgres://nginxpulse:nginxpulse@127.0.0.1:5432/nginxpulse?sslmode=disable",
    "maxOpenConns": 10,
    "maxIdleConns": 5,
    "connMaxLifetime": "30m"
  },
  "websites": [
    { 
      "name": "log-access",
      "logPath": "/share/log/nginx/access.log",
      "domains": [
        "log-access"
      ]
    },
    {
      "name": "slb-access",
      "logPath": "/share/log/nginx/slb-access.log",
      "domains": [
        "slb-access"
      ],
      "logType": "nginx",
      "logRegex": "^(\\S+) - (?P<user>\\S+) \\[(?P<time>[^\\]]+)\\] \"(?P<method>\\S+) (?P<url>[^\"]+) HTTP/\\d\\.\\d\" (?P<status>\\d+) (?P<bytes>\\d+) \"(?P<referer>[^\"]*)\" \"(?P<ua>[^\"]*)\" \"(?P<ip>\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})[^\"]*\" (\\S+) (\\S+)$"
    },
    {
    "name": "proj-access",
    "domains": ["proj-access"],
    "logType": "nginx",
    "logRegex": "^(\\S+) - (?P<user>\\S+) \\[(?P<time>[^\\]]+)\\] \"(?P<method>\\S+) (?P<url>[^\"]+) HTTP/\\d\\.\\d\" (?P<status>\\d+) (?P<bytes>\\d+) \"(?P<referer>[^\"]*)\" \"(?P<ua>[^\"]*)\" \"(?P<ip>\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})[^\"]*\" (\\S+) (\\S+)$",
    "sources": [
      {
        "id": "proja-logs",
        "type": "local",
        "path": "/share/log/nginx/proja/proj.access.log",
        "compression": "auto"
      },
      {
        "id": "projb-logs",
        "type": "local",
        "pattern": "/share/log/nginx/projb/proj.access*",
        "compression": "auto"
      }
    ]
   }
  ],
  "pvFilter": {
    "statusCodeInclude": [
      200
    ],
    "excludePatterns": [
      "favicon.ico$",
      "robots.txt$",
      "sitemap.xml$",
      "^/health$",
      "^/_(?:nuxt|next)/",
      "rss.xml$",
      "feed.xml$",
      "atom.xml$"
    ],
    "excludeIPs": [
      "127.0.0.1",
      "::1"
    ]
  }
}