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

  1. Ist die Aufbewahrung gesetzt UND der Compactor mit retention_enabled: true aktiv?
  2. Beginnen alle Abfragen mit einem Label-Filter?
  3. Sind die Labels bewusst gewählt (unter 100 Werte je Label)?
  4. Gibt es keine Kennungen (Benutzer, Anfrage-IDs, Adressen) als Label?
  5. Sind die Labelnamen mit Prometheus abgestimmt, damit Verknüpfungen funktionieren?
  6. Ist das Speicherbackend für ein Wachstum über Monate ausgelegt?
  7. Sind Ratenlimits gesetzt, damit ein Anwendungsfehler mit endloser Protokollausgabe („Protokollschleife“) Loki nicht flutet?
  8. Ist die Größe des Protokollspeichers auf einem Dashboard sichtbar?

Häufige Fehler

  1. 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.
  2. Aufbewahrung ohne Compactor. Siehe oben – die Protokolle bleiben für immer.
  3. Abfragen ohne Label. Ein globaler Scan über Monate ist weder schnell noch sinnvoll.
  4. 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.
  5. 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.
  6. 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.
  7. Keine Protokollrotation auf den Quellen. Promtail merkt sich die Leseposition – wird eine Datei rotiert, bevor alles gelesen wurde, gehen Zeilen verloren.
  8. 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.