Initial loki skill with promtail auto-discovery
This commit is contained in:
87
SKILL.md
Normal file
87
SKILL.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: loki
|
||||
description: Log aggregation with Loki and Promtail
|
||||
metadata:
|
||||
version: "1.0.0"
|
||||
vibestack:
|
||||
main: false
|
||||
metrics-port: 3100
|
||||
---
|
||||
|
||||
# Loki Skill
|
||||
|
||||
Log aggregation using [Grafana Loki](https://grafana.com/oss/loki/) with Promtail for log shipping.
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight log storage (indexes labels, not content)
|
||||
- Auto-discovers skill logs from supervisor
|
||||
- Prometheus-compatible metrics
|
||||
- LogQL query language
|
||||
- Auto-registers with Caddy if present
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
supervisor logs ──► promtail ──► loki ──► /loki/api/v1/query
|
||||
/var/log/supervisor/*.log :3100
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `LOKI_PORT` | Loki API port | `3100` |
|
||||
| `LOKI_DATA_DIR` | Data storage directory | `/data/loki` |
|
||||
| `LOKI_RETENTION` | Log retention period | `168h` (7 days) |
|
||||
| `LOKI_DOMAIN` | Domain for Caddy auto-config | (none) |
|
||||
| `SUPERVISOR_LOG_DIR` | Where to find skill logs | `/var/log/supervisor` |
|
||||
|
||||
## Querying Logs
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
# All logs from last hour
|
||||
curl -G "http://localhost:3100/loki/api/v1/query_range" \
|
||||
--data-urlencode 'query={job="skills"}' \
|
||||
--data-urlencode 'start=1h'
|
||||
|
||||
# Logs from specific skill
|
||||
curl -G "http://localhost:3100/loki/api/v1/query_range" \
|
||||
--data-urlencode 'query={skill="caddy"}'
|
||||
|
||||
# Search for errors
|
||||
curl -G "http://localhost:3100/loki/api/v1/query_range" \
|
||||
--data-urlencode 'query={job="skills"} |= "error"'
|
||||
```
|
||||
|
||||
### LogQL Examples
|
||||
|
||||
```logql
|
||||
# All skill logs
|
||||
{job="skills"}
|
||||
|
||||
# Specific skill
|
||||
{skill="ttyd"}
|
||||
|
||||
# Filter by content
|
||||
{job="skills"} |= "error"
|
||||
{job="skills"} |~ "error|warn"
|
||||
|
||||
# JSON parsing (if logs are JSON)
|
||||
{job="skills"} | json | level="error"
|
||||
```
|
||||
|
||||
## Integration with Grafana
|
||||
|
||||
Add Loki as a data source:
|
||||
- URL: `http://loki:3100` (or via Caddy proxy)
|
||||
- Access: Server
|
||||
|
||||
## Metrics
|
||||
|
||||
Loki exposes Prometheus metrics at `:3100/metrics`:
|
||||
- `loki_ingester_streams_created_total`
|
||||
- `loki_distributor_bytes_received_total`
|
||||
- `loki_querier_query_duration_seconds`
|
||||
226
scripts/autorun.sh
Normal file
226
scripts/autorun.sh
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
LOKI_VERSION="${LOKI_VERSION:-3.0.0}"
|
||||
SKILLS_DIR="${SKILLS_DIR:-/skills}"
|
||||
LOKI_DATA_DIR="${LOKI_DATA_DIR:-/data/loki}"
|
||||
SUPERVISOR_LOG_DIR="${SUPERVISOR_LOG_DIR:-/var/log/supervisor}"
|
||||
SKILL_DIR="$(dirname "$(dirname "$0")")"
|
||||
|
||||
# Detect architecture
|
||||
get_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64) echo "amd64" ;;
|
||||
aarch64) echo "arm64" ;;
|
||||
armv7l) echo "arm" ;;
|
||||
*)
|
||||
echo "Unsupported architecture: $(uname -m)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Install Loki
|
||||
install_loki() {
|
||||
if command -v loki &>/dev/null; then
|
||||
echo "loki already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Installing Loki v${LOKI_VERSION}..."
|
||||
local arch=$(get_arch)
|
||||
local url="https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/loki-linux-${arch}.zip"
|
||||
|
||||
apt-get update && apt-get install -y unzip
|
||||
|
||||
curl -sSL "$url" -o /tmp/loki.zip
|
||||
unzip -o /tmp/loki.zip -d /tmp
|
||||
mv /tmp/loki-linux-${arch} /usr/local/bin/loki
|
||||
chmod +x /usr/local/bin/loki
|
||||
rm /tmp/loki.zip
|
||||
|
||||
echo "Loki installed"
|
||||
}
|
||||
|
||||
# Install Promtail
|
||||
install_promtail() {
|
||||
if command -v promtail &>/dev/null; then
|
||||
echo "promtail already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Installing Promtail v${LOKI_VERSION}..."
|
||||
local arch=$(get_arch)
|
||||
local url="https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/promtail-linux-${arch}.zip"
|
||||
|
||||
curl -sSL "$url" -o /tmp/promtail.zip
|
||||
unzip -o /tmp/promtail.zip -d /tmp
|
||||
mv /tmp/promtail-linux-${arch} /usr/local/bin/promtail
|
||||
chmod +x /usr/local/bin/promtail
|
||||
rm /tmp/promtail.zip
|
||||
|
||||
echo "Promtail installed"
|
||||
}
|
||||
|
||||
# Setup directories
|
||||
setup_dirs() {
|
||||
mkdir -p "$LOKI_DATA_DIR"
|
||||
mkdir -p "$SKILL_DIR/config"
|
||||
echo "Data directory: $LOKI_DATA_DIR"
|
||||
}
|
||||
|
||||
# Generate Loki config
|
||||
generate_loki_config() {
|
||||
local retention="${LOKI_RETENTION:-168h}"
|
||||
|
||||
cat > "$SKILL_DIR/config/loki.yaml" << EOF
|
||||
auth_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 3100
|
||||
grpc_listen_port: 9096
|
||||
|
||||
common:
|
||||
instance_addr: 127.0.0.1
|
||||
path_prefix: $LOKI_DATA_DIR
|
||||
storage:
|
||||
filesystem:
|
||||
chunks_directory: $LOKI_DATA_DIR/chunks
|
||||
rules_directory: $LOKI_DATA_DIR/rules
|
||||
replication_factor: 1
|
||||
ring:
|
||||
kvstore:
|
||||
store: inmemory
|
||||
|
||||
query_range:
|
||||
results_cache:
|
||||
cache:
|
||||
embedded_cache:
|
||||
enabled: true
|
||||
max_size_mb: 100
|
||||
|
||||
schema_config:
|
||||
configs:
|
||||
- from: 2020-10-24
|
||||
store: tsdb
|
||||
object_store: filesystem
|
||||
schema: v13
|
||||
index:
|
||||
prefix: index_
|
||||
period: 24h
|
||||
|
||||
limits_config:
|
||||
retention_period: $retention
|
||||
|
||||
ruler:
|
||||
alertmanager_url: http://localhost:9093
|
||||
|
||||
analytics:
|
||||
reporting_enabled: false
|
||||
EOF
|
||||
|
||||
echo "Loki config written to $SKILL_DIR/config/loki.yaml"
|
||||
}
|
||||
|
||||
# Generate Promtail config with auto-discovered log paths
|
||||
generate_promtail_config() {
|
||||
cat > "$SKILL_DIR/config/promtail.yaml" << EOF
|
||||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /tmp/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://localhost:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
# Supervisor logs (all skills)
|
||||
- job_name: skills
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: skills
|
||||
__path__: $SUPERVISOR_LOG_DIR/*.log
|
||||
|
||||
pipeline_stages:
|
||||
# Extract skill name from filename
|
||||
- match:
|
||||
selector: '{job="skills"}'
|
||||
stages:
|
||||
- regex:
|
||||
source: filename
|
||||
expression: '.*/(?P<skill>[^/]+)\\.log$'
|
||||
- labels:
|
||||
skill:
|
||||
|
||||
# Supervisor error logs
|
||||
- job_name: skills_errors
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: skills
|
||||
level: error
|
||||
__path__: $SUPERVISOR_LOG_DIR/*.err
|
||||
|
||||
pipeline_stages:
|
||||
- match:
|
||||
selector: '{job="skills"}'
|
||||
stages:
|
||||
- regex:
|
||||
source: filename
|
||||
expression: '.*/(?P<skill>[^/]+)\\.err$'
|
||||
- labels:
|
||||
skill:
|
||||
EOF
|
||||
|
||||
echo "Promtail config written to $SKILL_DIR/config/promtail.yaml"
|
||||
}
|
||||
|
||||
# Configure Caddy if present
|
||||
configure_caddy() {
|
||||
local caddy_dir="$SKILLS_DIR/caddy"
|
||||
local loki_port="${LOKI_PORT:-3100}"
|
||||
local loki_domain="${LOKI_DOMAIN:-}"
|
||||
|
||||
if [ ! -d "$caddy_dir" ]; then
|
||||
echo "Caddy not found - Loki API on port $loki_port"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Caddy detected - configuring reverse proxy..."
|
||||
mkdir -p "$caddy_dir/snippets.d"
|
||||
|
||||
local snippet="$caddy_dir/snippets.d/loki.caddy"
|
||||
|
||||
if [ -n "$loki_domain" ]; then
|
||||
cat > "$snippet" << EOF
|
||||
# Auto-generated by loki skill
|
||||
$loki_domain {
|
||||
reverse_proxy localhost:$loki_port
|
||||
}
|
||||
EOF
|
||||
echo "Caddy config: $loki_domain -> localhost:$loki_port"
|
||||
else
|
||||
cat > "$snippet" << EOF
|
||||
# Auto-generated by loki skill
|
||||
# Add to your site block:
|
||||
# handle /loki/* {
|
||||
# reverse_proxy localhost:$loki_port
|
||||
# }
|
||||
EOF
|
||||
echo "Caddy snippet created (manual config needed)"
|
||||
fi
|
||||
}
|
||||
|
||||
install_loki
|
||||
install_promtail
|
||||
setup_dirs
|
||||
generate_loki_config
|
||||
generate_promtail_config
|
||||
configure_caddy
|
||||
|
||||
echo "Loki setup complete"
|
||||
50
scripts/run.sh
Normal file
50
scripts/run.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SKILL_DIR="$(dirname "$(dirname "$0")")"
|
||||
LOKI_PORT="${LOKI_PORT:-3100}"
|
||||
|
||||
# Start Loki in background
|
||||
start_loki() {
|
||||
echo "Starting Loki on port $LOKI_PORT..."
|
||||
loki -config.file="$SKILL_DIR/config/loki.yaml" &
|
||||
LOKI_PID=$!
|
||||
sleep 3
|
||||
|
||||
if ! kill -0 $LOKI_PID 2>/dev/null; then
|
||||
echo "Failed to start Loki"
|
||||
exit 1
|
||||
fi
|
||||
echo "Loki running (PID $LOKI_PID)"
|
||||
}
|
||||
|
||||
# Start Promtail in background
|
||||
start_promtail() {
|
||||
echo "Starting Promtail..."
|
||||
promtail -config.file="$SKILL_DIR/config/promtail.yaml" &
|
||||
PROMTAIL_PID=$!
|
||||
sleep 2
|
||||
|
||||
if ! kill -0 $PROMTAIL_PID 2>/dev/null; then
|
||||
echo "Failed to start Promtail"
|
||||
exit 1
|
||||
fi
|
||||
echo "Promtail running (PID $PROMTAIL_PID)"
|
||||
}
|
||||
|
||||
# Cleanup on exit
|
||||
cleanup() {
|
||||
echo "Shutting down..."
|
||||
[ -n "$PROMTAIL_PID" ] && kill $PROMTAIL_PID 2>/dev/null
|
||||
[ -n "$LOKI_PID" ] && kill $LOKI_PID 2>/dev/null
|
||||
exit 0
|
||||
}
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
start_loki
|
||||
start_promtail
|
||||
|
||||
echo "Loki stack running. Query at http://localhost:$LOKI_PORT/loki/api/v1/query"
|
||||
|
||||
# Wait for either process to exit
|
||||
wait
|
||||
Reference in New Issue
Block a user