feat(primitives): metrics-scrape + log-ship shell primitives

This commit is contained in:
Parfii-bot 2026-04-21 20:41:17 +08:00
parent 48cff91056
commit e49660cd69
2 changed files with 165 additions and 0 deletions

83
_primitives/log-ship.sh Executable file
View file

@ -0,0 +1,83 @@
#!/bin/sh
# log-ship — tee structured JSON-line logs from stdin to stdout and optionally
# forward each line to Loki / Datadog / generic HTTP endpoint.
# Install path: $HOME/.claude/agents/_primitives/log-ship.sh
# POSIX sh. Deps: curl, awk. Optional: jq (for --validate).
#
# Usage:
# cat log.jsonl | log-ship --target stdout
# journalctl -o json | log-ship --target loki --endpoint http://loki:3100/loki/api/v1/push --label job=api
# tail -f app.log | log-ship --target datadog --endpoint https://http-intake.logs.datadoghq.com/api/v2/logs
# cat log.jsonl | log-ship --target http --endpoint https://my.collector/ingest
# cat log.jsonl | log-ship --target stdout --validate
#
# ENV overrides (avoid CLI token leak):
# LOG_SHIP_DD_API_KEY — Datadog API key (HTTP header DD-API-KEY)
# LOG_SHIP_BEARER — generic Bearer token for --target http
#
# Always tees to local stdout first, then forwards. Forwarding failure does NOT
# drop the local tee — observability MUST degrade gracefully.
set -eu
TARGET="stdout"
ENDPOINT=""
LABEL=""
VALIDATE=0
usage() { sed -n '2,17p' "$0" >&2; exit 1; }
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage ;;
--target) TARGET="${2:-stdout}"; shift 2 ;;
--endpoint) ENDPOINT="${2:-}"; shift 2 ;;
--label) LABEL="${2:-}"; shift 2 ;;
--validate) VALIDATE=1; shift ;;
*) echo "[log-ship] unknown arg: $1" >&2; exit 2 ;;
esac
done
case "$TARGET" in stdout|loki|datadog|http) ;; *) echo "[log-ship] bad target: $TARGET" >&2; exit 2 ;; esac
[ "$TARGET" != "stdout" ] && [ -z "$ENDPOINT" ] && { echo "[log-ship] --endpoint required for target=$TARGET" >&2; exit 2; }
[ "$VALIDATE" = 1 ] && ! command -v jq >/dev/null 2>&1 && { echo "[log-ship] jq required for --validate" >&2; exit 1; }
command -v curl >/dev/null 2>&1 || { echo "[log-ship] curl required" >&2; exit 1; }
forward() {
LINE="$1"
case "$TARGET" in
stdout) : ;;
loki)
NS=$(awk 'BEGIN{srand(); printf "%d000000000", systime()}')
ESC=$(printf '%s' "$LINE" | awk '{ gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); print }')
curl -fsS --max-time 5 -H 'Content-Type: application/json' \
-X POST "$ENDPOINT" -d "{\"streams\":[{\"stream\":{\"job\":\"${LABEL:-log-ship}\"},\"values\":[[\"$NS\",\"$ESC\"]]}]}" \
>/dev/null 2>&1 || echo "[log-ship] loki push failed (tee OK)" >&2
;;
datadog)
KEY="${LOG_SHIP_DD_API_KEY:-}"
[ -z "$KEY" ] && { echo "[log-ship] LOG_SHIP_DD_API_KEY unset" >&2; return; }
curl -fsS --max-time 5 -H "DD-API-KEY: $KEY" -H 'Content-Type: application/json' \
-X POST "$ENDPOINT" -d "[$LINE]" >/dev/null 2>&1 \
|| echo "[log-ship] datadog push failed (tee OK)" >&2
;;
http)
AUTH=""
[ -n "${LOG_SHIP_BEARER:-}" ] && AUTH="-H Authorization: Bearer $LOG_SHIP_BEARER"
# shellcheck disable=SC2086
curl -fsS --max-time 5 $AUTH -H 'Content-Type: application/json' \
-X POST "$ENDPOINT" -d "$LINE" >/dev/null 2>&1 \
|| echo "[log-ship] http push failed (tee OK)" >&2
;;
esac
}
# Main loop: one JSON object per line. Tee first, validate optional, forward.
while IFS= read -r line; do
[ -z "$line" ] && continue
printf '%s\n' "$line"
if [ "$VALIDATE" = 1 ]; then
printf '%s' "$line" | jq -e . >/dev/null 2>&1 || { echo "[log-ship] WARN invalid JSON: $line" >&2; continue; }
fi
[ "$TARGET" = "stdout" ] || forward "$line"
done

