add deny missing docs
This commit is contained in:
@@ -1,18 +1,24 @@
|
|||||||
//! Schema for the alertmanager http POST requests that are sent to us
|
//! Schema for the Alertmanager HTTP POST webhook requests.
|
||||||
|
//!
|
||||||
|
//! See <https://prometheus.io/docs/alerting/latest/configuration/#webhook_config>
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// Alert status indicating whether an alert is firing or resolved.
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
|
/// The alert condition is no longer true.
|
||||||
Resolved,
|
Resolved,
|
||||||
|
/// The alert condition is currently true.
|
||||||
Firing,
|
Firing,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
impl Status {
|
||||||
|
/// Returns an emoji symbol representing the status.
|
||||||
pub fn symbol(&self) -> char {
|
pub fn symbol(&self) -> char {
|
||||||
match self {
|
match self {
|
||||||
Status::Firing => '🔴',
|
Status::Firing => '🔴',
|
||||||
@@ -21,41 +27,59 @@ impl Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The top-level webhook payload from Alertmanager.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AlertPost {
|
pub struct AlertPost {
|
||||||
// should be 4.0
|
/// Alertmanager API version (should be "4").
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
/// Key used to group alerts together.
|
||||||
pub group_key: String,
|
pub group_key: String,
|
||||||
|
/// Overall status of the alert group.
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
|
/// Name of the receiver that triggered this webhook.
|
||||||
pub receiver: String,
|
pub receiver: String,
|
||||||
|
/// Labels used to group the alerts.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub group_labels: BTreeMap<String, String>,
|
pub group_labels: BTreeMap<String, String>,
|
||||||
|
/// Labels common to all alerts in this group.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub common_labels: BTreeMap<String, String>,
|
pub common_labels: BTreeMap<String, String>,
|
||||||
|
/// Annotations common to all alerts in this group.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub common_annotations: BTreeMap<String, String>,
|
pub common_annotations: BTreeMap<String, String>,
|
||||||
|
/// URL of the Alertmanager instance.
|
||||||
#[serde(alias = "externalURL")]
|
#[serde(alias = "externalURL")]
|
||||||
pub external_url: String,
|
pub external_url: String,
|
||||||
|
/// List of alerts in this notification.
|
||||||
pub alerts: Vec<Alert>,
|
pub alerts: Vec<Alert>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An individual alert from Alertmanager.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Alert {
|
pub struct Alert {
|
||||||
|
/// Status of this specific alert.
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
|
/// Labels identifying the alert.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub labels: BTreeMap<String, String>,
|
pub labels: BTreeMap<String, String>,
|
||||||
|
/// Annotations providing additional information.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub annotations: BTreeMap<String, String>,
|
pub annotations: BTreeMap<String, String>,
|
||||||
|
/// Time when the alert started firing.
|
||||||
pub starts_at: DateTime<Utc>,
|
pub starts_at: DateTime<Utc>,
|
||||||
|
/// Time when the alert was resolved (zero value if still firing).
|
||||||
pub ends_at: DateTime<Utc>,
|
pub ends_at: DateTime<Utc>,
|
||||||
|
/// URL to the Prometheus graph for this alert's expression.
|
||||||
#[serde(alias = "generatorURL")]
|
#[serde(alias = "generatorURL")]
|
||||||
pub generator_url: String,
|
pub generator_url: String,
|
||||||
|
/// Unique identifier for this alert.
|
||||||
pub fingerprint: String,
|
pub fingerprint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Alert {
|
impl Alert {
|
||||||
|
/// Parse the PromQL expression from the generator URL.
|
||||||
pub fn parse_expr_from_generator_url(&self) -> Result<String, String> {
|
pub fn parse_expr_from_generator_url(&self) -> Result<String, String> {
|
||||||
let url = Url::parse(&self.generator_url).map_err(|err| err.to_string())?;
|
let url = Url::parse(&self.generator_url).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Gateway for bridging alerts and logs with Signal messenger.
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use crate::jsonrpc::connect_ipc;
|
use crate::jsonrpc::connect_ipc;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -38,28 +40,32 @@ use log_handler::{LogHandler, LogHandlerConfig};
|
|||||||
mod rate_limiter;
|
mod rate_limiter;
|
||||||
use rate_limiter::{MultiRateLimiter, RateThreshold, SourceLocationRateLimiter};
|
use rate_limiter::{MultiRateLimiter, RateThreshold, SourceLocationRateLimiter};
|
||||||
|
|
||||||
|
/// Configuration for the gateway.
|
||||||
#[derive(Conf, Debug)]
|
#[derive(Conf, Debug)]
|
||||||
#[cfg_attr(unix, conf(one_of_fields(signal_cli_tcp_addr, signal_cli_socket_path)))]
|
#[cfg_attr(unix, conf(one_of_fields(signal_cli_tcp_addr, signal_cli_socket_path)))]
|
||||||
#[cfg_attr(not(unix), conf(one_of_fields(signal_cli_tcp_addr)))]
|
#[cfg_attr(not(unix), conf(one_of_fields(signal_cli_tcp_addr)))]
|
||||||
pub struct GatewayConfig {
|
pub struct GatewayConfig {
|
||||||
/// TCP address of signal-cli JSON-RPC server
|
/// TCP address of signal-cli JSON-RPC server.
|
||||||
#[conf(long, env)]
|
#[conf(long, env)]
|
||||||
pub signal_cli_tcp_addr: Option<SocketAddr>,
|
pub signal_cli_tcp_addr: Option<SocketAddr>,
|
||||||
/// Unix socket path of signal-cli JSON-RPC server
|
/// Unix socket path of signal-cli JSON-RPC server.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[conf(long, env)]
|
#[conf(long, env)]
|
||||||
pub signal_cli_socket_path: Option<PathBuf>,
|
pub signal_cli_socket_path: Option<PathBuf>,
|
||||||
|
/// The phone number or UUID of the Signal account to use.
|
||||||
#[conf(long, env)]
|
#[conf(long, env)]
|
||||||
pub signal_account: String,
|
pub signal_account: String,
|
||||||
/// Admin UUIDs mapped to their safety numbers (can be empty).
|
/// Admin UUIDs mapped to their safety numbers (can be empty).
|
||||||
/// Example: {"uuid1": ["12345...", "67890..."], "uuid2": []}
|
/// Example: `{"uuid1": ["12345...", "67890..."], "uuid2": []}`
|
||||||
#[conf(long, env, value_parser = serde_json::from_str)]
|
#[conf(long, env, value_parser = serde_json::from_str)]
|
||||||
pub admin_safety_numbers: HashMap<String, Vec<String>>,
|
pub admin_safety_numbers: HashMap<String, Vec<String>>,
|
||||||
/// If set, alerts are sent to this group instead of individual admins
|
/// If set, alerts are sent to this group instead of individual admins.
|
||||||
#[conf(long, env)]
|
#[conf(long, env)]
|
||||||
pub alert_group_id: Option<String>,
|
pub alert_group_id: Option<String>,
|
||||||
|
/// Prometheus server configuration for querying metrics.
|
||||||
#[conf(flatten)]
|
#[conf(flatten)]
|
||||||
pub prometheus: Option<PrometheusConfig>,
|
pub prometheus: Option<PrometheusConfig>,
|
||||||
|
/// Log handler configuration for processing log messages.
|
||||||
#[conf(flatten)]
|
#[conf(flatten)]
|
||||||
pub log_handler: LogHandlerConfig,
|
pub log_handler: LogHandlerConfig,
|
||||||
}
|
}
|
||||||
@@ -187,6 +193,7 @@ pub struct Gateway {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Gateway {
|
impl Gateway {
|
||||||
|
/// Create a new gateway with the given configuration.
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
config: GatewayConfig,
|
config: GatewayConfig,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
@@ -212,6 +219,7 @@ impl Gateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run the gateway main loop, reconnecting to signal-cli on errors.
|
||||||
pub async fn run(&self) {
|
pub async fn run(&self) {
|
||||||
loop {
|
loop {
|
||||||
if self.token.is_cancelled() {
|
if self.token.is_cancelled() {
|
||||||
@@ -483,7 +491,7 @@ impl Gateway {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler function that processes incoming http requests (push's from alertmanager expected)
|
/// Handle an incoming HTTP request (e.g., webhooks from Alertmanager).
|
||||||
pub async fn handle_http_request<B>(&self, req: Request<B>) -> Result<Response<String>, String>
|
pub async fn handle_http_request<B>(&self, req: Request<B>) -> Result<Response<String>, String>
|
||||||
where
|
where
|
||||||
B: Body + Send,
|
B: Body + Send,
|
||||||
@@ -812,6 +820,7 @@ impl Gateway {
|
|||||||
Ok(text)
|
Ok(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Process an incoming log message, buffering it and potentially triggering an alert.
|
||||||
pub async fn handle_log_message(&self, log_msg: impl Into<LogMessage>) {
|
pub async fn handle_log_message(&self, log_msg: impl Into<LogMessage>) {
|
||||||
let log_msg = log_msg.into();
|
let log_msg = log_msg.into();
|
||||||
let origin = Origin::from(&log_msg);
|
let origin = Origin::from(&log_msg);
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
//! Signal Gateway library for bridging alertmanager and logging with Signal messenger.
|
||||||
|
//!
|
||||||
|
//! This crate provides the core functionality for receiving alerts and log messages
|
||||||
|
//! and forwarding them to Signal messenger via signal-cli.
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
pub mod alertmanager;
|
pub mod alertmanager;
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
pub mod message_handler;
|
pub mod message_handler;
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
//! Log message schema used by this crate
|
//! Log message schema and types.
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// Log severity level, following syslog conventions.
|
||||||
|
///
|
||||||
|
/// Lower values indicate higher severity. The ordering allows comparisons
|
||||||
|
/// like `level <= Level::ERROR` to match ERROR, CRITICAL, ALERT, and EMERGENCY.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum Level {
|
pub enum Level {
|
||||||
|
/// System is unusable.
|
||||||
EMERGENCY = 0,
|
EMERGENCY = 0,
|
||||||
|
/// Action must be taken immediately.
|
||||||
ALERT = 1,
|
ALERT = 1,
|
||||||
|
/// Critical conditions.
|
||||||
CRITICAL = 2,
|
CRITICAL = 2,
|
||||||
|
/// Error conditions.
|
||||||
ERROR = 3,
|
ERROR = 3,
|
||||||
|
/// Warning conditions.
|
||||||
WARNING = 4,
|
WARNING = 4,
|
||||||
|
/// Normal but significant condition.
|
||||||
NOTICE = 5,
|
NOTICE = 5,
|
||||||
|
/// Informational messages.
|
||||||
INFO = 6,
|
INFO = 6,
|
||||||
|
/// Debug-level messages.
|
||||||
DEBUG = 7,
|
DEBUG = 7,
|
||||||
|
/// Trace-level messages (more verbose than debug).
|
||||||
TRACE = 8,
|
TRACE = 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,21 +46,32 @@ impl Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A structured log message.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LogMessage {
|
pub struct LogMessage {
|
||||||
|
/// Severity level of the message.
|
||||||
pub level: Level,
|
pub level: Level,
|
||||||
|
/// Unix timestamp in seconds.
|
||||||
pub timestamp: Option<i64>,
|
pub timestamp: Option<i64>,
|
||||||
|
/// Nanosecond component of the timestamp.
|
||||||
pub timestamp_nanos: u32,
|
pub timestamp_nanos: u32,
|
||||||
|
/// Hostname where the log originated.
|
||||||
pub hostname: Option<Box<str>>,
|
pub hostname: Option<Box<str>>,
|
||||||
|
/// Application name that generated the log.
|
||||||
pub appname: Option<Box<str>>,
|
pub appname: Option<Box<str>>,
|
||||||
|
/// The log message text.
|
||||||
pub msg: Box<str>,
|
pub msg: Box<str>,
|
||||||
|
/// Module path (e.g., `myapp::server::handler`).
|
||||||
pub module_path: Option<Box<str>>,
|
pub module_path: Option<Box<str>>,
|
||||||
|
/// Source file path.
|
||||||
pub file: Option<Box<str>>,
|
pub file: Option<Box<str>>,
|
||||||
|
/// Line number in the source file.
|
||||||
pub line: Option<Box<str>>,
|
pub line: Option<Box<str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogMessage {
|
impl LogMessage {
|
||||||
|
/// Create a builder for constructing a log message.
|
||||||
pub fn builder(level: Level, msg: impl Into<Box<str>>) -> LogMessageBuilder {
|
pub fn builder(level: Level, msg: impl Into<Box<str>>) -> LogMessageBuilder {
|
||||||
LogMessageBuilder {
|
LogMessageBuilder {
|
||||||
level,
|
level,
|
||||||
@@ -63,6 +87,7 @@ impl LogMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builder for constructing [`LogMessage`] instances.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LogMessageBuilder {
|
pub struct LogMessageBuilder {
|
||||||
level: Level,
|
level: Level,
|
||||||
@@ -77,41 +102,49 @@ pub struct LogMessageBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LogMessageBuilder {
|
impl LogMessageBuilder {
|
||||||
|
/// Set the Unix timestamp in seconds.
|
||||||
pub fn timestamp(mut self, ts: i64) -> Self {
|
pub fn timestamp(mut self, ts: i64) -> Self {
|
||||||
self.timestamp = Some(ts);
|
self.timestamp = Some(ts);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the nanosecond component of the timestamp.
|
||||||
pub fn timestamp_nanos(mut self, nanos: u32) -> Self {
|
pub fn timestamp_nanos(mut self, nanos: u32) -> Self {
|
||||||
self.timestamp_nanos = nanos;
|
self.timestamp_nanos = nanos;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the hostname.
|
||||||
pub fn hostname(mut self, hostname: impl Into<Box<str>>) -> Self {
|
pub fn hostname(mut self, hostname: impl Into<Box<str>>) -> Self {
|
||||||
self.hostname = Some(hostname.into());
|
self.hostname = Some(hostname.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the application name.
|
||||||
pub fn appname(mut self, appname: impl Into<Box<str>>) -> Self {
|
pub fn appname(mut self, appname: impl Into<Box<str>>) -> Self {
|
||||||
self.appname = Some(appname.into());
|
self.appname = Some(appname.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the module path.
|
||||||
pub fn module_path(mut self, module_path: impl Into<Box<str>>) -> Self {
|
pub fn module_path(mut self, module_path: impl Into<Box<str>>) -> Self {
|
||||||
self.module_path = Some(module_path.into());
|
self.module_path = Some(module_path.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the source file path.
|
||||||
pub fn file(mut self, file: impl Into<Box<str>>) -> Self {
|
pub fn file(mut self, file: impl Into<Box<str>>) -> Self {
|
||||||
self.file = Some(file.into());
|
self.file = Some(file.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the line number.
|
||||||
pub fn line(mut self, line: impl Into<Box<str>>) -> Self {
|
pub fn line(mut self, line: impl Into<Box<str>>) -> Self {
|
||||||
self.line = Some(line.into());
|
self.line = Some(line.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build the log message.
|
||||||
pub fn build(self) -> LogMessage {
|
pub fn build(self) -> LogMessage {
|
||||||
LogMessage {
|
LogMessage {
|
||||||
level: self.level,
|
level: self.level,
|
||||||
@@ -134,10 +167,13 @@ impl From<LogMessageBuilder> for LogMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies the source of log messages (app name + host).
|
/// Identifies the source of log messages (app name + host).
|
||||||
|
///
|
||||||
/// Used to separate log buffers and rate limiters per source.
|
/// Used to separate log buffers and rate limiters per source.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||||
pub struct Origin {
|
pub struct Origin {
|
||||||
|
/// Application name.
|
||||||
pub app: Box<str>,
|
pub app: Box<str>,
|
||||||
|
/// Hostname.
|
||||||
pub host: Box<str>,
|
pub host: Box<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,15 +210,21 @@ impl Origin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filter criteria for matching log messages
|
/// Filter criteria for matching log messages.
|
||||||
|
///
|
||||||
|
/// All non-empty fields must match for the filter to pass.
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
pub struct LogFilter {
|
pub struct LogFilter {
|
||||||
|
/// If non-empty, the message must contain this substring.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub msg_contains: String,
|
pub msg_contains: String,
|
||||||
|
/// If non-empty, the module path must equal this value exactly.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub module_equals: String,
|
pub module_equals: String,
|
||||||
|
/// If non-empty, the file path must equal this value exactly.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub file_equals: String,
|
pub file_equals: String,
|
||||||
|
/// If non-empty, the line number must equal this value exactly.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub line_equals: String,
|
pub line_equals: String,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user