This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
4
2
[PATCH bad-news v2 1/4] config: extract Config into its own file
---
src/config.rs | 23 +++++++++++++++++++++++
src/main.rs | 24 ++ ----------------------
2 files changed, 25 insertions(+), 22 deletions(-)
create mode 100644 src/config.rs
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..9d99a37
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,23 @@
+ use matrix_sdk::identifiers::RoomId;
+ use serde::Deserialize;
+ use std::collections::HashSet;
+ use std::path::PathBuf;
+ use url::Url;
+
+ /// Holds the configuration for the bot.
+ #[derive(Clone, Deserialize)]
+ pub struct Config {
+ /// The URL for the homeserver we should connect to
+ pub homeserver: Url,
+ /// The bot's account username
+ pub username: String,
+ /// The bot's account password
+ pub password: String,
+ /// Path to a directory where the bot will store Matrix state and current session information.
+ pub state_dir: PathBuf,
+ /// ID of the Matrix room where the bot should post messages. The bot will only accept
+ /// invitations to this room.
+ pub room_id: RoomId,
+ /// Units to watch for logs
+ pub units: HashSet<String>,
+ }
diff --git a/src/main.rs b/src/main.rs
index dee3102..877d58e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,20 +1,18 @@
use std::{
- collections::HashSet,
fs::File,
io::{self, BufReader},
path::PathBuf,
};
use clap::Clap;
- use matrix_sdk::identifiers::RoomId;
- use serde::Deserialize;
use thiserror::Error;
- use url::Url;
mod autojoin;
mod bot;
+ mod config;
use bot::BadNewsBot;
+ use config::Config;
#[derive(Error, Debug)]
enum BadNewsError {
@@ -32,24 +30,6 @@ struct Opts {
config: PathBuf,
}
- /// Holds the configuration for the bot.
- #[derive(Clone, Deserialize)]
- pub struct Config {
- /// The URL for the homeserver we should connect to
- homeserver: Url,
- /// The bot's account username
- username: String,
- /// The bot's account password
- password: String,
- /// Path to a directory where the bot will store Matrix state and current session information.
- state_dir: PathBuf,
- /// ID of the Matrix room where the bot should post messages. The bot will only accept
- /// invitations to this room.
- room_id: RoomId,
- /// Units to watch for logs
- units: HashSet<String>,
- }
-
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
--
2.31.1
[PATCH bad-news v2 2/4] config: add optional filter option for units
This allows the user to write:
```yaml
units:
- foo.service
- bar.service
- name: baz.service
filter: "^Error: .*$"
```
So a unit can be provided as a string, or as a map which contains both
`name` and `filter`.
---
Cargo.lock | 7 +++++
Cargo.toml | 1 +
src/bot.rs | 2 + -
src/config.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ --
4 files changed, 82 insertions(+), 4 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index c41772e..e2fbccb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -139,6 +139,7 @@ dependencies = [
"tokio",
"tracing-subscriber",
"url",
+ "void",
]
[[package]]
@@ -2365,6 +2366,12 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+ [[package]]
+ name = "void"
+ version = "1.0.2"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
[[package]]
name = "want"
version = "0.3.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7fe7e51..eb687c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,6 +17,7 @@ serde_yaml = "0.8"
serde = "1.0"
systemd = "0.8"
thiserror = "1.0"
+ void = "1"
[dependencies.matrix-sdk]
git = "https://github.com/matrix-org/matrix-rust-sdk"
diff --git a/src/bot.rs b/src/bot.rs
index b2a6387..aa97d32 100644
--- a/src/bot.rs
+++ b/src/bot.rs
@@ -100,7 +100,7 @@ impl BadNewsBot {
const KEY_MESSAGE: &str = "MESSAGE";
if let Some(unit) = record.get(KEY_UNIT) {
- if !self.config.units.contains(unit) {
+ if !self.config.units.iter().map(|u| &u.name).any(|name| name == unit) {
return;
}
diff --git a/src/config.rs b/src/config.rs
index 9d99a37..0a06c3a 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,8 +1,12 @@
use matrix_sdk::identifiers::RoomId;
- use serde::Deserialize;
- use std::collections::HashSet;
+ use serde::de::{self, MapAccess, Visitor};
+ use serde::{Deserialize, Deserializer};
+ use std::fmt;
+ use std::marker::PhantomData;
use std::path::PathBuf;
+ use std::str::FromStr;
use url::Url;
+ use void::Void;
/// Holds the configuration for the bot.
#[derive(Clone, Deserialize)]
@@ -19,5 +23,71 @@ pub struct Config {
/// invitations to this room.
pub room_id: RoomId,
/// Units to watch for logs
- pub units: HashSet<String>,
+ pub units: Vec<Unit>,
+ }
+
+ /// Holds a single unit's configuration.
+ #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+ #[serde(from = "SerializedUnit")]
+ pub struct Unit {
+ /// Can be serialized from a string only instead of a map.
+ pub name: String,
+ /// Regex to filter each line read from the unit's logs.
+ pub filter: Option<String>, // FIXME: regex
+ }
+
+ #[derive(Debug, Deserialize)]
+ #[serde(transparent)]
+ struct SerializedUnit(#[serde(deserialize_with = "unit_name_or_struct")] Unit);
+
+ impl From<SerializedUnit> for Unit {
+ fn from(s: SerializedUnit) -> Self {
+ s.0
+ }
+ }
+
+ impl FromStr for Unit {
+ type Err = Void;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(Unit {
+ name: s.to_string(),
+ filter: None,
+ })
+ }
+ }
+
+ fn unit_name_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+ where
+ T: Deserialize<'de> + FromStr<Err = Void>,
+ D: Deserializer<'de>,
+ {
+ struct StringOrStruct<T>(PhantomData<fn() -> T>);
+
+ impl<'de, T> Visitor<'de> for StringOrStruct<T>
+ where
+ T: Deserialize<'de> + FromStr<Err = Void>,
+ {
+ type Value = T;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("string or map")
+ }
+
+ fn visit_str<E>(self, value: &str) -> Result<T, E>
+ where
+ E: de::Error,
+ {
+ Ok(FromStr::from_str(value).unwrap())
+ }
+
+ fn visit_map<M>(self, map: M) -> Result<T, M::Error>
+ where
+ M: MapAccess<'de>,
+ {
+ Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
+ }
+ }
+
+ deserializer.deserialize_any(StringOrStruct(PhantomData))
}
--
2.31.1
[PATCH bad-news v2 3/4] config: store and parse 'filter' as regex
---
Cargo.lock | 24 ++++++++++++++++++++++++
Cargo.toml | 2 ++
src/config.rs | 14 ++++++++++++ --
3 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index e2fbccb..a615ff6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -66,6 +66,15 @@ dependencies = [
"opaque-debug",
]
+ [[package]]
+ name = "aho-corasick"
+ version = "0.7.15"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+ dependencies = [
+ "memchr",
+ ]
+
[[package]]
name = "ansi_term"
version = "0.12.1"
@@ -132,7 +141,9 @@ dependencies = [
"clap",
"futures",
"matrix-sdk",
+ "regex",
"serde",
+ "serde_regex",
"serde_yaml",
"systemd",
"thiserror",
@@ -1439,7 +1450,10 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
+ "aho-corasick",
+ "memchr",
"regex-syntax",
+ "thread_local",
]
[[package]]
@@ -1774,6 +1788,16 @@ dependencies = [
"serde",
]
+ [[package]]
+ name = "serde_regex"
+ version = "1.1.0"
+ source = "registry+https://github.com/rust-lang/crates.io-index"
+ checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf"
+ dependencies = [
+ "regex",
+ "serde",
+ ]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.0"
diff --git a/Cargo.toml b/Cargo.toml
index eb687c7..8004b32 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,9 +10,11 @@ edition = "2018"
anyhow = "1.0"
clap = "3.0.0-beta.2"
futures = "0.3"
+ regex = "1"
tokio = { version = "1", features = [ "full" ] }
tracing-subscriber = "0.2"
url = { version = "2.2", features = [ "serde" ] }
+ serde_regex = "1"
serde_yaml = "0.8"
serde = "1.0"
systemd = "0.8"
diff --git a/src/config.rs b/src/config.rs
index 0a06c3a..0a4e40b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,4 +1,5 @@
use matrix_sdk::identifiers::RoomId;
+ use regex::Regex;
use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use std::fmt;
@@ -27,15 +28,24 @@ pub struct Config {
}
/// Holds a single unit's configuration.
- #[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+ #[derive(Clone, Debug, Deserialize)]
#[serde(from = "SerializedUnit")]
pub struct Unit {
/// Can be serialized from a string only instead of a map.
pub name: String,
/// Regex to filter each line read from the unit's logs.
- pub filter: Option<String>, // FIXME: regex
+ #[serde(with = "serde_regex")]
+ pub filter: Option<Regex>,
}
+ impl PartialEq for Unit {
+ fn eq(&self, other: &Self) -> bool {
+ self.name == other.name
+ }
+ }
+
+ impl Eq for Unit {}
+
#[derive(Debug, Deserialize)]
#[serde(transparent)]
struct SerializedUnit(#[serde(deserialize_with = "unit_name_or_struct")] Unit);
--
2.31.1
[PATCH bad-news v2 4/4] bot: apply service filters
---
src/bot.rs | 10 +++++++++ -
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/bot.rs b/src/bot.rs
index aa97d32..eb833db 100644
--- a/src/bot.rs
+++ b/src/bot.rs
@@ -100,11 +100,19 @@ impl BadNewsBot {
const KEY_MESSAGE: &str = "MESSAGE";
if let Some(unit) = record.get(KEY_UNIT) {
- if !self.config.units.iter().map(|u| &u.name).any(|name| name == unit) {
+ let unit_config = self.config.units.iter().find(|u| &u.name == unit);
+ if unit_config.is_none() {
return;
}
+ let unit_config = unit_config.unwrap();
let message = record.get(KEY_MESSAGE);
+ if let Some(filter) = &unit_config.filter {
+ if message.is_none() || !filter.is_match(message.unwrap()) {
+ return;
+ }
+ }
+
let message = format!(
"[{}] {}",
unit.strip_suffix(".service").unwrap_or(unit),
--
2.31.1
Re: [PATCH bad-news v2 4/4] bot: apply service filters
Thanks, applied!
--
Antoine Martin