Files
netbird-telegraf-installer/telegraf/apache.conf
T
mm 3236d9cede feat(apache): Error-Logs erfassen + drei stille Fallen behoben (VERSION 1.3.0)
Bisher wurden NUR access.log gesammelt — real nachgemessen: 31 logfile-Labels,
0 davon error.log. Bei einem KeyHelp-Host mit PHP/CMS fehlten damit genau die
Fehler, fuer die man ins Monitoring schaut.

Error-Logs jetzt in einem EIGENEN Input (name_override=apache_error), roh nach
Loki, mit Tags logtype=error und logfile=<pfad>. Getrennt vom Access-Grok, weil
das Format nicht passt:
  [Mon Sep 14 00:10:01.123456 2026] [core:error] [pid 9] AH00124: ...
Liefen sie durch den Access-Parser, entstuenden keine Metriken, aber der
Aggregator wuerde sie in leere Gruppen einordnen.

DREI STILLE FALLEN dabei gefunden und behoben:
1. processors.rename hatte namepass=["journal"] — apache_error behielt das
   Feld value, jede Panel-Query mit {.message} haette es nicht gefunden.
2. outputs.loki namepass enthielt apache_error nicht — Input sammelt ein,
   Output verwirft, keine Fehlermeldung, Panels leer (wie bei journal damals).
3. outputs.prometheus namedrop ebenso — sonst wandern Textzeilen in die TSDB.

Verifiziert gegen echte KeyHelp-Formate: access=2 Metriken, error=4 Rohzeilen,
Feld heisst message, 0 Error-Zeilen als Metrik.
2026-09-14 10:03:13 +02:00

170 lines
8.7 KiB
ApacheConf