82
_primitives/metrics-scrape.sh Executable file
View file

@ -0,0 +1,82 @@
#!/bin/sh
# metrics-scrape — scrape a Prometheus /metrics endpoint, parse and pretty-print.
# Install path: $HOME/.claude/agents/_primitives/metrics-scrape.sh
# POSIX sh. Deps: curl, awk. Optional: jq (for --format json).
#
# Usage:
# metrics-scrape <url> # table (default)
# metrics-scrape <url> --format json # JSON array, needs jq
# metrics-scrape <url> --format table # aligned table
# metrics-scrape <url> --format alert-check # non-zero exit if any filtered metric > threshold
# metrics-scrape <url> --filter <regex> # only lines whose metric name matches
# metrics-scrape <url> --format alert-check --filter '^http_requests_total' --threshold 1000
set -eu
URL=""
FORMAT="table"
FILTER=""
THRESHOLD=""
usage() {
sed -n '2,12p' "$0" >&2
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage ;;
--format) FORMAT="${2:-table}"; shift 2 ;;
--filter) FILTER="${2:-}"; shift 2 ;;
--threshold) THRESHOLD="${2:-}"; shift 2 ;;
--*) echo "[metrics-scrape] unknown flag: $1" >&2; exit 2 ;;
*) [ -z "$URL" ] && URL="$1" || { echo "[metrics-scrape] extra arg: $1" >&2; exit 2; }; shift ;;
esac
done
[ -z "$URL" ] && { echo "[metrics-scrape] missing URL" >&2; usage; }
command -v curl >/dev/null 2>&1 || { echo "[metrics-scrape] curl required" >&2; exit 1; }
RAW=$(curl -fsS --max-time 10 "$URL") || { echo "[metrics-scrape] scrape failed: $URL" >&2; exit 3; }
# Strip HELP/TYPE comments and blanks. Optionally filter by metric-name regex.
parse() {
printf '%s\n' "$RAW" | awk -v f="$FILTER" '
/^[[:space:]]*$/ { next }
/^#/ { next }
{
name=$1; sub(/\{.*/, "", name)
if (f == "" || name ~ f) print $0
}'
}
case "$FORMAT" in
table)
parse | awk '
BEGIN { printf "%-60s %s\n", "METRIC", "VALUE"; printf "%-60s %s\n", "------", "-----" }
{ printf "%-60s %s\n", substr($0, 1, length($0)-length($NF)-1), $NF }'
;;
json)
command -v jq >/dev/null 2>&1 || { echo "[metrics-scrape] jq required for --format json" >&2; exit 1; }
parse | awk '
BEGIN { print "[" ; first=1 }
{
val=$NF; line=$0; sub(/[[:space:]]+[^[:space:]]+$/, "", line)
if (!first) printf ",\n"; first=0
gsub(/"/, "\\\"", line)
printf " {\"metric\":\"%s\",\"value\":\"%s\"}", line, val
}
END { print "\n]" }' | jq '.'
;;
alert-check)
[ -z "$THRESHOLD" ] && { echo "[metrics-scrape] --threshold required for alert-check" >&2; exit 2; }
OVER=$(parse | awk -v t="$THRESHOLD" '$NF+0 > t+0 { print $0 }')
if [ -n "$OVER" ]; then
echo "[metrics-scrape] ALERT — $(printf '%s\n' "$OVER" | wc -l | tr -d ' ') metrics over threshold=$THRESHOLD:" >&2
printf '%s\n' "$OVER" >&2
exit 4
fi
echo "[metrics-scrape] OK — all filtered metrics <= $THRESHOLD" >&2
;;
*) echo "[metrics-scrape] unknown format: $FORMAT (table|json|alert-check)" >&2; exit 2 ;;
esac