add deny missing docs

This commit is contained in:
Chris Beck
2025-12-05 11:51:15 -07:00
parent b3a0894221
commit 5b98e74f92
4 changed files with 91 additions and 9 deletions
+26 -2
View File
@@ -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 serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use url::Url;
/// Alert status indicating whether an alert is firing or resolved.
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Status {
/// The alert condition is no longer true.
Resolved,
/// The alert condition is currently true.
Firing,
}
impl Status {
/// Returns an emoji symbol representing the status.
pub fn symbol(&self) -> char {
match self {
Status::Firing => '🔴',
@@ -21,41 +27,59 @@ impl Status {
}
}
/// The top-level webhook payload from Alertmanager.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AlertPost {
// should be 4.0
/// Alertmanager API version (should be "4").
pub version: String,
/// Key used to group alerts together.
pub group_key: String,
/// Overall status of the alert group.
pub status: Status,
/// Name of the receiver that triggered this webhook.
pub receiver: String,
/// Labels used to group the alerts.
#[serde(default)]
pub group_labels: BTreeMap<String, String>,
/// Labels common to all alerts in this group.
#[serde(default)]
pub common_labels: BTreeMap<String, String>,
/// Annotations common to all alerts in this group.
#[serde(default)]
pub common_annotations: BTreeMap<String, String>,
/// URL of the Alertmanager instance.
#[serde(alias = "externalURL")]
pub external_url: String,
/// List of alerts in this notification.
pub alerts: Vec<Alert>,
}
/// An individual alert from Alertmanager.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Alert {
/// Status of this specific alert.
pub status: Status,
/// Labels identifying the alert.
#[serde(default)]
pub labels: BTreeMap<String, String>,
/// Annotations providing additional information.
#[serde(default)]
pub annotations: BTreeMap<String, String>,
/// Time when the alert started firing.
pub starts_at: DateTime<Utc>,
/// Time when the alert was resolved (zero value if still firing).
pub ends_at: DateTime<Utc>,
/// URL to the Prometheus graph for this alert's expression.
#[serde(alias = "generatorURL")]
pub generator_url: String,
/// Unique identifier for this alert.
pub fingerprint: String,
}
impl Alert {
/// Parse the PromQL expression from the generator URL.
pub fn parse_expr_from_generator_url(&self) -> Result<String, String> {
let url = Url::parse(&self.generator_url).map_err(|err| err.to_string())?;
+14 -5
View File
@@ -1,3 +1,5 @@
//! Gateway for bridging alerts and logs with Signal messenger.
#[cfg(unix)]
use crate::jsonrpc::connect_ipc;
use crate::{
@@ -38,28 +40,32 @@ use log_handler::{LogHandler, LogHandlerConfig};
mod rate_limiter;
use rate_limiter::{MultiRateLimiter, RateThreshold, SourceLocationRateLimiter};
/// Configuration for the gateway.
#[derive(Conf, Debug)]
#[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)))]
pub struct GatewayConfig {
/// TCP address of signal-cli JSON-RPC server
/// TCP address of signal-cli JSON-RPC server.
#[conf(long, env)]
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)]
#[conf(long, env)]
pub signal_cli_socket_path: Option<PathBuf>,
/// The phone number or UUID of the Signal account to use.
#[conf(long, env)]
pub signal_account: String,
/// 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)]
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)]
pub alert_group_id: Option<String>,
/// Prometheus server configuration for querying metrics.
#[conf(flatten)]
pub prometheus: Option<PrometheusConfig>,
/// Log handler configuration for processing log messages.
#[conf(flatten)]
pub log_handler: LogHandlerConfig,
}
@@ -187,6 +193,7 @@ pub struct Gateway {
}
impl Gateway {
/// Create a new gateway with the given configuration.
pub async fn new(
config: GatewayConfig,
token: CancellationToken,
@@ -212,6 +219,7 @@ impl Gateway {
}
}
/// Run the gateway main loop, reconnecting to signal-cli on errors.
pub async fn run(&self) {
loop {
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>
where
B: Body + Send,
@@ -812,6 +820,7 @@ impl Gateway {
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>) {
let log_msg = log_msg.into();
let origin = Origin::from(&log_msg);
+7
View File
@@ -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 gateway;
pub mod message_handler;
+44 -2
View File
@@ -1,18 +1,31 @@
//! Log message schema used by this crate
//! Log message schema and types.
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]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Level {
/// System is unusable.
EMERGENCY = 0,
/// Action must be taken immediately.
ALERT = 1,
/// Critical conditions.
CRITICAL = 2,
/// Error conditions.
ERROR = 3,
/// Warning conditions.
WARNING = 4,
/// Normal but significant condition.
NOTICE = 5,
/// Informational messages.
INFO = 6,
/// Debug-level messages.
DEBUG = 7,
/// Trace-level messages (more verbose than debug).
TRACE = 8,
}
@@ -33,21 +46,32 @@ impl Level {
}
}
/// A structured log message.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct LogMessage {
/// Severity level of the message.
pub level: Level,
/// Unix timestamp in seconds.
pub timestamp: Option<i64>,
/// Nanosecond component of the timestamp.
pub timestamp_nanos: u32,
/// Hostname where the log originated.
pub hostname: Option<Box<str>>,
/// Application name that generated the log.
pub appname: Option<Box<str>>,
/// The log message text.
pub msg: Box<str>,
/// Module path (e.g., `myapp::server::handler`).
pub module_path: Option<Box<str>>,
/// Source file path.
pub file: Option<Box<str>>,
/// Line number in the source file.
pub line: Option<Box<str>>,
}
impl LogMessage {
/// Create a builder for constructing a log message.
pub fn builder(level: Level, msg: impl Into<Box<str>>) -> LogMessageBuilder {
LogMessageBuilder {
level,
@@ -63,6 +87,7 @@ impl LogMessage {
}
}
/// Builder for constructing [`LogMessage`] instances.
#[derive(Clone, Debug)]
pub struct LogMessageBuilder {
level: Level,
@@ -77,41 +102,49 @@ pub struct LogMessageBuilder {
}
impl LogMessageBuilder {
/// Set the Unix timestamp in seconds.
pub fn timestamp(mut self, ts: i64) -> Self {
self.timestamp = Some(ts);
self
}
/// Set the nanosecond component of the timestamp.
pub fn timestamp_nanos(mut self, nanos: u32) -> Self {
self.timestamp_nanos = nanos;
self
}
/// Set the hostname.
pub fn hostname(mut self, hostname: impl Into<Box<str>>) -> Self {
self.hostname = Some(hostname.into());
self
}
/// Set the application name.
pub fn appname(mut self, appname: impl Into<Box<str>>) -> Self {
self.appname = Some(appname.into());
self
}
/// Set the module path.
pub fn module_path(mut self, module_path: impl Into<Box<str>>) -> Self {
self.module_path = Some(module_path.into());
self
}
/// Set the source file path.
pub fn file(mut self, file: impl Into<Box<str>>) -> Self {
self.file = Some(file.into());
self
}
/// Set the line number.
pub fn line(mut self, line: impl Into<Box<str>>) -> Self {
self.line = Some(line.into());
self
}
/// Build the log message.
pub fn build(self) -> LogMessage {
LogMessage {
level: self.level,
@@ -134,10 +167,13 @@ impl From<LogMessageBuilder> for LogMessage {
}
/// Identifies the source of log messages (app name + host).
///
/// Used to separate log buffers and rate limiters per source.
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Origin {
/// Application name.
pub app: Box<str>,
/// Hostname.
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)]
pub struct LogFilter {
/// If non-empty, the message must contain this substring.
#[serde(default)]
pub msg_contains: String,
/// If non-empty, the module path must equal this value exactly.
#[serde(default)]
pub module_equals: String,
/// If non-empty, the file path must equal this value exactly.
#[serde(default)]
pub file_equals: String,
/// If non-empty, the line number must equal this value exactly.
#[serde(default)]
pub line_equals: String,
}