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.
This commit is contained in:
mm committed 2026-09-14 10:03:13 +02:00
1 parent 6d0d0bd900
commit 3236d9cede
4 files changed
+68 -11

No files matched your search

+1 -1
View File
@@ -1 +1 @@
1.2.0 1.3.0
+30 -6
View File
@@ -303,7 +303,12 @@ if [ "$HAS_APACHE" -eq 1 ] && [ -z "$APACHE_LOG" ]; then
"/home/*/logs/*/access.log" \ "/home/*/logs/*/access.log" \
"/var/www/vhosts/*/logs/access_log" \ "/var/www/vhosts/*/logs/access_log" \
"/usr/local/apache/domlogs/*" \ "/usr/local/apache/domlogs/*" \
"/var/log/httpd/*access*.log"; do "/var/log/httpd/*access*.log" \
"/var/log/apache2/*/error.log" \
"/home/users/*/logs/*/error.log" \
"/home/*/logs/*/error.log" \
"/var/log/apache2/error.log" \
"/var/log/httpd/*error*.log"; do
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ls $_pat >/dev/null 2>&1; then if ls $_pat >/dev/null 2>&1; then
_pats="$_pats $_pat" _pats="$_pats $_pat"
@@ -313,7 +318,8 @@ if [ "$HAS_APACHE" -eq 1 ] && [ -z "$APACHE_LOG" ]; then
# /var/log/apache2/access.log vorhanden, aber 0 Bytes — ein Input darauf # /var/log/apache2/access.log vorhanden, aber 0 Bytes — ein Input darauf
# liefert still nichts. # liefert still nichts.
for _p in /var/log/apache2/access.log /var/log/httpd/access_log \ for _p in /var/log/apache2/access.log /var/log/httpd/access_log \
/var/log/apache2/other_vhosts_access.log; do /var/log/apache2/other_vhosts_access.log \
/var/log/apache2/error.log /var/log/httpd/error_log; do
[ -s "$_p" ] && _pats="$_pats $_p" [ -s "$_p" ] && _pats="$_pats $_p"
done done
# Nichts gefunden? Dann die CustomLog-Direktive befragen (verlaesslichste # Nichts gefunden? Dann die CustomLog-Direktive befragen (verlaesslichste
@@ -331,7 +337,9 @@ if [ "$HAS_APACHE" -eq 1 ] && [ -z "$APACHE_LOG" ]; then
# Letzter Ausweg: breiter Scan der ueblichen Wurzelverzeichnisse. # Letzter Ausweg: breiter Scan der ueblichen Wurzelverzeichnisse.
if [ -z "$_pats" ]; then if [ -z "$_pats" ]; then
_pats="$(find /var/log/apache2 /var/log/httpd /home -maxdepth 4 \ _pats="$(find /var/log/apache2 /var/log/httpd /home -maxdepth 4 \
-name 'access.log' -o -name 'access_log' 2>/dev/null | head -200 | tr '\n' ' ')" \( -name 'access.log' -o -name 'access_log' \
-o -name 'error.log' -o -name 'error_log' \) 2>/dev/null \
| head -200 | tr '\n' ' ')"
fi fi
[ -n "$_pats" ] && APACHE_LOG="$(printf '%s' "$_pats" | sed 's/^ *//; s/ *$//')" [ -n "$_pats" ] && APACHE_LOG="$(printf '%s' "$_pats" | sed 's/^ *//; s/ *$//')"
[ -n "$APACHE_LOG" ] || APACHE_LOG="/var/log/apache2/access.log" [ -n "$APACHE_LOG" ] || APACHE_LOG="/var/log/apache2/access.log"
@@ -352,12 +360,28 @@ if [ "$HAS_APACHE" -eq 1 ] || [ "$ROLE" = "apache" ]; then
# feste Einzelpfade und jede NEUE Domain fiele heraus, bis der Installer # feste Einzelpfade und jede NEUE Domain fiele heraus, bis der Installer
# erneut laeuft. Wir wollen die Muster in der Conf, Telegraf loest sie bei # erneut laeuft. Wir wollen die Muster in der Conf, Telegraf loest sie bei
# jedem Lauf selbst auf. # jedem Lauf selbst auf.
# TRENNUNG access/error — wichtig fuer die Metriken:
# Der Grok-Parser versteht nur das ACCESS-Format
# IP ident auth [HTTPDATE] "VERB REQ" CODE BYTES
# Eine Error-Zeile sieht anders aus:
# [Mon Sep 14 00:10:01.123456 2026] [core:error] [pid 9] AH00124: ...
# Laeuft sie durch den Access-Parser, entstehen keine Metriken (Muster passt
# nicht) — schlimmer: der Aggregator zaehlt sie in Gruppen mit, die es nicht
# gibt. Deshalb bekommt der Access-Input nur Access-Pfade, und die Error-Logs
# gehen ueber einen eigenen Input roh nach Loki.
set -f set -f
for _p in $APACHE_LOG; do for _p in $APACHE_LOG; do
APACHE_FILES="${APACHE_FILES:+$APACHE_FILES, }\"$_p\"" case "$_p" in
*error*|*Error*)
APACHE_ERROR_FILES="${APACHE_ERROR_FILES:+$APACHE_ERROR_FILES, }\"$_p\"" ;;
*)
APACHE_FILES="${APACHE_FILES:+$APACHE_FILES, }\"$_p\"" ;;
esac
done done
set +f set +f
[ -n "$APACHE_FILES" ] || APACHE_FILES='"/var/log/apache2/access.log"' [ -n "$APACHE_FILES" ] || APACHE_FILES='"/var/log/apache2/access.log"'
APACHE_ERROR_FILES="[${APACHE_ERROR_FILES:-\"/var/log/apache2/error.log\"}]"
export APACHE_ERROR_FILES
APACHE_FILES="[$APACHE_FILES]" APACHE_FILES="[$APACHE_FILES]"
fi fi
# Sicherheitsnetz: die Variable MUSS nicht-leer sein, bevor apache.conf gerendert # Sicherheitsnetz: die Variable MUSS nicht-leer sein, bevor apache.conf gerendert
@@ -625,9 +649,9 @@ fetch_conf() {
fetch "$1" "$2.tmp" fetch "$1" "$2.tmp"
load_defaults load_defaults
if command -v envsubst >/dev/null; then if command -v envsubst >/dev/null; then
envsubst '${NODE_NAME} ${PROM_URL} ${LOKI_URL} ${TELEGRAF_TOKEN} ${PVE_TOKEN} ${PVE_NODE} ${PVE_URL} ${APACHE_LOG} ${APACHE_FILES}' < "$2.tmp" > "$2" envsubst '${NODE_NAME} ${PROM_URL} ${LOKI_URL} ${TELEGRAF_TOKEN} ${PVE_TOKEN} ${PVE_NODE} ${PVE_URL} ${APACHE_LOG} ${APACHE_FILES} ${APACHE_ERROR_FILES}'
else else
subst_vars "$2.tmp" "$2" NODE_NAME PROM_URL LOKI_URL TELEGRAF_TOKEN PVE_TOKEN PVE_NODE PVE_URL APACHE_LOG APACHE_FILES subst_vars "$2.tmp" "$2" NODE_NAME PROM_URL LOKI_URL TELEGRAF_TOKEN PVE_TOKEN PVE_NODE PVE_URL APACHE_LOG APACHE_FILES APACHE_ERROR_FILES
fi fi
if grep -q '\${' "$2"; then if grep -q '\${' "$2"; then
log "WARNUNG: $2 enthaelt noch unaufgeloeste Platzhalter:" log "WARNUNG: $2 enthaelt noch unaufgeloeste Platzhalter:"
+29 -2
View File
@@ -35,15 +35,42 @@
[inputs.tail.tags] [inputs.tail.tags]
source = "apache" 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]] [[processors.rename]]
namepass = ["journal"] namepass = ["journal", "apache_error"]
order = 1 order = 1
[[processors.rename.replace]] [[processors.rename.replace]]
field = "value" field = "value"
dest = "message" dest = "message"
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# 2) Request-Metriken (Common/Combined Log Format) → Prometheus # 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: # Common Log Format:
# 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 # 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
+8 -2
View File
@@ -17,7 +17,10 @@
url = "${PROM_URL}" url = "${PROM_URL}"
data_format = "prometheusremotewrite" data_format = "prometheusremotewrite"
timeout = "10s" timeout = "10s"
namedrop = ["docker_log", "apache_log", "journal"] # Roh-Logs gehoeren NICHT in Prometheus — sie sind Text, keine Zeitreihen.
# `apache_error` hier ergaenzen, sonst versucht der Remote-Write-Output
# Textzeilen als Metriken zu senden.
namedrop = ["docker_log", "apache_log", "journal", "apache_error"]
[outputs.http.headers] [outputs.http.headers]
Content-Type = "application/x-protobuf" Content-Type = "application/x-protobuf"
Content-Encoding = "snappy" Content-Encoding = "snappy"
@@ -29,7 +32,10 @@
domain = "${LOKI_URL}" domain = "${LOKI_URL}"
endpoint = "/loki/api/v1/push" endpoint = "/loki/api/v1/push"
timeout = "10s" timeout = "10s"
namepass = ["docker_log", "apache_log", "journal"] # `apache_error` MUSS hier stehen: ohne den Namen sammelt der Input die
# Error-Logs ein und dieser Output verwirft sie wieder — kein Fehler, keine
# Meldung, die Panels bleiben leer. Derselbe stille Fehler wie bei `journal`.
namepass = ["docker_log", "apache_log", "journal", "apache_error"]
sanitize_label_names = true sanitize_label_names = true
metric_name_label = "" metric_name_label = ""
http_headers = { "Authorization" = "Bearer ${TELEGRAF_TOKEN}" } http_headers = { "Authorization" = "Bearer ${TELEGRAF_TOKEN}" }