Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
Executable
+755
@@ -0,0 +1,755 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# Script Name: rsyslog-metrics-exporter.sh
|
||||
# Version: 1.0
|
||||
# Description: Prometheus exporter for rsyslog internal metrics via impstats
|
||||
# JSON output. Exports queue depths, action success/failure,
|
||||
# input message counts, process resource usage, and overall
|
||||
# rsyslog health metrics.
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# Website: https://mylinux.work
|
||||
# License: MIT
|
||||
#
|
||||
# Prerequisites:
|
||||
# - rsyslog with impstats module enabled (JSON output to file)
|
||||
# - jq (JSON parser)
|
||||
# - netcat (nc) for HTTP mode
|
||||
# - Standard Unix tools (awk, grep, tail)
|
||||
#
|
||||
# Performance:
|
||||
# The stats file is read once per collection cycle — the last occurrence
|
||||
# of each named object is extracted in a single pass using jq.
|
||||
# Typical run time: under one second.
|
||||
#
|
||||
# Usage:
|
||||
# # One-shot output to stdout
|
||||
# ./rsyslog-metrics-exporter.sh --once
|
||||
#
|
||||
# # Textfile collector mode (daemon, writes every COLLECTION_INTERVAL)
|
||||
# ./rsyslog-metrics-exporter.sh --daemon --textfile
|
||||
#
|
||||
# # HTTP server mode
|
||||
# ./rsyslog-metrics-exporter.sh --http -p 9199
|
||||
#
|
||||
# Metrics Exported:
|
||||
# Core Status:
|
||||
# - rsyslog_up - rsyslog process status (1=running, 0=down)
|
||||
# - rsyslog_info{version} - rsyslog version info
|
||||
#
|
||||
# Queue Metrics:
|
||||
# - rsyslog_messages_total - Total messages processed
|
||||
# - rsyslog_queue_size{queue} - Current queue depth
|
||||
# - rsyslog_queue_enqueued_total{queue} - Messages enqueued
|
||||
# - rsyslog_queue_dequeued_total{queue} - Messages dequeued
|
||||
# - rsyslog_queue_full_total{queue} - Times queue was full
|
||||
# - rsyslog_queue_max_size{queue} - Configured max queue size
|
||||
# - rsyslog_queue_disk_usage_bytes{queue} - Disk-assisted queue usage
|
||||
#
|
||||
# Action Metrics:
|
||||
# - rsyslog_action_processed_total{action} - Messages processed per action
|
||||
# - rsyslog_action_failed_total{action} - Failed action attempts
|
||||
# - rsyslog_action_suspended{action} - 1 if action is suspended
|
||||
# - rsyslog_action_resumed_total{action} - Times action resumed
|
||||
#
|
||||
# Input Metrics:
|
||||
# - rsyslog_input_received_total{input} - Messages received per input
|
||||
#
|
||||
# Process Metrics:
|
||||
# - rsyslog_process_memory_bytes - RSS memory of rsyslog
|
||||
# - rsyslog_process_open_fds - Open file descriptors
|
||||
#
|
||||
# Exporter Health:
|
||||
# - rsyslog_exporter_duration_seconds - Script execution time
|
||||
# - rsyslog_exporter_last_run_timestamp - Last run timestamp
|
||||
# - rsyslog_exporter_success - 1 if collection succeeded
|
||||
#
|
||||
# Configuration (environment variables):
|
||||
# NODE_DIR - Textfile collector directory (default: /var/lib/node_exporter)
|
||||
# STATS_FILE - impstats JSON output file (default: /var/log/rsyslog-stats.log)
|
||||
# COLLECTION_INTERVAL - Seconds between collections in daemon mode (default: 60)
|
||||
# HTTP_PORT - HTTP server port (default: 9199)
|
||||
# DEBUG - Set to any value to enable debug output
|
||||
#
|
||||
# impstats Configuration:
|
||||
# Add to /etc/rsyslog.d/impstats.conf:
|
||||
# module(load="impstats"
|
||||
# interval="60"
|
||||
# severity="7"
|
||||
# log.syslog="off"
|
||||
# log.file="/var/log/rsyslog-stats.log"
|
||||
# format="json"
|
||||
# )
|
||||
#
|
||||
################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION VARIABLES
|
||||
# ============================================================================
|
||||
|
||||
NODE_DIR="${NODE_DIR:-/var/lib/node_exporter}"
|
||||
STATS_FILE="${STATS_FILE:-/var/log/rsyslog-stats.log}"
|
||||
COLLECTION_INTERVAL="${COLLECTION_INTERVAL:-60}"
|
||||
HTTP_PORT="${HTTP_PORT:-9199}"
|
||||
DEBUG="${DEBUG:-}"
|
||||
|
||||
OUTPUT_FILE=""
|
||||
HTTP_MODE=false
|
||||
DAEMON_MODE=false
|
||||
ONCE_MODE=false
|
||||
TEMP_FILE=""
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
Export rsyslog impstats metrics as Prometheus metrics (v1.0).
|
||||
|
||||
MODES:
|
||||
--once Run once and output to stdout (default)
|
||||
--textfile Write to node_exporter textfile collector
|
||||
--daemon Run continuously (use with --textfile)
|
||||
--http Run HTTP server on port $HTTP_PORT
|
||||
|
||||
OPTIONS:
|
||||
-p, --port HTTP port (default: $HTTP_PORT)
|
||||
-o, --output Output file path
|
||||
--check Verify impstats is configured and show setup instructions
|
||||
-h, --help Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
NODE_DIR Textfile collector dir (default: /var/lib/node_exporter)
|
||||
STATS_FILE impstats output file (default: /var/log/rsyslog-stats.log)
|
||||
COLLECTION_INTERVAL Daemon interval in seconds (default: 60)
|
||||
HTTP_PORT HTTP server port (default: 9199)
|
||||
DEBUG Enable debug output (unset by default)
|
||||
|
||||
EXAMPLES:
|
||||
$0 --once # One-shot output to stdout
|
||||
$0 --textfile # Write to textfile collector (once)
|
||||
$0 --daemon --textfile # Continuous textfile collector mode
|
||||
$0 --http --port 9199 # Run HTTP server
|
||||
$0 --check # Verify impstats configuration
|
||||
$0 -o /tmp/rsyslog.prom # Write to custom file
|
||||
|
||||
METRICS:
|
||||
- Queue depths, enqueue/dequeue counters, full events
|
||||
- Per-action processed/failed/suspended counts
|
||||
- Per-input received message counts
|
||||
- rsyslog process memory and file descriptor usage
|
||||
- Total messages processed
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help) show_usage ;;
|
||||
--once) ONCE_MODE=true; shift ;;
|
||||
--textfile) OUTPUT_FILE="$NODE_DIR/rsyslog_metrics.prom"; shift ;;
|
||||
--daemon) DAEMON_MODE=true; shift ;;
|
||||
--http) HTTP_MODE=true; shift ;;
|
||||
--check) check_impstats_config; exit 0 ;;
|
||||
-p|--port) HTTP_PORT="$2"; shift 2 ;;
|
||||
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
debug_log() {
|
||||
[ -n "$DEBUG" ] && echo "DEBUG: $*" >&2
|
||||
}
|
||||
|
||||
# Verify that impstats is configured and the stats file exists
|
||||
# Shows suggested configuration if not found
|
||||
check_impstats_config() {
|
||||
echo "Checking rsyslog impstats configuration..." >&2
|
||||
echo "" >&2
|
||||
|
||||
# Check rsyslog is installed
|
||||
if ! command -v rsyslogd >/dev/null 2>&1; then
|
||||
echo "ERROR: rsyslogd not found in PATH" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local version
|
||||
version=$(rsyslogd -v 2>/dev/null | head -1 || echo "unknown")
|
||||
echo "rsyslog: $version" >&2
|
||||
|
||||
# Check if rsyslog is running
|
||||
if pidof rsyslogd >/dev/null 2>&1; then
|
||||
echo "Status: running (PID $(pidof rsyslogd))" >&2
|
||||
else
|
||||
echo "Status: NOT running" >&2
|
||||
fi
|
||||
|
||||
# Check stats file
|
||||
if [ -f "$STATS_FILE" ]; then
|
||||
echo "Stats file: $STATS_FILE (exists, $(wc -l < "$STATS_FILE") lines)" >&2
|
||||
echo "" >&2
|
||||
|
||||
# Verify it contains JSON
|
||||
if tail -1 "$STATS_FILE" 2>/dev/null | jq . >/dev/null 2>&1; then
|
||||
echo "OK: Stats file contains valid JSON" >&2
|
||||
else
|
||||
echo "WARNING: Stats file does not contain valid JSON" >&2
|
||||
echo " Ensure impstats is configured with format=\"json\"" >&2
|
||||
fi
|
||||
else
|
||||
echo "Stats file: $STATS_FILE (NOT FOUND)" >&2
|
||||
echo "" >&2
|
||||
echo "impstats does not appear to be configured." >&2
|
||||
echo "Add the following to /etc/rsyslog.d/impstats.conf:" >&2
|
||||
echo "" >&2
|
||||
cat <<CONF
|
||||
module(load="impstats"
|
||||
interval="60"
|
||||
severity="7"
|
||||
log.syslog="off"
|
||||
log.file="$STATS_FILE"
|
||||
format="json"
|
||||
)
|
||||
CONF
|
||||
echo "" >&2
|
||||
echo "Then restart rsyslog: systemctl restart rsyslog" >&2
|
||||
fi
|
||||
|
||||
# Check jq
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
echo "jq: installed ($(jq --version 2>/dev/null))" >&2
|
||||
else
|
||||
echo "jq: NOT FOUND (required)" >&2
|
||||
echo " Install with: apt install jq OR yum install jq" >&2
|
||||
fi
|
||||
|
||||
# Check node_exporter textfile dir
|
||||
if [ -d "$NODE_DIR" ]; then
|
||||
echo "Textfile dir: $NODE_DIR (exists)" >&2
|
||||
else
|
||||
echo "Textfile dir: $NODE_DIR (not found, create for --textfile mode)" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean up temp files on exit
|
||||
cleanup() {
|
||||
[ -n "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
|
||||
TEMP_FILE=""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# DATA COLLECTION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Check if rsyslog is running
|
||||
# Returns: 0 if running, 1 if not
|
||||
is_rsyslog_running() {
|
||||
pidof rsyslogd >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Get rsyslog version string
|
||||
# Returns: version string (e.g., "8.2312.0")
|
||||
get_rsyslog_version() {
|
||||
local version_line
|
||||
version_line=$(rsyslogd -v 2>/dev/null | head -1)
|
||||
# Extract version number from lines like "rsyslogd 8.2312.0 (aka 2023.12)"
|
||||
echo "$version_line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1
|
||||
}
|
||||
|
||||
# Extract the latest stats for each unique object name from the impstats file
|
||||
# The stats file grows over time; we only want the most recent entry per object.
|
||||
# Args: $1 - origin filter (e.g., "core.queue", "core.action")
|
||||
# Returns: JSON lines, one per unique object name (latest occurrence wins)
|
||||
get_latest_stats() {
|
||||
local origin_filter="$1"
|
||||
|
||||
if [ ! -f "$STATS_FILE" ] || [ ! -r "$STATS_FILE" ]; then
|
||||
debug_log "Stats file not readable: $STATS_FILE"
|
||||
return
|
||||
fi
|
||||
|
||||
# Read the last 500 lines (covers several collection cycles) and
|
||||
# extract the last occurrence of each named object with the given origin
|
||||
tail -500 "$STATS_FILE" 2>/dev/null | \
|
||||
jq -c --arg origin "$origin_filter" \
|
||||
'select(.origin == $origin)' 2>/dev/null | \
|
||||
jq -s -c 'group_by(.name) | map(last) | .[]' 2>/dev/null
|
||||
}
|
||||
|
||||
# Get the total messages processed from the main queue stats
|
||||
# Returns: total enqueued count from "main Q"
|
||||
get_total_messages() {
|
||||
if [ ! -f "$STATS_FILE" ] || [ ! -r "$STATS_FILE" ]; then
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
|
||||
local val
|
||||
val=$(tail -500 "$STATS_FILE" 2>/dev/null | \
|
||||
jq -c 'select(.origin == "core.queue" and .name == "main Q")' 2>/dev/null | \
|
||||
tail -1 | jq -r '.enqueued // 0' 2>/dev/null)
|
||||
echo "${val:-0}"
|
||||
}
|
||||
|
||||
# Get rsyslog process RSS memory in bytes from /proc
|
||||
# Returns: RSS in bytes, or 0 if unavailable
|
||||
get_process_memory() {
|
||||
local pid
|
||||
pid=$(pidof rsyslogd 2>/dev/null) || { echo "0"; return; }
|
||||
|
||||
# Use the first PID if multiple
|
||||
pid=${pid%% *}
|
||||
|
||||
if [ -f "/proc/$pid/status" ]; then
|
||||
local vmrss_kb
|
||||
vmrss_kb=$(awk '/^VmRSS:/ {print $2}' "/proc/$pid/status" 2>/dev/null)
|
||||
if [ -n "$vmrss_kb" ]; then
|
||||
echo $((vmrss_kb * 1024))
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "0"
|
||||
}
|
||||
|
||||
# Get rsyslog open file descriptor count from /proc
|
||||
# Returns: number of open fds, or 0 if unavailable
|
||||
get_open_fds() {
|
||||
local pid
|
||||
pid=$(pidof rsyslogd 2>/dev/null) || { echo "0"; return; }
|
||||
|
||||
# Use the first PID if multiple
|
||||
pid=${pid%% *}
|
||||
|
||||
if [ -d "/proc/$pid/fd" ]; then
|
||||
ls "/proc/$pid/fd" 2>/dev/null | wc -l
|
||||
return
|
||||
fi
|
||||
|
||||
echo "0"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# METRIC GENERATION
|
||||
# ============================================================================
|
||||
|
||||
# Generate all Prometheus metrics
|
||||
# Returns: Prometheus text format metrics on stdout
|
||||
generate_metrics() {
|
||||
local script_start
|
||||
script_start=$(date +%s.%N 2>/dev/null || date +%s)
|
||||
local success=1
|
||||
|
||||
# Verify jq is available
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
cat <<EOF
|
||||
# HELP rsyslog_up rsyslog process status
|
||||
# TYPE rsyslog_up gauge
|
||||
rsyslog_up 0
|
||||
|
||||
# HELP rsyslog_exporter_success Whether the last collection succeeded
|
||||
# TYPE rsyslog_exporter_success gauge
|
||||
rsyslog_exporter_success 0
|
||||
EOF
|
||||
echo "ERROR: jq is required but not installed" >&2
|
||||
return
|
||||
fi
|
||||
|
||||
# ========================================================================
|
||||
# Core Status
|
||||
# ========================================================================
|
||||
local rsyslog_running=0
|
||||
if is_rsyslog_running; then
|
||||
rsyslog_running=1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_up rsyslog process status (1=running, 0=down)
|
||||
# TYPE rsyslog_up gauge
|
||||
rsyslog_up $rsyslog_running
|
||||
EOF
|
||||
|
||||
local version
|
||||
version=$(get_rsyslog_version)
|
||||
if [ -n "$version" ]; then
|
||||
cat <<EOF
|
||||
|
||||
# HELP rsyslog_info rsyslog version information
|
||||
# TYPE rsyslog_info gauge
|
||||
rsyslog_info{version="$version"} 1
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ========================================================================
|
||||
# Total Messages
|
||||
# ========================================================================
|
||||
local total_messages
|
||||
total_messages=$(get_total_messages)
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_messages_total Total messages processed (main queue enqueued)
|
||||
# TYPE rsyslog_messages_total counter
|
||||
rsyslog_messages_total $total_messages
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
|
||||
# ========================================================================
|
||||
# Queue Metrics
|
||||
# ========================================================================
|
||||
local queue_stats
|
||||
queue_stats=$(get_latest_stats "core.queue")
|
||||
|
||||
if [ -n "$queue_stats" ]; then
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_size Current queue depth
|
||||
# TYPE rsyslog_queue_size gauge
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname qsize
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
qsize=$(echo "$line" | jq -r '.size // 0')
|
||||
echo "rsyslog_queue_size{queue=\"$qname\"} $qsize"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_enqueued_total Messages enqueued to queue
|
||||
# TYPE rsyslog_queue_enqueued_total counter
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname val
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.enqueued // 0')
|
||||
echo "rsyslog_queue_enqueued_total{queue=\"$qname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_dequeued_total Messages dequeued from queue
|
||||
# TYPE rsyslog_queue_dequeued_total counter
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname val
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.dequeued // 0')
|
||||
echo "rsyslog_queue_dequeued_total{queue=\"$qname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_full_total Times queue reached full capacity
|
||||
# TYPE rsyslog_queue_full_total counter
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname val
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.full // 0')
|
||||
echo "rsyslog_queue_full_total{queue=\"$qname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_max_size Configured maximum queue size
|
||||
# TYPE rsyslog_queue_max_size gauge
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname val
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.maxqsize // 0')
|
||||
echo "rsyslog_queue_max_size{queue=\"$qname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_queue_disk_usage_bytes Disk-assisted queue disk usage in bytes
|
||||
# TYPE rsyslog_queue_disk_usage_bytes gauge
|
||||
EOF
|
||||
echo "$queue_stats" | while IFS= read -r line; do
|
||||
local qname val
|
||||
qname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.["diskspace.used"] // 0')
|
||||
echo "rsyslog_queue_disk_usage_bytes{queue=\"$qname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ========================================================================
|
||||
# Action Metrics
|
||||
# ========================================================================
|
||||
local action_stats
|
||||
action_stats=$(get_latest_stats "core.action")
|
||||
|
||||
if [ -n "$action_stats" ]; then
|
||||
cat <<EOF
|
||||
# HELP rsyslog_action_processed_total Messages processed per action
|
||||
# TYPE rsyslog_action_processed_total counter
|
||||
EOF
|
||||
echo "$action_stats" | while IFS= read -r line; do
|
||||
local aname val
|
||||
aname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.processed // 0')
|
||||
echo "rsyslog_action_processed_total{action=\"$aname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_action_failed_total Failed action attempts
|
||||
# TYPE rsyslog_action_failed_total counter
|
||||
EOF
|
||||
echo "$action_stats" | while IFS= read -r line; do
|
||||
local aname val
|
||||
aname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.failed // 0')
|
||||
echo "rsyslog_action_failed_total{action=\"$aname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_action_suspended Action is currently suspended (1=yes, 0=no)
|
||||
# TYPE rsyslog_action_suspended gauge
|
||||
EOF
|
||||
echo "$action_stats" | while IFS= read -r line; do
|
||||
local aname val
|
||||
aname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.suspended // 0')
|
||||
echo "rsyslog_action_suspended{action=\"$aname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_action_resumed_total Times action was resumed from suspended state
|
||||
# TYPE rsyslog_action_resumed_total counter
|
||||
EOF
|
||||
echo "$action_stats" | while IFS= read -r line; do
|
||||
local aname val
|
||||
aname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.resumed // 0')
|
||||
echo "rsyslog_action_resumed_total{action=\"$aname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ========================================================================
|
||||
# Input Metrics
|
||||
# ========================================================================
|
||||
# Input modules use various origin values; collect them all
|
||||
local input_stats=""
|
||||
for input_origin in "imuxsock" "imjournal" "imtcp" "imudp" "imptcp" "imrelp"; do
|
||||
local stats
|
||||
stats=$(get_latest_stats "$input_origin")
|
||||
if [ -n "$stats" ]; then
|
||||
if [ -n "$input_stats" ]; then
|
||||
input_stats="${input_stats}"$'\n'"${stats}"
|
||||
else
|
||||
input_stats="$stats"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$input_stats" ]; then
|
||||
cat <<EOF
|
||||
# HELP rsyslog_input_received_total Messages received per input
|
||||
# TYPE rsyslog_input_received_total counter
|
||||
EOF
|
||||
echo "$input_stats" | while IFS= read -r line; do
|
||||
local iname val
|
||||
iname=$(echo "$line" | jq -r '.name // "unknown"')
|
||||
val=$(echo "$line" | jq -r '.submitted // .received // 0')
|
||||
echo "rsyslog_input_received_total{input=\"$iname\"} $val"
|
||||
done
|
||||
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ========================================================================
|
||||
# Process Metrics
|
||||
# ========================================================================
|
||||
if is_rsyslog_running; then
|
||||
local mem_bytes open_fds
|
||||
mem_bytes=$(get_process_memory)
|
||||
open_fds=$(get_open_fds)
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_process_memory_bytes RSS memory of rsyslog process in bytes
|
||||
# TYPE rsyslog_process_memory_bytes gauge
|
||||
rsyslog_process_memory_bytes $mem_bytes
|
||||
|
||||
# HELP rsyslog_process_open_fds Number of open file descriptors
|
||||
# TYPE rsyslog_process_open_fds gauge
|
||||
rsyslog_process_open_fds $open_fds
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ========================================================================
|
||||
# Exporter Health
|
||||
# ========================================================================
|
||||
local script_end script_duration
|
||||
script_end=$(date +%s.%N 2>/dev/null || date +%s)
|
||||
script_duration=$(awk "BEGIN {printf \"%.3f\", $script_end - $script_start}" 2>/dev/null || echo "0")
|
||||
|
||||
cat <<EOF
|
||||
# HELP rsyslog_exporter_duration_seconds Time to generate all metrics
|
||||
# TYPE rsyslog_exporter_duration_seconds gauge
|
||||
rsyslog_exporter_duration_seconds $script_duration
|
||||
|
||||
# HELP rsyslog_exporter_last_run_timestamp Unix timestamp of last successful run
|
||||
# TYPE rsyslog_exporter_last_run_timestamp gauge
|
||||
rsyslog_exporter_last_run_timestamp $(date +%s)
|
||||
|
||||
# HELP rsyslog_exporter_success Whether the last collection succeeded
|
||||
# TYPE rsyslog_exporter_success gauge
|
||||
rsyslog_exporter_success $success
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# HTTP SERVER MODE
|
||||
# ============================================================================
|
||||
|
||||
# Run simple HTTP server using netcat
|
||||
# Serves metrics on /metrics endpoint
|
||||
run_http_server() {
|
||||
echo "Starting rsyslog metrics exporter on port $HTTP_PORT..." >&2
|
||||
|
||||
if ! command -v nc >/dev/null 2>&1; then
|
||||
echo "ERROR: netcat (nc) required for HTTP mode" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Infinite loop accepting HTTP requests
|
||||
while true; do
|
||||
{
|
||||
read -r request
|
||||
# Check if request is for /metrics endpoint
|
||||
if [[ "$request" =~ ^GET\ /metrics ]]; then
|
||||
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r"
|
||||
generate_metrics
|
||||
else # Serve HTML landing page for other requests
|
||||
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r"
|
||||
cat <<EOF
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Rsyslog Metrics Exporter v1.0</title></head>
|
||||
<body>
|
||||
<h1>Rsyslog Metrics Exporter v1.0</h1>
|
||||
<p><a href="/metrics">Metrics</a></p>
|
||||
<h2>Metric Categories</h2>
|
||||
<ul>
|
||||
<li>Core Status: rsyslog up/down, version info</li>
|
||||
<li>Queue Metrics: depth, enqueued/dequeued, full events, disk usage</li>
|
||||
<li>Action Metrics: processed/failed/suspended per action</li>
|
||||
<li>Input Metrics: messages received per input module</li>
|
||||
<li>Process Metrics: memory usage, open file descriptors</li>
|
||||
<li>Exporter Health: runtime, last run timestamp, success flag</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
fi
|
||||
} | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# TEXTFILE WRITER (with atomic rename)
|
||||
# ============================================================================
|
||||
|
||||
# Write metrics to textfile collector file atomically
|
||||
# Uses temp file + rename to avoid partial reads
|
||||
write_textfile() {
|
||||
local output_dir
|
||||
output_dir="$(dirname "$OUTPUT_FILE")"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
# Create temp file in SAME directory for atomic rename (same filesystem)
|
||||
TEMP_FILE=$(mktemp "${output_dir}/.rsyslog_metrics.XXXXXX")
|
||||
|
||||
# Generate metrics to temp file
|
||||
if ! generate_metrics > "$TEMP_FILE" 2>/dev/null; then
|
||||
rm -f "$TEMP_FILE"
|
||||
TEMP_FILE=""
|
||||
echo "ERROR: Failed to generate metrics" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate: file must have content
|
||||
local file_lines
|
||||
file_lines=$(wc -l < "$TEMP_FILE" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$file_lines" -lt 5 ]; then
|
||||
rm -f "$TEMP_FILE"
|
||||
TEMP_FILE=""
|
||||
echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Set permissions before move
|
||||
chmod 644 "$TEMP_FILE"
|
||||
|
||||
# Atomic rename - no gap where file is missing
|
||||
mv -f "$TEMP_FILE" "$OUTPUT_FILE"
|
||||
TEMP_FILE=""
|
||||
|
||||
debug_log "Metrics written to $OUTPUT_FILE ($file_lines lines)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAIN EXECUTION
|
||||
# ============================================================================
|
||||
|
||||
# Main entry point - routes to appropriate output mode
|
||||
main() {
|
||||
parse_args "$@"
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ "$HTTP_MODE" = true ]; then
|
||||
# Run HTTP server (blocks until killed)
|
||||
run_http_server
|
||||
elif [ -n "$OUTPUT_FILE" ] && [ "$DAEMON_MODE" = true ]; then
|
||||
# Daemon mode: write textfile on a loop
|
||||
echo "Starting rsyslog metrics exporter (daemon, interval=${COLLECTION_INTERVAL}s)..." >&2
|
||||
echo "Writing to: $OUTPUT_FILE" >&2
|
||||
|
||||
while true; do
|
||||
write_textfile || true
|
||||
sleep "$COLLECTION_INTERVAL"
|
||||
done
|
||||
elif [ -n "$OUTPUT_FILE" ]; then
|
||||
# Textfile collector mode: write once
|
||||
write_textfile
|
||||
echo "Metrics written to $OUTPUT_FILE" >&2
|
||||
else
|
||||
# Default: output to stdout
|
||||
generate_metrics
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute main function with all script arguments
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user