Grafana Loki ist ein Protokollspeicher, der bewusst anders arbeitet als Elasticsearch: Er indiziert nur die Beschriftungen (Labels), nicht den Inhalt. Das macht ihn deutlich günstiger im Betrieb und passt hervorragend zu Umgebungen, die bereits Prometheus und Grafana nutzen.
Die Grundidee
Elasticsearch indiziert den vollstaendigen Text
-> Jedes Wort ist suchbar, aber der Index ist oft
groesser als die Daten selbst. Speicher- und
Rechenaufwand entsprechend hoch.
Loki indiziert nur die Labels
-> Der Inhalt wird komprimiert in Bloecken abgelegt.
Der Index ist winzig. Die Suche im Inhalt laeuft
ueber den Strom ("stream") mit, nicht ueber einen Index.
-> Konsequenz: Suche ist schnell, wenn man den Strom
eingrenzt ("alle Protokolle von app=api in env=prod
der letzten Stunde"), und langsam, wenn man ohne
Einschraenkung global sucht.
Daraus folgt die wichtigste Regel:
IMMER mit Label-Filtern beginnen, dann den Inhalt durchsuchen.
LogQL, das ohne Label beginnt, ist ein Vollscan.
Aufbau
Anwendungen -> Promtail / Alloy / Fluent Bit -> Loki -> Grafana
Promtail Agent, liest Dateien und Systemjournal,
haengt Labels an und schickt sie an Loki
Loki Speichert und indiziert Labels, beantwortet LogQL
Grafana Abfrage und Darstellung, verknuepft mit Metriken
ueber gemeinsame Labels
Speicher:
Speicher-Backend fuer Bloecke (Dateisystem, S3, MinIO)
Index-Backend (boltdb-shipper / tsdb - als Dateien oder Objektspeicher)
Loki konfigurieren
# /etc/loki/config.yml (Einzelinstanz)
auth_enabled: false
server:
http_listen_port: 3100
log_level: warn
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2026-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
# Aufbewahrung - PFLICHT, sonst waechst der Speicher unbegrenzt
retention_period: 30d
# Schutz vor zu vielen Stroemen (siehe unten)
max_streams_per_user: 10000
max_query_series: 500
# Zeilenlimit pro Abfrage
max_entries_limit_per_query: 5000
# Ratenlimit je Mandant
ingestion_rate_mb: 8
ingestion_burst_size_mb: 16
compactor:
working_directory: /var/lib/loki/compactor
retention_enabled: true # sonst wird NICHTS geloescht!
delete_request_store: filesystem
Die Komponente compactor mit retention_enabled: true ist nicht optional. Ohne sie wird retention_period ignoriert und die Datenmenge wächst unbegrenzt – ein häufiger und teurer Fehler.
Promtail: Protokolle einsammeln
# /etc/promtail/config.yml
server:
http_listen_port: 9080
positions:
filename: /var/lib/promtail/positions.yaml # merkt sich die Leseposition
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Systemjournal
- job_name: journal
journal:
labels:
job: systemd-journal
max_age: 12h
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: unit
- source_labels: ['__journal__hostname']
target_label: host
# Protokolldateien einer Anwendung
- job_name: app
static_configs:
- targets: [localhost]
labels:
job: api
app: api
env: prod
__path__: /srv/api/log/*.log
# Docker-Container (alle)
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
relabel_configs:
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: container
# WICHTIG: Labels bewusst waehlen!
# GUT: app, env, level, host, job, unit
# -> viele Protokolle teilen dieselben Werte
# SCHLECHT: request_id, user_id, timestamp, pfad, ip
# -> jede Zeile wird ein eigener Strom
# -> "Kardinalitaetsexplosion", Loki wird unbenutzbar
#
# Faustregel: Ein Label soll weniger als etwa 100 verschiedene
# Werte haben. Alles andere gehoert in den Inhalt, nicht in ein Label.
LogQL: Abfragen
{app="api"} einfachster Strom
{app="api", env="prod"} mehrere Labels
{app=~"api|worker"} regulaerer Ausdruck
{namespace="prod"} |= "error" enthaelt "error"
{app="api"} |= "timeout" != "retry" enthaelt, aber nicht
{app="api"} |~ "fehler|Fehler|ERROR" regulaerer Ausdruck
{job="systemd-journal", unit="nginx.service"}
# JSON-Protokolle auswerten
{app="api"} | json
{app="api"} | json | status >= 500
{app="api"} | json | level="error" | line_format "{{.method}} {{.path}} -> {{.status}}"
# Muster zerlegen (ohne JSON)
{app="nginx"} | pattern ` - - [<_>] " <_>" `
{app="nginx"} | pattern `<_>` | status >= 500
# Zaehlen und gruppieren - das ist der eigentliche Nutzen
sum(count_over_time({app="api"} |= "error" [5m])) by (level)
sum by (status) (count_over_time({app="nginx"} | pattern `<_>` | status >= 500 [1h]))
# Fehlerquote im Verhaeltnis zu allen Anfragen
sum(rate({app="api"} | json | status >= 500 [5m]))
/
sum(rate({app="api"} [5m]))
# Haeufigste Fehlermeldungen
topk(10, sum by (message) (count_over_time({app="api"} | json | level="error" [1h])))
# Ausgabe als Messwert fuer ein Diagramm
sum(rate({app="nginx"} | pattern `<_>` | status=~"5.." [5m])) by (path)
# Messwerte direkt aus Protokollen (ohne Metrik-Instrumentierung)
quantile_over_time(0.95,
{app="api"} | json | unwrap duration_ms | __error__="" [5m]) by (path)
Alarme aus Protokollen
# In Loki hinterlegte Regel (oder in Grafana Alerting)
groups:
- name: protokoll_alarme
rules:
- alert: VieleFehler
expr: |
sum(rate({app="api"} | json | level="error" [5m])) > 1
for: 5m
labels: {severity: warning}
annotations:
summary: "Mehr als 1 Fehler/s in app=api"
- alert: AnmeldefehlerHaeufung
expr: |
sum(count_over_time({app="api"} |= "authentication failed" [10m])) > 20
for: 0m
labels: {severity: critical}
annotations:
summary: "Haeufige Anmeldefehler - moeglicher Angriff"
Grafana-Verknüpfung: der eigentliche Gewinn
Loki und Prometheus teilen Labels. Damit lassen sich in Grafana Panels verknüpfen: Ein Klick auf einen Ausschlag in einem Metrik-Diagramm öffnet genau die Protokolle dieses Zeitraums für diese Instanz.
# Datenquelle Loki in Grafana mit abgeleiteten Feldern
# und einer Verknuepfung zur Metrik-Datenquelle:
# Panel -> Datenverknuepfung -> "Zugehoerige Protokolle anzeigen"
#
# Voraussetzung sind die RICHTIGEN Labels:
# Prometheus: up{job="api", instance="10.0.0.11:8080"}
# Loki: {app="api", host="10.0.0.11"}
# -> Die Labels muessen einander zuordenbar sein.
# Wer zufaellige Labels vergibt, verliert genau diesen Nutzen.
Prüfliste
- Ist die Aufbewahrung gesetzt UND der Compactor mit
retention_enabled: trueaktiv? - Beginnen alle Abfragen mit einem Label-Filter?
- Sind die Labels bewusst gewählt (unter 100 Werte je Label)?
- Gibt es keine Kennungen (Benutzer, Anfrage-IDs, Adressen) als Label?
- Sind die Labelnamen mit Prometheus abgestimmt, damit Verknüpfungen funktionieren?
- Ist das Speicherbackend für ein Wachstum über Monate ausgelegt?
- Sind Ratenlimits gesetzt, damit ein Anwendungsfehler mit endloser Protokollausgabe („Protokollschleife“) Loki nicht flutet?
- Ist die Größe des Protokollspeichers auf einem Dashboard sichtbar?
Häufige Fehler
- Kennungen als Label. Der teuerste Fehler: Tausende Ströme, der Index explodiert, Loki wird langsam und bricht zusammen. Alles, was variabel ist, gehört in den Inhalt.
- Aufbewahrung ohne Compactor. Siehe oben – die Protokolle bleiben für immer.
- Abfragen ohne Label. Ein globaler Scan über Monate ist weder schnell noch sinnvoll.
- Zu hohe Erfassungsrate. Eine Anwendung, die im Fehlerfall 5.000 Zeilen pro Sekunde schreibt, füllt den Speicher in Stunden. Ratenlimits und eine Protokollstufe, die nicht debug in Produktion fährt.
- Protokolle als einzige Quelle der Wahrheit. Loki ist zum Durchsuchen gebaut, nicht als Archiv. Wer Protokolle langfristig aufbewahren muss (Aufbewahrungspflichten), braucht zusätzlich eine kalte Ablage.
- Loki mit Elasticsearch verwechseln. Volltextsuche über alles ist nicht das Ziel. Wer das braucht, ist mit Elasticsearch besser bedient; wer korrelierte Auswertung mit Metriken braucht, mit Loki.
- Keine Protokollrotation auf den Quellen. Promtail merkt sich die Leseposition – wird eine Datei rotiert, bevor alles gelesen wurde, gehen Zeilen verloren.
- Kein Test der Wiederherstellung. Wer Protokolle für die Fehlersuche braucht, muss wissen, dass sie bei einem Neustart vollständig sind.
Verwandte Grundlagen: Logging, Elasticsearch im Betrieb, Monitoring mit Prometheus und Grafana.