How Valerter works under the hood.
Valerter is a real-time log alerting daemon that streams logs from VictoriaLogs and sends notifications with full log context.
src/
├── main.rs # Entry point, startup, shutdown
├── lib.rs # Library root, module re-exports
├── cli.rs # CLI argument parsing (clap)
├── engine.rs # RuleEngine - orchestrates rule tasks
├── error.rs # Error types (thiserror)
├── tail.rs # VictoriaLogs streaming client
├── parser.rs # Regex/JSON field extraction
├── throttle.rs # LRU cache-based rate limiting
├── template.rs # Jinja2 templating (minijinja)
├── stream_buffer.rs # UTF-8 safe NDJSON buffering
├── metrics.rs # Prometheus metrics server
├── config/ # Configuration module
│ ├── mod.rs # Module root
│ ├── types.rs # Config, RuleConfig, etc.
│ ├── notifiers.rs # Notifier-specific configs
│ ├── runtime.rs # Compiled runtime config
│ ├── validation.rs # Validation functions
│ ├── env.rs # Environment variable resolution
│ ├── secret.rs # SecretString for sensitive values
│ └── tests.rs # Config tests
└── notify/ # Notification module
├── mod.rs # Module root
├── traits.rs # Notifier async trait
├── registry.rs # NotifierRegistry
├── payload.rs # AlertPayload structure
├── queue.rs # NotificationQueue + Worker
├── mattermost.rs # Mattermost notifier
├── email.rs # Email notifier (SMTP)
├── webhook.rs # Webhook notifier
└── tests.rs # Notify tests
Valerter connects to VictoriaLogs’ /select/logsql/tail endpoint via HTTP streaming. Unlike polling-based approaches:
Each rule maintains its own streaming connection, providing isolation.
Extracts structured fields from log lines:
(?P<field>...))If parsing fails, the error is logged and the line is skipped (no crash).
Prevents alert spam using a sliding window algorithm:
count alerts per window durationRenders the final message using Jinja2 templates (via minijinja):
rule_name, _msg, etc.)default, upper, lower, length, etc.Sends alerts to configured destinations via NotifierRegistry:
join_all)log_timestamp (ISO 8601) and log_timestamp_formatted (human-readable with timezone)main.rs
│
├── MetricsServer::run() ────────────────► :9090/metrics
│
├── NotificationWorker::run() ───────────► consumes from bounded queue
│
└── RuleEngine::run()
│
└── JoinSet<()>
├── rule_task("rule-1") ──► tail → parse → throttle → template → queue
├── rule_task("rule-2") ──► tail → parse → throttle → template → queue
└── rule_task("rule-N") ──► ...
Key properties:
JoinSetPANIC_RESTART_DELAY)CancellationTokenvalerter_rule_panics_total{rule_name} tracks panics per ruleWhen VictoriaLogs connection fails:
valerter_victorialogs_up set to 0 (restored to 1 on successful reconnection)valerter_reconnections_total incrementedUses Tokio’s broadcast channel with ring buffer semantics:
DEFAULT_QUEUE_CAPACITY)valerter_alerts_dropped_total tracks dropped alerts globally via RecvError::Lagged(n)NotificationWorker sends to ALL destinations in parallel via join_allRuleEngine (producers) NotificationWorker (consumer)
│ │
├── rule_task ─┐ │
├── rule_task ──┼── broadcast::Sender ──┼── broadcast::Receiver
└── rule_task ─┘ │
▼
NotifierRegistry
├── mattermost-ops
├── email-alerts
└── webhook-pagerduty
At startup, Valerter validates (in order):
body_htmlmattermost_channel set but no Mattermost notifier in destinationsIf any validation fails, Valerter exits immediately with a clear error message.
Config can be split across multiple files:
/etc/valerter/
├── config.yaml # Main config (victorialogs, defaults, metrics)
├── rules.d/ # Rules as HashMap (name: config)
├── templates.d/ # Templates as HashMap
└── notifiers.d/ # Notifiers as HashMap
Files are loaded alphabetically. Duplicate names across files cause startup failure.
Rule tasks ─────┐
├──► metrics::counter!() ──► Prometheus Recorder ──► /metrics
NotificationWorker ─┘
Metrics are emitted throughout the pipeline:
valerter_logs_matched_total - After parsing succeedsvalerter_alerts_throttled_total - When throttle blocksvalerter_alerts_sent_total - After successful notificationLog+Continue pattern: Errors in spawned tasks are logged, never propagated up.
// ✅ CORRECT
match process().await {
Ok(_) => continue,
Err(e) => {
tracing::error!(error = %e, "Processing failed");
tokio::time::sleep(backoff).await;
}
}
// ❌ NEVER DO THIS
tokio::spawn(async { process().unwrap(); }); // Silent crash
chmod 600 recommended (secrets in plaintext)body_html templates auto-escape variables (XSS prevention)tls.verify: true)