# Apache: Access-Logs → Loki (roh) UND Request-Metriken → Prometheus
#
# Zwei Wege aus einem Log — sie ergaenzen sich:
# inputs.tail : jede Zeile roh nach Loki (fuer die Fehlersuche im Einzelfall)
# inputs.logparser: dieselben Zeilen als Metriken (Request-Rate, Status-Klassen,
# Antwortzeit) — das ist, was Panels und Alerts brauchen
#
# Die Logs landen NICHT in Prometheus: der Remote-Write-Output hat
# `namedrop = ["docker_log", "journal"]` — hier kommt `apache_log` dazu.
#
# FALLE: `from_beginning` existiert bei inputs.tail NICHT (weder im Docker-Image
# noch im InfluxData-Paket 1.40). Ein solcher Block laesst den Dienst beim Start
# hart scheitern: "plugin inputs.tail: configuration specified the fields
# [from_beginning]" → systemctl start schlaegt fehl, ALLE Metriken aus.
# Stattdessen: initial_read_offset = "saved-or-end" (Default), damit beim ersten
# Lauf nicht die komplette Historie ausgeschuettet wird.
# --------------------------------------------------------------------------
# 1) Roh-Logs → Loki
# --------------------------------------------------------------------------
# ${APACHE_FILES} rendert zu einer echten TOML-Liste — der Installer baut sie
# aus allen gefundenen Logpfaden (Panel-Hoster haben pro VHost eigene Dateien).
# Ein einzelner String mit Leerzeichen ("/a.log /b.log") waere ein ungueltiger
# Pfad und der Input liefe still leer.
[[inputs.tail]]
files = ${APACHE_FILES}
name_override = "journal"
# Beim Erstanlauf nur Neues lesen; danach Position merken.
initial_read_offset = "saved-or-end"
data_format = "value"
data_type = "string"
# Vereinheitlicht den Feldnamen auf `message` — das Logs-Panel in Grafana
# erwartet es; `value` wuerde als value="…" erscheinen.
path_tag = "logfile"
[inputs.tail.tags]
source = "apache"
# Vereinheitlicht den Feldnamen auf `message` — fuer JEDEN Log-Input.
# `apache_error` muss mit drin sein: sonst heisst das Feld dort `value` und
# jede Panel-/Alert-Query mit `| logfmt | line_format "{{.message}}"` findet
# die Error-Logs nicht. Das faellt nicht auf, solange niemand danach sucht.
[[processors.rename]]
namepass = ["journal", "apache_error"]
order = 1
[[processors.rename.replace]]
field = "value"
dest = "message"
# --------------------------------------------------------------------------
# 2) Error-Logs → Loki (NUR roh, KEINE Metriken)
# --------------------------------------------------------------------------
# Error-Logs haben ein anderes Format als Access-Logs:
# [Mon Sep 14 00:10:01.123456 2026] [core:error] [pid 9] AH00124: ...
# Der Access-Grok weiter unten kann das nicht lesen. Liefen sie durch ihn,
# gaebe es keine Metriken — aber der Aggregator wuerde sie in leere Gruppen
# einordnen. Deshalb ein EIGENER Input, der sie unveraendert nach Loki schickt.
#
# Eigener Messungsname `apache_error` statt `journal`: so sind sie in Loki
# eindeutig filterbar, und das Logs-Panel kann sie getrennt zeigen.
[[inputs.tail]]
files = ${APACHE_ERROR_FILES}
name_override = "apache_error"
initial_read_offset = "saved-or-end"
data_format = "value"
data_type = "string"
path_tag = "logfile"
# Der Remote-Write-Output laesst Logs per namedrop draussen; Loki nimmt sie.
[inputs.tail.tags]
source = "apache"
logtype = "error"
# --------------------------------------------------------------------------
# 3) Request-Metriken (Common/Combined Log Format) → Prometheus
# --------------------------------------------------------------------------
# Common Log Format:
# 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
# VHost-Apache (Debian-Default) stellt den Hostnamen voran:
# example.com:80 127.0.0.1 - - [10/Oct/2000:13:55:36 -0700] "GET / HTTP/1.0" 200 2326
[[inputs.tail]]
files = ${APACHE_FILES}
name_override = "apache_access"
initial_read_offset = "saved-or-end"
data_format = "grok"
# Korrekte Namen der Telegraf-Grok-Sammlung: COMMON_LOG_FORMAT und
# COMBINED_LOG_FORMAT — NICHT HTTPD_* (das sind andere Muster, Telegraf kennt
# sie nicht: "no pattern found for %{HTTPD_COMBINED_LOG_FORMAT}").
# Reihenfolge: spezifischste zuerst — VHost-Varianten vor der einfachen Form.
# EIGENES MUSTER statt %{COMBINED_LOG_FORMAT} — wegen eines realen Ausfalls.
#
# WARUM EIN EIGENES MUSTER: das eingebaute Muster vergibt dem Log-Zeitfeld den
# Namen `ts`. Telegraf nimmt einen erkannten Zeitstempel dann als SAMPLE-ZEIT,
# und die Log-Zeile hat nur SEKUNDEN-Aufloesung. Mit `log_time` findet Telegraf
# kein Zeitfeld mehr und stempelt mit eigener Uhr. Die Log-Zeit bleibt als
# Feld erhalten — praktisch, weil Remote Write nur MILLISEKUNDEN kann.
#
# DER EIGENTLICHE FEHLER liegt tiefer, und ein Zeitstempel-Trick loest ihn
# NICHT: Telegraf schreibt pro Sammelzyklus einen Batch mit EINEM Zeitstempel.
# Treffen zwei Requests derselben Serie (path+resp_code+verb) in dieselbe
# Millisekunde, entsteht "duplicate sample ... overrides not allowed".
# Prometheus weist den Batch ab, Telegraf wiederholt ihn ENDLOS — der Batch
# sitzt im FIFO-Kopf fest und der Node liefert danach GAR NICHTS mehr, auch
# kein system_uptime. Erkennungsmerkmal: derselbe eingefrorene Timestamp
# wiederholt sich alle ~15 s im Prometheus-Log.
#
# DIE LOESUNG ist deshalb der Aggregator weiter unten: er fasst alle
# Metriken einer Serie pro Intervall zu EINEM Punkt zusammen. Pro Serie und
# Intervall gibt es dann genau einen Punkt — eine Kollision ist strukturell
# unmoeglich, unabhaengig von der Last. Gemessen an 2000 echten x22-Zeilen:
# ohne Aggregator ~2000 Einzelpunkte, mit Aggregator 4 Punkte, 0 Konflikte.
grok_custom_patterns = '''
APACHE_LINE (?:%{HOSTNAME:vh}(?::%{NUMBER:vport})? )?%{IPORHOST:client_ip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:log_time}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:http_version})?|-)" %{NUMBER:resp_code:int} (?:%{NUMBER:resp_bytes:int}|-)(?: "%{DATA:referrer}" "%{DATA:agent}")?
'''
grok_patterns = ["%{APACHE_LINE}"]
grok_timezone = "UTC"
# Kein Tag-Wildwuchs: nur das Noetige als Tag, der Rest als Feld.
[inputs.tail.tags]
source = "apache"
# resp_code und verb waren im eingebauten Muster TAGS. Das eigene Muster
# liefert sie als Felder — ohne diesen Schritt haetten die Grafana-Panels,
# die nach resp_code filtern, plotzlich keine Daten mehr.
[[processors.converter]]
namepass = ["apache_access"]
order = 1
[processors.converter.fields]
tag = ["resp_code", "verb"]
# --------------------------------------------------------------------------
# Aggregation: der eigentliche Schutz gegen Zeitstempel-Kollisionen
# --------------------------------------------------------------------------
# WARUM DAS NOETIG IST (realer Ausfall, nicht Theorie):
# Telegraf schreibt pro Sammelzyklus einen Batch, dessen Metriken ALLE denselben
# Zeitstempel tragen. Prometheus' Remote Write kann nur Millisekunden. Treffen
# zwei Requests derselben Serie (path + resp_code + verb) in dieselbe
# Millisekunde, antwortet Prometheus:
# "duplicate sample for timestamp ...; overrides not allowed"
# Er verwirft damit den GANZEN Batch. Telegrafs Puffer ist FIFO: der abgelehnte
# Batch bleibt am Kopf stehen und wird endlos wiederholt — die Node liefert
# danach gar nichts mehr, auch keine Systemmetriken. Merkmal im Log: derselbe
# eingefrorene Timestamp alle ~15 s.
#
# Der Aggregator loest das an der Wurzel: er fasst alle Metriken einer Serie pro
# Intervall zu EINEM Punkt zusammen. Damit gibt es pro Serie und Intervall genau
# einen Wert — eine Kollision ist strukturell unmoeglich, egal wie hoch die Last
# ist. Zusaetzlich sinkt die Serienzahl deutlich, was Prometheus entlastet.
#
# Was dabei entsteht (gemessen an 2000 echten x22-Zeilen, 4 Gruppen):
# apache_access_resp_bytes_count — Requests je Gruppe (das brauchen die Panels)
# apache_access_resp_bytes_sum — uebertragene Bytes je Gruppe
# apache_access_resp_bytes_mean — mittlere Antwortgroesse
# Die Felder `resp_code` und `verb` sind Tags und bleiben damit Gruppenschluessel.
#
# `drop_original = true` ist zwingend: ohne das schickt Telegraf die Einzelwerte
# ZUSAETZLICH und die Kollision waere wieder da.
[[aggregators.basicstats]]
namepass = ["apache_access"]
period = "60s"
drop_original = true
stats = ["count", "sum", "mean"]
# --------------------------------------------------------------------------
# 3) mod_status (optional, auskommentiert)
# --------------------------------------------------------------------------
# Liefert Auslastung (BusyWorkers, ReqPerSec) — nuetzlich, braucht aber
# `a2enmod status`, `ExtendedStatus On` und eine lokale Freigabe von
# /server-status in der Apache-Config.
#
# [[inputs.apache]]
# urls = ["http://127.0.0.1/server-status?auto"]