A web-based monitoring dashboard designed to track QGIS Server request performance across multiple server pools. Parses QGIS Server logs in real time, records every GetMap request with its response time, project, user, and layer information, and stores everything in SQLite for long-term analysis. Helps you identify slow projects, heavy users, peak-hour bottlenecks, and per-pool performance differences — so you can find the culprits behind slow map rendering.
System metrics (CPU, RAM) are collected as a secondary data source for correlation.
Note: This project has been developed and tested with Py-QGIS-Server. If you are running a "pure" QGIS Server setup (e.g. spawned via FastCGI/Apache), the log format and service unit names may differ — small adjustments to the pool configuration and log-parsing patterns in
monitor.pywill be needed.
- Per-pool response times — Live average, P95, min/max across configurable time windows (10 min, 30 min, 1 h, 24 h)
- Slowest requests — Rolling top-5 slowest
GetMaprequests per pool with project, user, and layer details - Historical analytics — Performance trends, peak-hour request volume, day-of-week patterns, per-project rankings, pool comparison
- Request history — Searchable log of individual requests with filters for project, pool, and time range
- User & project breakdowns — Most active users, slowest projects, request counts
- Configure any number of pools — Just edit the
QGIS_POOLSdictionary; the entire dashboard (cards, tables, charts, filters) adapts automatically - Pool comparison chart — Side-by-side response time comparison across all configured pools
- Print layout tracking —
GetPrintandGetPrintAtlasrequests are logged with the layout name (TEMPLATE) and the layers used, stored in separate columns - Lizmap WFS-T editing — Edits made through QGIS Desktop (WFS Transaction with INSERT/UPDATE/DELETE in the request) are detected and logged with action type and layer name
- Lizmap edit form detection — When Lizmap opens an edit form for a layer that has constrained fields, the
SERVICE=EXPRESSIONcall is detected and logged as an edit form open event (deduplicated: one entry per user/layer within 30 seconds). Note: layers without any constrained fields produce no QGIS Server log activity and cannot be detected this way. - Feature info & GetFeature —
GetFeatureInfoandGetFeaturerequests are tracked per user, project, and layer
- Admin / anonymous toggles — Include or exclude admin and anonymous users from all statistics (default: excluded)
- German / English — Full UI localization with language switcher, persisted in browser
- Hide usernames — Toggle next to the language switcher blurs all usernames across the entire dashboard (useful for presentations); state is persisted in browser
- SQLite storage — Persistent data with configurable retention (default: 180 days for requests, 30 days for system metrics)
- Log rotation handling — Supports both
createandcopytruncaterotation methods - Live system metrics — CPU, memory usage pushed via WebSocket (secondary to request performance)
- PHP-FPM monitoring — Tracks errors and warnings from PHP-FPM logs
- Single-page dashboard — Charts (Chart.js) with real-time updates (Socket.IO)
Live Monitor — per-pool response times & slowest requests:
Request History — individual requests, most active users & projects:
Analytics — performance trends, peak hours, project rankings, pool comparison:
System History (CPU & RAM):
Deep Usage insight:
The monitoring dashboard relies on detailed QGIS Server log output. Make sure debug-level logging and request profiling are enabled.
Py-QGIS-Server — in your server.conf:
[logging]
level = debugQGIS Server environment — in your qgis-service.env (or equivalent systemd EnvironmentFile):
QGIS_SERVER_LOG_LEVEL=0
QGIS_SERVER_LOG_PROFILE=TRUEQGIS Server logs grow very fast at debug level. Set up logrotate to keep them under control:
sudo nano /etc/logrotate.d/qgis-server/var/log/qgis/qgis-server.log {
daily
rotate 2
copytruncate
compress
delaycompress
missingok
notifempty
dateext
dateformat -%Y%m%d
}
If you run multiple pools, add a similar entry for each pool log file (e.g.
qgis-server-pool2.log,qgis-server-pool3.log).
git clone https://github.com/meyerlor/qgis-server-monitoring.git
cd qgis-server-monitoringpip install flask flask-socketio eventlet psutilOpen monitor.py and edit the CONFIGURATION section at the top of the file. The most important settings are:
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Listen address |
PORT |
8080 |
Listen port |
DB_PATH |
/opt/monitoring/monitoring.db |
SQLite database location |
REQUEST_RETENTION_DAYS |
180 |
Days to keep request data |
SYSTEM_METRICS_RETENTION_DAYS |
30 |
Days to keep system metrics |
DEBUG_LOG |
/var/log/monitoring-debug.log |
Debug log path (None to disable) |
QGIS_POOLS |
3 pools | Dict of pool name → service unit + log file |
PHP_FPM_SERVICE_UNIT |
php8.3-fpm.service |
systemd unit for PHP-FPM |
PHP_FPM_LOG_FILE |
/var/log/php8.3-fpm.log |
PHP-FPM log path |
Edit the QGIS_POOLS dictionary. Each entry maps a pool name to its systemd service unit and log file path. The dashboard adapts automatically — no frontend changes needed.
QGIS_POOLS = {
'qgis-pool1': {
'service_unit': 'qgis.service',
'log_file': '/var/log/qgis/qgis-server.log',
},
# Add or remove pools as needed — the dashboard will
# generate cards, tables, filters, and charts dynamically.
}# Create database directory
sudo mkdir -p /opt/monitoring
sudo chown $USER:$USER /opt/monitoring
# Create debug log (optional)
sudo touch /var/log/monitoring-debug.log
sudo chown $USER:$USER /var/log/monitoring-debug.logpython3 monitor.pyThen open http://<your-server-ip>:8080 in a browser.
For a production setup it is recommended to run the dashboard as a systemd service.
# /etc/systemd/system/monitoring-dashboard.service
[Unit]
Description=QGIS Server Monitoring Dashboard
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/monitoring
ExecStart=/usr/bin/python3 /opt/monitoring/monitor.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now monitoring-dashboard.servicesudo systemctl stop monitoring-dashboard.service
sudo systemctl start monitoring-dashboard.service
sudo systemctl restart monitoring-dashboard.serviceCheck the service status and logs:
sudo systemctl status monitoring-dashboard.service
sudo journalctl -u monitoring-dashboard.service -fIt is strongly recommended to put the dashboard behind Nginx with password protection and SSL/TLS enabled.
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd-monitoring adminsudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.example.comserver {
server_name your-domain.example.com;
# HTTP Basic Auth
auth_basic "GIS Monitoring Dashboard";
auth_basic_user_file /etc/nginx/.htpasswd-monitoring;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# WebSocket support (required for live updates)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for long-lived connections
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
}
# SSL configuration will be added by Certbot
listen 443 ssl;
# ssl_certificate /etc/letsencrypt/live/your-domain.example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your-domain.example.com/privkey.pem;
}
server {
listen 80;
server_name your-domain.example.com;
return 301 https://$host$request_uri;
}Replace
your-domain.example.comwith your actual domain. After runningcertbot --nginx, the SSL certificate paths will be filled in automatically.
The monitoring process needs read access to the QGIS and PHP-FPM log files. Either run it as a user that has access, or add the service user to the appropriate group:
sudo usermod -aG adm www-dataUSE AT YOUR OWN RISK. This software is provided "as is", without warranty of any kind. See the LICENSE file for details.