116 lines
4.2 KiB
Bash
Executable File
116 lines
4.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# journal-tail.sh — journald-Logs inkrementell als Influx Line Protocol.
|
|
#
|
|
# Warum dieses Script:
|
|
# * inputs.journal existiert in den offiziellen Telegraf-Builds NICHT
|
|
# (Docker-Image UND InfluxData-Debian-Paket 1.40 geprueft; das Plugin
|
|
# braucht einen Build mit systemd-Headern).
|
|
# * Auf PVE gibt es KEIN rsyslog -> /var/log/syslog existiert dort nicht.
|
|
# * Der JSON-Parser von inputs.exec liest nur EIN Objekt, keine NDJSON-Zeilen.
|
|
# * Line Protocol direkt ist dasselbe bewaehrte Muster wie gpu-rocm.sh.
|
|
#
|
|
# PERFORMANCE: das Parsen laeuft in EINEM awk-Prozess. Eine Vorgaengerversion
|
|
# rief pro Feld ein sed auf (~2500 Prozessstarts bei 500 Zeilen) und riss
|
|
# dadurch Telegrafs exec-Timeout. Keine externen Abhaengigkeiten (kein jq).
|
|
#
|
|
# ZUSTAND: der Cursor steckt in jeder journalctl-JSON-Zeile ("__CURSOR").
|
|
# Er wird am Ende des Laufs in $STATE_DIR/cursor geschrieben -> keine
|
|
# Duplikate, kein Verlust bei Neustart oder Journal-Rotation.
|
|
#
|
|
# Telegraf:
|
|
# [[inputs.exec]]
|
|
# interval = "30s"
|
|
# timeout = "28s"
|
|
# data_format = "influx"
|
|
# commands = ["/etc/telegraf/journal-tail.sh"]
|
|
#
|
|
# Feintuning via /etc/default/telegraf:
|
|
# JOURNAL_PRIORITY=warning warning|err|info|debug
|
|
# JOURNAL_MAX_LINES=500 Obergrenze pro Lauf
|
|
# JOURNAL_BUDGET=15 Sekunden, die journalctl hoechstens lesen darf
|
|
|
|
set -u
|
|
|
|
PRIORITY="${JOURNAL_PRIORITY:-warning}"
|
|
MAX_LINES="${JOURNAL_MAX_LINES:-500}"
|
|
BUDGET="${JOURNAL_BUDGET:-15}"
|
|
SINCE="${JOURNAL_SINCE:-}"
|
|
STATE_DIR="${JOURNAL_STATE_DIR:-/var/lib/telegraf-journal}"
|
|
CURSOR_FILE="$STATE_DIR/cursor"
|
|
|
|
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
|
|
# Wurde noch nie gelesen? Dann nicht die komplette Historie ausschuetten.
|
|
if [ -s "$CURSOR_FILE" ]; then
|
|
START_ARGS=(--after-cursor "$(cat "$CURSOR_FILE" 2>/dev/null)")
|
|
elif [ -n "$SINCE" ]; then
|
|
START_ARGS=(--since "$SINCE")
|
|
else
|
|
START_ARGS=(--since "-5min")
|
|
fi
|
|
|
|
# --no-hostname: den Hostnamen liefert Telegraf als global_tag (node). Der
|
|
# journaleigene Name waere sonst ein zweites, abweichendes Feld.
|
|
timeout -k 2 "$BUDGET" journalctl -o json --no-pager --no-hostname \
|
|
-p "$PRIORITY" -n "$MAX_LINES" "${START_ARGS[@]}" 2>/dev/null \
|
|
| awk -v TMP="$CURSOR_FILE.tmp" '
|
|
# JSON-String-Feld lesen — beruecksichtigt Backslash-Escapes.
|
|
function jget(line, key, pat, p, rest, out, i, n, c) {
|
|
pat = "\"" key "\":\""
|
|
p = index(line, pat)
|
|
if (p == 0) return ""
|
|
rest = substr(line, p + length(pat))
|
|
out = ""; n = length(rest)
|
|
for (i = 1; i <= n; i++) {
|
|
c = substr(rest, i, 1)
|
|
if (c == "\\") { out = out c; i++; if (i <= n) out = out substr(rest, i, 1); continue }
|
|
if (c == "\"") break
|
|
out = out c
|
|
}
|
|
return out
|
|
}
|
|
# Tag-Werte: Komma, Leerzeichen, Gleichheitszeichen escapen
|
|
function etag(s) { gsub(/[ ,=]/, "\\\\&", s); return s }
|
|
# String-Feld: Backslash und Anfuehrungszeichen escapen
|
|
function estr(s) { gsub(/\\/, "\\\\", s); gsub(/"/, "\\\"", s); return s }
|
|
|
|
function emit( cur, msg, unit, ident, prio, ts, lhost, ns) {
|
|
cur = jget($0, "__CURSOR")
|
|
if (cur != "") last = cur
|
|
|
|
msg = jget($0, "MESSAGE")
|
|
if (msg == "") return
|
|
|
|
unit = jget($0, "_SYSTEMD_UNIT"); if (unit == "") unit = jget($0, "SYSLOG_IDENTIFIER")
|
|
ident = jget($0, "SYSLOG_IDENTIFIER")
|
|
prio = jget($0, "PRIORITY")
|
|
ts = jget($0, "__REALTIME_TIMESTAMP")
|
|
lhost = jget($0, "_HOSTNAME")
|
|
|
|
if (unit == "") unit = "unknown"
|
|
if (ident == "") ident = "unknown"
|
|
if (prio == "") prio = "0"
|
|
if (lhost == "") lhost = "unknown"
|
|
|
|
# __REALTIME_TIMESTAMP ist in Mikrosekunden -> Nanosekunden
|
|
ns = (ts ~ /^[0-9]+$/) ? ts "000" : sprintf("%.0f000000000", systime())
|
|
|
|
# Tag heisst jhost, NICHT host: Telegraf setzt host=<node> als global_tag,
|
|
# sonst filtert das Dashboard-Log-Panel nach dem falschen Knoten.
|
|
printf "journal,unit=%s,ident=%s,priority=%s,jhost=%s message=\"%s\" %s\n", \
|
|
etag(unit), etag(ident), etag(prio), etag(lhost), estr(msg), ns
|
|
count++
|
|
}
|
|
|
|
{ if (substr($0, 1, 1) == "{") emit() }
|
|
|
|
END {
|
|
# Cursor einmal am Ende sichern — atomar ersetzt das aufrufende Shell-Skript.
|
|
if (last != "") { printf "%s", last > TMP; close(TMP) }
|
|
}
|
|
'
|
|
|
|
if [ -s "$CURSOR_FILE.tmp" ]; then
|
|
mv -f "$CURSOR_FILE.tmp" "$CURSOR_FILE" 2>/dev/null || true
|
|
fi
|