# 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" [[processors.rename]] namepass = ["journal"] order = 1 [[processors.rename.replace]] field = "value" dest = "message" # -------------------------------------------------------------------------- # 2) 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"]