docs: add project documentation, logging config, status script
Some checks failed
Backend API CI / test-unit (push) Failing after 0s
Backend API CI / test-integration (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Storybook Audit / Build & audit Storybook (push) Failing after 0s

- docs/VEZA_PROJECT_DOCUMENTATION.md
- config/logging.toml
- status.sh utility script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
senke 2026-03-18 11:36:36 +01:00
parent 20a16f7cbe
commit d5bfe4a558
3 changed files with 1533 additions and 0 deletions

150
config/logging.toml Normal file
View file

@ -0,0 +1,150 @@
# ═══════════════════════════════════════════════════════════════════════════════
# VEZA — Centralized Logging Configuration
# ═══════════════════════════════════════════════════════════════════════════════
#
# Single source of truth for ALL service logging.
# Read by: Go backend, Rust stream server, Frontend (via env vars)
#
# Override any value via environment variable:
# LOG_LEVEL=DEBUG LOG_DIR=/tmp/veza-logs make dev
#
# ═══════════════════════════════════════════════════════════════════════════════
# ── Global defaults ──────────────────────────────────────────────────────────
[global]
# Log level: TRACE, DEBUG, INFO, WARN, ERROR (case-insensitive, Go & Rust compatible)
level = "INFO"
# Base directory for all log files
# Created automatically with 0755 permissions if it doesn't exist.
# Fallback: ./logs (if /var/log/veza is not writable)
dir = "/var/log/veza"
# Format: "json" (production/staging) or "text" (development)
# Auto-detected from APP_ENV if not set: production/staging → json, else → text
format = "auto"
# ── File rotation (lumberjack for Go, tracing-appender for Rust) ─────────────
[rotation]
# Maximum size of a single log file before rotation (megabytes)
max_size_mb = 100
# Maximum number of rotated files to retain
max_backups = 10
# Maximum age of rotated files before deletion (days)
max_age_days = 30
# Compress rotated files with gzip
compress = true
# Rust rotation strategy: "hourly" or "daily"
# Hourly if max_size_mb ≤ 100, daily otherwise
rust_rotation = "hourly"
# Maximum rotated files for Rust (tracing-appender)
rust_max_files = 5
# ── Go Backend API ───────────────────────────────────────────────────────────
[backend]
# Module name — determines log file names: {module}.log, {module}-error.log
module = "backend-api"
# Additional module loggers (each gets its own .log + -error.log pair)
# These log database queries, redis commands, rabbitmq events separately
modules = ["db", "rabbitmq"]
# Slow request detection threshold (milliseconds)
# Requests slower than this are logged at WARN level
slow_request_threshold_ms = 1000
# Sampling (production only — prevents log spam under high load)
# Initial: first N messages per second are always logged
# Thereafter: 1 in N subsequent messages are logged
sampling_initial = 100
sampling_thereafter = 100
# Async buffered writes
# Buffer size in KB — reduces syscalls by batching writes
buffer_size_kb = 256
# Flush interval in milliseconds — maximum delay before buffer is flushed
flush_interval_ms = 100
# ── Rust Stream Server ───────────────────────────────────────────────────────
[stream]
# Module name — determines log file prefix: {module}.YYYY-MM-DD-HH
module = "stream"
# Include source file and line number in log entries
include_source = true
# Include thread IDs in log entries
include_thread_ids = true
# ── Frontend (React) ────────────────────────────────────────────────────────
[frontend]
# Frontend log level (VITE_LOG_LEVEL)
# Defaults: DEBUG in development, WARN in production
level = "auto"
# Backend endpoint for forwarding frontend errors
# Uses navigator.sendBeacon() for non-blocking delivery
endpoint = "/api/v1/logs/frontend"
# Enable Sentry error tracking (requires VITE_SENTRY_DSN env var)
sentry_enabled = false
# ── Log Aggregation (optional — Loki/Grafana) ───────────────────────────────
[aggregation]
# Enable centralized log aggregation (Loki-compatible)
enabled = false
# Aggregation endpoint URL
# endpoint = "http://loki:3100/loki/api/v1/push"
# Batch size — number of log entries per HTTP push
batch_size = 100
# Flush interval (seconds) — maximum delay between pushes
flush_interval_s = 5
# HTTP timeout for push requests (seconds)
timeout_s = 10
# Static labels applied to all log entries
# labels = "app=veza,env=production"
# ── File permissions ─────────────────────────────────────────────────────────
[permissions]
# Directory permissions (octal)
dir_mode = "0755"
# File permissions (octal) — Go files
file_mode = "0640"
# ═══════════════════════════════════════════════════════════════════════════════
# Environment Variable Overrides (highest priority)
# ═══════════════════════════════════════════════════════════════════════════════
#
# LOG_LEVEL → global.level
# LOG_DIR → global.dir
# LOG_FORMAT → global.format
# SLOW_REQUEST_THRESHOLD_MS → backend.slow_request_threshold_ms
# LOG_AGGREGATION_ENABLED → aggregation.enabled
# LOG_AGGREGATION_ENDPOINT → aggregation.endpoint
# LOG_AGGREGATION_BATCH_SIZE → aggregation.batch_size
# LOG_AGGREGATION_FLUSH_INTERVAL → aggregation.flush_interval_s
# LOG_AGGREGATION_TIMEOUT → aggregation.timeout_s
# LOG_AGGREGATION_LABELS → aggregation.labels
# VITE_LOG_LEVEL → frontend.level
# VITE_SENTRY_DSN → frontend.sentry_enabled (if set)
#
# ═══════════════════════════════════════════════════════════════════════════════

File diff suppressed because it is too large Load diff

324
status.sh Executable file
View file

@ -0,0 +1,324 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════════════════
# VEZA — Project Status Dashboard
# Usage: ./status.sh (full report)
# ./status.sh --quick (infra + services only, no tests)
# ═══════════════════════════════════════════════════════════════════════════════
set -uo pipefail
# ── Colors & symbols ─────────────────────────────────────────────────────────
R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[0;33m' B=$'\033[0;34m'
M=$'\033[0;35m' C=$'\033[0;36m' W=$'\033[1;37m' D=$'\033[0;90m'
NC=$'\033[0m' BOLD=$'\033[1m' DIM=$'\033[2m'
OK="${G}${NC}" FAIL="${R}${NC}" WARN="${Y}${NC}" SKIP="${D}${NC}"
SPIN="${C}${NC}"
ROOT="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT"
QUICK=false
[[ "${1:-}" == "--quick" || "${1:-}" == "-q" ]] && QUICK=true
# ── Helpers ──────────────────────────────────────────────────────────────────
hr() { echo -e "${D}$(printf '─%.0s' {1..72})${NC}"; }
hdr() { echo -e "\n${BOLD}${C}$1${NC}"; hr; }
ok() { echo -e " ${OK} $1"; }
ko() { echo -e " ${FAIL} $1"; }
wr() { echo -e " ${WARN} $1"; }
sk() { echo -e " ${SKIP} ${D}$1${NC}"; }
kv() { printf " %-24s " "$1"; echo -e "$2"; }
check_port() {
local port="$1"
if ss -tlnp 2>/dev/null | grep -q ":${port} " || nc -z 127.0.0.1 "$port" 2>/dev/null; then
echo "up"
else
echo "down"
fi
}
http_check() {
local url="$1" timeout="${2:-3}"
curl -s -o /dev/null -w '%{http_code}' --max-time "$timeout" "$url" 2>/dev/null || echo "000"
}
# ═══════════════════════════════════════════════════════════════════════════════
# HEADER
# ═══════════════════════════════════════════════════════════════════════════════
echo
echo -e "${BOLD}${M} ╔═══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${M} ║ VEZA — Project Status Dashboard ║${NC}"
echo -e "${BOLD}${M} ╚═══════════════════════════════════════════════════════════════╝${NC}"
echo -e " ${D}$(date '+%Y-%m-%d %H:%M:%S')$(hostname)${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# GIT & VERSION
# ═══════════════════════════════════════════════════════════════════════════════
hdr "GIT & VERSION"
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")
commit=$(git rev-parse --short HEAD 2>/dev/null || echo "?")
commit_msg=$(git log -1 --format='%s' 2>/dev/null | head -c 60 || echo "")
tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "none")
ahead=$(git rev-list "${tag}..HEAD" --count 2>/dev/null || echo "?")
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
kv "Branch:" "${W}${branch}${NC}"
kv "Latest tag:" "${G}${tag}${NC} ${D}(+${ahead} commits)${NC}"
kv "HEAD:" "${commit}${DIM}${commit_msg}${NC}"
if [[ "$dirty" -gt 0 ]]; then
staged=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
unstaged=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l | tr -d ' ')
kv "Working tree:" "${Y}${dirty} changes${NC} ${D}(staged:${staged} modified:${unstaged} untracked:${untracked})${NC}"
else
kv "Working tree:" "${G}clean${NC}"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# TOOLCHAIN
# ═══════════════════════════════════════════════════════════════════════════════
hdr "TOOLCHAIN"
go_ver=$(go version 2>/dev/null | awk '{print $3}' | sed 's/go//' || echo "not installed")
rust_ver=$(rustc --version 2>/dev/null | awk '{print $2}' || echo "not installed")
node_ver=$(node --version 2>/dev/null | sed 's/v//' || echo "not installed")
npm_ver=$(npm --version 2>/dev/null || echo "?")
docker_ver=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' || echo "not installed")
kv "Go:" "${go_ver}"
kv "Rust:" "${rust_ver}"
kv "Node:" "${node_ver} ${D}(npm ${npm_ver})${NC}"
kv "Docker:" "${docker_ver}"
air_ok=$(command -v air >/dev/null 2>&1 && echo "${G}yes${NC}" || echo "${D}no${NC}")
cargo_watch_ok=$(command -v cargo-watch >/dev/null 2>&1 && echo "${G}yes${NC}" || echo "${D}no${NC}")
kv "Hot reload:" "air=${air_ok} cargo-watch=${cargo_watch_ok}"
# ═══════════════════════════════════════════════════════════════════════════════
# DOCKER INFRASTRUCTURE
# ═══════════════════════════════════════════════════════════════════════════════
hdr "DOCKER INFRASTRUCTURE"
if ! docker info >/dev/null 2>&1; then
ko "Docker daemon is not running"
else
for name in postgres redis rabbitmq clamav minio elasticsearch; do
container="veza_${name}"
status=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "absent")
health=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "n/a")
label=$(printf '%-16s' "$name")
case "$status" in
running)
if [[ "$health" == "healthy" ]]; then
ok "${label} ${G}running${NC} ${G}healthy${NC}"
elif [[ "$health" == "unhealthy" ]]; then
wr "${label} ${G}running${NC} ${R}unhealthy${NC}"
else
ok "${label} ${G}running${NC} ${D}(no healthcheck)${NC}"
fi ;;
exited) ko "${label} ${R}exited${NC}" ;;
*) sk "${label} ${status}" ;;
esac
done
fi
# ═══════════════════════════════════════════════════════════════════════════════
# APPLICATION SERVICES
# ═══════════════════════════════════════════════════════════════════════════════
hdr "APPLICATION SERVICES"
declare -A SVC_PORTS=( [backend-api]=18080 [stream-server]=18082 [frontend]=5173 )
declare -A SVC_HEALTH=( [backend-api]="/api/v1/health" [stream-server]="/health" [frontend]="/" )
for svc in backend-api stream-server frontend; do
port="${SVC_PORTS[$svc]}"
endpoint="${SVC_HEALTH[$svc]}"
url="http://localhost:${port}${endpoint}"
label=$(printf '%-16s' "$svc")
state=$(check_port "$port")
if [[ "$state" == "up" ]]; then
code=$(http_check "$url" 3)
if [[ "$code" =~ ^2 ]]; then
ok "${label} ${G}:${port}${NC} HTTP ${G}${code}${NC}"
elif [[ "$code" == "000" ]]; then
wr "${label} ${Y}:${port}${NC} ${Y}port open, no HTTP${NC}"
else
wr "${label} ${G}:${port}${NC} HTTP ${Y}${code}${NC}"
fi
else
ko "${label} ${R}:${port} not listening${NC}"
fi
done
# Rate limiting probe (only if backend is up)
if [[ "$(check_port 18080)" == "up" ]]; then
got429=false
for _ in $(seq 1 8); do
c=$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 \
-X POST http://localhost:18080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"probe@test.invalid","password":"x"}' 2>/dev/null) || c="000"
[[ "$c" == "429" ]] && got429=true && break
done
if $got429; then
kv "Rate limiting:" "${Y}ACTIVE${NC} — use ${W}make dev-e2e${NC} for tests"
else
kv "Rate limiting:" "${G}disabled (test mode)${NC}"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# DATABASE
# ═══════════════════════════════════════════════════════════════════════════════
hdr "DATABASE"
pg_port="${PORT_POSTGRES:-15432}"
if [[ "$(check_port "$pg_port")" == "up" ]]; then
pg_url="postgres://veza:${DB_PASSWORD:-password}@localhost:${pg_port}/veza?sslmode=disable"
tbl_count=$(psql "$pg_url" -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema='public'" 2>/dev/null | tr -d ' ' || echo "?")
kv "Tables:" "${tbl_count}"
mig_count=$(find veza-backend-api/migrations -maxdepth 1 -name '*.sql' ! -name '*_down*' 2>/dev/null | wc -l | tr -d ' ')
latest_mig=$(find veza-backend-api/migrations -maxdepth 1 -name '*.sql' ! -name '*_down*' -printf '%f\n' 2>/dev/null | sort -n | tail -1 || echo "none")
kv "Migrations:" "${mig_count} files — latest: ${D}${latest_mig}${NC}"
for tbl in users tracks playlists products; do
cnt=$(psql "$pg_url" -t -c "SELECT count(*) FROM ${tbl}" 2>/dev/null | tr -d ' ' || echo "—")
kv " ${tbl}:" "${cnt} rows"
done
else
sk "PostgreSQL not reachable on :${pg_port}"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# CODEBASE STATS
# ═══════════════════════════════════════════════════════════════════════════════
hdr "CODEBASE"
go_files=$(find veza-backend-api -name '*.go' ! -path '*/vendor/*' 2>/dev/null | wc -l | tr -d ' ')
rs_files=$(find veza-stream-server -name '*.rs' 2>/dev/null | wc -l | tr -d ' ')
ts_files=$(find apps/web/src \( -name '*.ts' -o -name '*.tsx' \) 2>/dev/null | wc -l | tr -d ' ')
e2e_files=$(find tests/e2e -name '*.spec.ts' 2>/dev/null | wc -l | tr -d ' ')
e2e_tests=$(grep -rch "test(" tests/e2e/*.spec.ts 2>/dev/null | awk '{s+=$1}END{print s+0}')
kv "Go (backend):" "${go_files} files"
kv "Rust (stream):" "${rs_files} files"
kv "TS/TSX (web):" "${ts_files} files"
kv "E2E specs:" "${e2e_files} files — ${W}~${e2e_tests} tests${NC}"
# ═══════════════════════════════════════════════════════════════════════════════
# TESTS (skip with --quick)
# ═══════════════════════════════════════════════════════════════════════════════
if $QUICK; then
hdr "TESTS ${D}(skipped — run without --quick)${NC}"
else
hdr "TESTS"
# ── Go tests ──
echo -ne " ${SPIN} Running Go tests…\r"
go_out=$(cd veza-backend-api && go test ./internal/handlers/... ./internal/services/... -short -count=1 -timeout 60s 2>&1) || true
go_pass=$(echo "$go_out" | grep -c '^ok' || true)
go_fail=$(echo "$go_out" | grep -c '^FAIL' || true)
go_skip=$(echo "$go_out" | grep -c '\[no test files\]' || true)
printf '\r\033[2K'
if [[ "$go_fail" -eq 0 ]]; then
ok "Go: ${G}${go_pass} passed${NC} ${D}${go_skip} skipped${NC}"
else
ko "Go: ${G}${go_pass} passed${NC} ${R}${go_fail} FAILED${NC} ${D}${go_skip} skipped${NC}"
fi
# ── Rust tests ──
echo -ne " ${SPIN} Running Rust tests…\r"
rust_out=$(cd veza-stream-server && cargo test --lib -q 2>&1) || true
rust_summary=$(echo "$rust_out" | grep -E 'test result:' | head -1 || echo "")
printf '\r\033[2K'
if echo "$rust_summary" | grep -q "0 failed" 2>/dev/null; then
ok "Rust: ${G}${rust_summary}${NC}"
elif [[ -n "$rust_summary" ]]; then
ko "Rust: ${R}${rust_summary}${NC}"
else
rust_pass=$(echo "$rust_out" | grep -oP '\d+ passed' | head -1 || echo "? passed")
ok "Rust: ${G}${rust_pass}${NC}"
fi
# ── Frontend unit tests ──
echo -ne " ${SPIN} Running frontend tests…\r"
web_out=$(cd apps/web && npx vitest run --reporter=verbose 2>&1 | tail -30) || true
web_summary=$(echo "$web_out" | grep -iE 'Tests\s+\d|test files' | tail -1 || echo "")
printf '\r\033[2K'
if [[ -n "$web_summary" ]]; then
if echo "$web_summary" | grep -qi "failed"; then
ko "Web: ${web_summary}"
else
ok "Web: ${web_summary}"
fi
else
sk "Web: could not parse results"
fi
# ── E2E last run ──
last_run="tests/e2e/test-results/.last-run.json"
if [[ -f "$last_run" ]]; then
pw_status=$(python3 -c "import json,sys; d=json.load(open(sys.argv[1])); print(d.get('status','?'))" "$last_run" 2>/dev/null || echo "?")
kv "E2E last run:" "${pw_status}"
else
sk "E2E: no previous run — use ${W}npm run e2e${NC}"
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# VERSIONS ROADMAP
# ═══════════════════════════════════════════════════════════════════════════════
hdr "ROADMAP"
roadmap="VEZA_VERSIONS_ROADMAP.md"
if [[ -f "$roadmap" ]]; then
done_count=$(grep -c '✅ DONE' "$roadmap" || true)
todo_count=$(grep -c '⏳ TODO' "$roadmap" || true)
progress_count=$(grep -cE '🔄|🔨' "$roadmap" || true)
kv "Done:" "${G}${done_count}${NC} versions"
[[ "$progress_count" -gt 0 ]] && kv "In progress:" "${Y}${progress_count}${NC} versions"
[[ "$todo_count" -gt 0 ]] && kv "Todo:" "${D}${todo_count}${NC} versions"
# Extract next TODO version name (format: "## vX.Y.Z — Name")
next_line=$(grep -B5 '⏳ TODO' "$roadmap" | grep -oP 'v\d+\.\d+\.\d+\s*[—–-]\s*.*' | head -1 || true)
[[ -n "$next_line" ]] && kv "Next up:" "${W}${next_line}${NC}"
else
sk "Roadmap file not found"
fi
# ═══════════════════════════════════════════════════════════════════════════════
# HEALTH SUMMARY
# ═══════════════════════════════════════════════════════════════════════════════
echo
hr
total_ok=0; total_ko=0
for svc in postgres redis rabbitmq; do
container="veza_${svc}"
s=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "absent")
h=$(docker inspect -f '{{.State.Health.Status}}' "$container" 2>/dev/null || echo "n/a")
if [[ "$s" == "running" && "$h" == "healthy" ]]; then ((total_ok++)); else ((total_ko++)) || true; fi
done
for port in 18080 18082 5173; do
if [[ "$(check_port "$port")" == "up" ]]; then ((total_ok++)); else ((total_ko++)) || true; fi
done
if [[ "$total_ko" -eq 0 ]]; then
echo -e " ${BOLD}${G}ALL SYSTEMS OPERATIONAL${NC} (${total_ok}/6 services up)"
elif [[ "$total_ko" -le 2 ]]; then
echo -e " ${BOLD}${Y}PARTIAL${NC}${total_ok} up, ${total_ko} down"
else
echo -e " ${BOLD}${R}DEGRADED${NC}${total_ok} up, ${total_ko} down"
fi
echo