Date : 15-01-2026 Status : IMPLEMENTEE Priorité : HAUTE Estimation : ~4h
Grafana ne voit que 3 sources de logs (containers Docker prod-portainer via Promtail).
| Source | Type | État actuel |
|---|---|---|
| PVE | Système + crons | Non collecté |
| Proxmox 155 | Système | Non collecté |
| nginx | Access + error logs | Fichiers locaux seulement |
| gitlab | Application logs | Non collecté |
| dev-portainer | Containers DEV | Non collecté |
| jellyfin | Application | Non collecté |
| vscode | IDE logs | Non collecté |
| win11 | Windows events | Non collecté |
| Crons (tous) | Exécution | Syslog local seulement |
┌─────────────────────────────────────────────────────────────────────────────┐
│ SOURCES │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ PVE Proxmox155 nginx prod-portainer dev-portainer │
│ │ │ │ │ │ │
│ └──────────────┴─────────────┴────────────────┴─────────────────┘ │
│ │ │
│ [Promtail agents] │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ LOKI │ (prod-portainer:3100) │
│ │ Hot storage │ Rétention: 7 jours │
│ └──────────────┘ │
│ │ │
│ Sync quotidien │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ O2SWITCH │ Cold storage │
│ │ ~/logs/ │ Rétention: 90 jours │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ GRAFANA │ Visualisation │
│ │ Loki + Logs │ Hot (7j) + Cold (90j) │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
labels:
env: "prod" | "dev" # Environnement
host: "pve" | "nginx" | ... # Machine source
type: "system" | "app" | "cron" | "access" | "error"
service: "docker" | "nginx" | "gitlab" | "zfs" | ...
job: "promtail-{host}" # Identifiant collecteur
| Source | Labels |
|---|---|
| Nginx access | env=prod, host=nginx, type=access, service=nginx |
| Docker container | env=prod, host=prod-portainer, type=app, service=ntfy |
| Cron PVE | env=prod, host=pve, type=cron, service=rsync-backup |
| Système PVE | env=prod, host=pve, type=system, service=syslog |
| GitLab app | env=prod, host=gitlab, type=app, service=gitlab |
~/logs/
├── prod/
│ ├── pve/
│ │ ├── system/
│ │ │ └── 2026-01-15.log.gz
│ │ └── cron/
│ │ └── 2026-01-15.log.gz
│ ├── nginx/
│ │ ├── access/
│ │ │ └── 2026-01-15.log.gz
│ │ └── error/
│ │ └── 2026-01-15.log.gz
│ ├── prod-portainer/
│ │ ├── docker/
│ │ │ └── 2026-01-15.log.gz
│ │ └── containers/
│ │ ├── ntfy-2026-01-15.log.gz
│ │ ├── loki-2026-01-15.log.gz
│ │ └── ...
│ └── gitlab/
│ └── app/
│ └── 2026-01-15.log.gz
└── dev/
└── dev-portainer/
└── ...
Format fichier : {service}-{YYYY-MM-DD}.log.gz ou {YYYY-MM-DD}.log.gz
# /etc/promtail/config.yml
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://192.168.1.12:3100/loki/api/v1/push
scrape_configs:
# Syslog système
- job_name: pve-system
static_configs:
- targets: [localhost]
labels:
env: prod
host: pve
type: system
service: syslog
__path__: /var/log/syslog
pipeline_stages:
- regex:
expression: '^(?P<timestamp>\w+\s+\d+\s+[\d:]+)\s+(?P<hostname>\S+)\s+(?P<process>[^:]+):\s+(?P<message>.*)$'
# Crons
- job_name: pve-cron
static_configs:
- targets: [localhost]
labels:
env: prod
host: pve
type: cron
service: cron
__path__: /var/log/cron.log
# Monitoring script
- job_name: pve-monitoring
static_configs:
- targets: [localhost]
labels:
env: prod
host: pve
type: cron
service: monitoring-33800
__path__: /var/log/monitoring-33800.log
# ZFS events
- job_name: pve-zfs
static_configs:
- targets: [localhost]
labels:
env: prod
host: pve
type: system
service: zfs
__path__: /var/log/zfs*.log
# /etc/promtail/config.yml
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://192.168.1.12:3100/loki/api/v1/push
scrape_configs:
# Access logs globaux
- job_name: nginx-access
static_configs:
- targets: [localhost]
labels:
env: prod
host: nginx
type: access
service: nginx
__path__: /var/log/nginx/access.log
# Error logs globaux
- job_name: nginx-error
static_configs:
- targets: [localhost]
labels:
env: prod
host: nginx
type: error
service: nginx
__path__: /var/log/nginx/error.log
# Logs par domaine (wildcard)
- job_name: nginx-domains
static_configs:
- targets: [localhost]
labels:
env: prod
host: nginx
type: access
service: nginx-domains
__path__: /var/log/nginx/*.access.log
pipeline_stages:
- regex:
source: filename
expression: '/var/log/nginx/(?P<domain>[^.]+)\..*\.access\.log'
- labels:
domain:
# /etc/promtail/config.yml
scrape_configs:
- job_name: gitlab-app
static_configs:
- targets: [localhost]
labels:
env: prod
host: gitlab
type: app
service: gitlab
__path__: /var/log/gitlab/**/*.log
# /etc/promtail/config.yml
scrape_configs:
- job_name: proxmox155-system
static_configs:
- targets: [localhost]
labels:
env: dev
host: proxmox155
type: system
service: syslog
__path__: /var/log/syslog
# Même config que prod-portainer mais avec env: dev
scrape_configs:
- job_name: dev-docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
relabel_configs:
- source_labels: [__meta_docker_container_name]
target_label: container
- replacement: dev
target_label: env
- replacement: dev-portainer
target_label: host
- replacement: app
target_label: type
Modifier /stock_8to/33800-stack/docker/stacks/monitoring/promtail-config.yml :
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: docker-containers
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
relabel_configs:
# Nom du container
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: container
# Stack (compose project)
- source_labels: ['__meta_docker_container_label_com_docker_compose_project']
target_label: stack
# Service (compose service)
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: service
# Labels fixes
- replacement: prod
target_label: env
- replacement: prod-portainer
target_label: host
- replacement: app
target_label: type
pipeline_stages:
- json:
expressions:
log: log
stream: stream
time: time
- timestamp:
source: time
format: RFC3339Nano
- output:
source: log
#!/bin/bash
# /stock_8to/33800-stack/scripts/logs/sync-logs-o2switch.sh
set -e
DATE=$(date -d "yesterday" +%Y-%m-%d)
LOKI_URL="http://192.168.1.12:3100"
O2SWITCH="nowhere84@ssh-nowhere84.o2switch.net"
REMOTE_DIR="~/logs"
TMP_DIR="/tmp/logs-export"
mkdir -p "$TMP_DIR"
# Fonction export par query
export_logs() {
local query="$1"
local output="$2"
local start="${DATE}T00:00:00Z"
local end="${DATE}T23:59:59Z"
curl -s -G "$LOKI_URL/loki/api/v1/query_range" \
--data-urlencode "query=$query" \
--data-urlencode "start=$start" \
--data-urlencode "end=$end" \
--data-urlencode "limit=100000" \
| jq -r '.data.result[].values[][1]' \
> "$TMP_DIR/$output"
if [ -s "$TMP_DIR/$output" ]; then
gzip "$TMP_DIR/$output"
echo "Exported: $output.gz"
fi
}
# Export par source
export_logs '{env="prod",host="pve",type="system"}' "prod/pve/system/$DATE.log"
export_logs '{env="prod",host="pve",type="cron"}' "prod/pve/cron/$DATE.log"
export_logs '{env="prod",host="nginx",type="access"}' "prod/nginx/access/$DATE.log"
export_logs '{env="prod",host="nginx",type="error"}' "prod/nginx/error/$DATE.log"
export_logs '{env="prod",host="prod-portainer",type="app"}' "prod/prod-portainer/docker/$DATE.log"
export_logs '{env="prod",host="gitlab",type="app"}' "prod/gitlab/app/$DATE.log"
export_logs '{env="dev",host="dev-portainer"}' "dev/dev-portainer/$DATE.log"
# Sync vers O2switch
rsync -avz --mkpath "$TMP_DIR/" "$O2SWITCH:$REMOTE_DIR/"
# Cleanup
rm -rf "$TMP_DIR"
echo "Logs $DATE synced to O2switch"
# /etc/cron.d/sync-logs-o2switch
0 2 * * * gouroubleu /stock_8to/33800-stack/scripts/logs/sync-logs-o2switch.sh >> /var/log/sync-logs.log 2>&1
ssh $O2SWITCH "mkdir -p ~/logs/{prod,dev}/{pve,nginx,prod-portainer,gitlab,proxmox155,dev-portainer}/{system,cron,access,error,app,docker}"
Modifier loki-config.yml :
limits_config:
retention_period: 168h # 7 jours (au lieu de 720h/30j)
compactor:
retention_enabled: true
retention_delete_delay: 2h
Avantage : Loki stocke 7 jours en "hot", O2switch stocke 90 jours en "cold".
Panels :
$env: prod, dev
$host: pve, nginx, prod-portainer, gitlab, dev-portainer, proxmox155
$type: system, app, cron, access, error
$service: (dynamique depuis labels)
# Tous les logs d'un host
{host="$host"}
# Erreurs seulement
{type="error"} |= "error" or |= "ERROR" or |= "Error"
# Crons échoués
{type="cron"} |= "failed" or |= "error"
# Nginx 5xx
{host="nginx", type="access"} |~ " 5[0-9]{2} "
# Logs d'un container spécifique
{host="prod-portainer", container="ntfy"}
| Action | Fichier | Machine |
|---|---|---|
| Créer | /etc/promtail/config.yml | PVE |
| Créer | /etc/promtail/config.yml | nginx |
| Créer | /etc/promtail/config.yml | gitlab |
| Créer | /etc/promtail/config.yml | proxmox155 |
| Créer | /etc/promtail/config.yml | dev-portainer |
| Modifier | promtail-config.yml | prod-portainer (stack monitoring) |
| Modifier | loki-config.yml | prod-portainer (rétention 7j) |
| Créer | sync-logs-o2switch.sh | PVE |
| Créer | /etc/cron.d/sync-logs-o2switch | PVE |
| Créer | Structure ~/logs/ | O2switch |
| Créer | Dashboard "Logs Overview" | Grafana |
| Métrique | Avant | Après |
|---|---|---|
| Sources dans Grafana | 3 | 15+ |
| Machines monitorées | 1 | 6 |
| Rétention hot (Loki) | 30j | 7j |
| Rétention cold (O2switch) | 0 | 90j |
| Stockage local logs | ~1.2 GB | ~300 MB |
| Archivage distant | Non | Oui |