Quickly Visualize Nginx Access Logs with Loki and Grafana
This guide shows how to collect Nginx access logs, convert them to JSON, store them in Loki, and create real‑time dashboards in Grafana, covering installation, configuration, Docker deployment, and required plugins for a complete monitoring solution.
A client needed to view website access statistics without using Google or Baidu analytics, so the solution is to collect Nginx logs and display them via Loki and Grafana.
For a small site, the stack chosen is
Nginx + Promtail + Loki + Grafana.
Promtail and Loki are installed by downloading the appropriate binary files, extracting them, and starting them with a configuration file.
<code>server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx
pipeline_stages:
- replace:
expression: '(?:[0-9]{1,3}\.){3}([0-9]{1,3})'
replace: '***'
static_configs:
- targets:
- localhost
labels:
job: nginx_access_log
host: expatsxxxxs
agent: promtail
__path__: /var/log/nginx/expatshxxxxs.access.log</code>The Nginx log format is changed to JSON to make it compatible with Loki:
<code>log_format json_analytics escape=json '{'
"msec": "$msec",
"connection": "$connection",
"connection_requests": "$connection_requests",
"pid": "$pid",
"request_id": "$request_id",
"request_length": "$request_length",
"remote_addr": "$remote_addr",
"remote_user": "$remote_user",
"remote_port": "$remote_port",
"time_local": "$time_local",
"time_iso8601": "$time_iso8601",
"request": "$request",
"request_uri": "$request_uri",
"args": "$args",
"status": "$status",
"body_bytes_sent": "$body_bytes_sent",
"bytes_sent": "$bytes_sent",
"http_referer": "$http_referer",
"http_user_agent": "$http_user_agent",
"http_x_forwarded_for": "$http_x_forwarded_for",
"http_host": "$http_host",
"server_name": "$server_name",
"request_time": "$request_time",
"upstream": "$upstream_addr",
"upstream_connect_time": "$upstream_connect_time",
"upstream_header_time": "$upstream_header_time",
"upstream_response_time": "$upstream_response_time",
"upstream_response_length": "$upstream_response_length",
"upstream_cache_status": "$upstream_cache_status",
"ssl_protocol": "$ssl_protocol",
"ssl_cipher": "$ssl_cipher",
"scheme": "$scheme",
"request_method": "$request_method",
"server_protocol": "$server_protocol",
"pipe": "$pipe",
"gzip_ratio": "$gzip_ratio",
"http_cf_ray": "$http_cf_ray",
"geoip_country_code": "$geoip_country_code"
}';</code>Because the JSON log format uses the GeoIP module, the required packages are installed with:
<code>yum -y install GeoIP GeoIP-data GeoIP-devel</code>Nginx is then recompiled with the
--with-http_geoip_moduleflag and reloaded using
kill -USR2. After recompilation, the new
log_formatworks without errors.
Sample JSON log entries are shown to confirm correct formatting.
Grafana is deployed quickly via Docker:
<code>docker run -d -p 3000:3000 grafana/grafana</code>After logging in with the default admin/admin credentials and resetting the password, a Loki data source is added.
Using Grafana’s Explore view confirms that logs are being stored in Loki.
A dashboard is imported by ID, but the world‑map panel is missing, so the required plugin is installed:
<code>grafana-cli plugins install grafana-worldmap-panel</code>Grafana is restarted, and the dashboard now displays the map (external map images may need a reverse proxy to load).
In summary, combining Loki and Grafana provides a fast and effective way to present detailed website access information.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.