chanlog: filter each log channel by snomask category; repeatable so different snomasks route to different channels
This commit is contained in:
parent
7cb9256ff5
commit
b8e24e2071
3 changed files with 64 additions and 18 deletions
|
|
@ -293,8 +293,13 @@ amu_target = both
|
||||||
# hidewhois_hide_idle = yes # hide 317
|
# hidewhois_hide_idle = yes # hide 317
|
||||||
# hidewhois_hide_secure = yes # hide 671
|
# hidewhois_hide_secure = yes # hide 671
|
||||||
# --- chanlog: mirror the oper server-notice stream into a channel so
|
# --- chanlog: mirror the oper server-notice stream into a channel so
|
||||||
# staff can watch it in a normal window. Set the channel (create/keep it opped):
|
# staff can watch it in a normal window. Set the channel (create/keep it opped).
|
||||||
# chanlog = #snotices
|
# Add snomask category letters after the channel to log only those categories
|
||||||
|
# (x x-lines, d dnsbl, c connects, o oper, q quit, k kill, …); no letters logs
|
||||||
|
# everything. Repeatable, so different snomasks can go to different channels:
|
||||||
|
# chanlog = #snotices # everything
|
||||||
|
# chanlog = #bans xdk # only x-lines, dnsbl hits, and kills
|
||||||
|
# chanlog = #conns cq # only connects and quits
|
||||||
# --- extbanbanlist: no config — adds the matching extban
|
# --- extbanbanlist: no config — adds the matching extban
|
||||||
# `b:<#channel>`, so `+b b:#staff` catches everyone banned in #staff (shares a
|
# `b:<#channel>`, so `+b b:#staff` catches everyone banned in #staff (shares a
|
||||||
# ban list between channels).
|
# ban list between channels).
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,38 @@
|
||||||
//! chanlog — mirror server notices (the `snotice` stream opers see with +s) into a
|
//! chanlog — mirror server notices (the `snotice` stream opers see with +s) into a
|
||||||
//! channel, so staff can watch the log in a normal channel window. Off unless
|
//! channel, so staff can watch the log in a normal channel window. Off unless at
|
||||||
//! `chanlog = #channel` is configured.
|
//! least one `chanlog = #channel [snomask-letters]` is configured.
|
||||||
|
//!
|
||||||
|
//! Each entry may carry a snomask filter — the category letters (the same letters
|
||||||
|
//! as the `+s` snomask set: `x` x-lines, `d` dnsbl, `c` connects, `o` oper, …) to
|
||||||
|
//! send there. With no letters, every category goes to that channel (the original
|
||||||
|
//! behaviour). The key is repeatable, so different snomasks can be routed to
|
||||||
|
//! different channels: `chanlog = #xlog x` / `chanlog = #conns cq`.
|
||||||
|
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
|
|
||||||
/// Tee `msg` to the configured chanlog channel (if set and it exists). Called at
|
/// Tee a snomask-`cat` server notice `msg` to every configured chanlog channel
|
||||||
/// the tail of `Server::snotice`. Read-only over server state, so it can't loop.
|
/// whose filter admits `cat`. Called at the tail of `Server::snotice_c`. Read-only
|
||||||
pub fn tee(s: &Server, msg: &str) {
|
/// over server state, so it can't loop.
|
||||||
let Some(chan) = s.conf("chanlog") else {
|
pub fn tee(s: &Server, cat: char, msg: &str) {
|
||||||
return;
|
for spec in s.conf_all("chanlog") {
|
||||||
|
let mut parts = spec.split_whitespace();
|
||||||
|
let Some(chan) = parts.next() else {
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
// Optional snomask filter: only these category letters go here; absent means
|
||||||
|
// every category (so a bare `chanlog = #channel` logs everything, as before).
|
||||||
|
if let Some(masks) = parts.next() {
|
||||||
|
if !masks.contains(cat) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
let key = chan.to_ascii_lowercase();
|
let key = chan.to_ascii_lowercase();
|
||||||
if !s.channels.contains_key(&key) {
|
if !s.channels.contains_key(&key) {
|
||||||
return; // channel not created yet — nothing to log into
|
continue; // channel not created yet — nothing to log into
|
||||||
}
|
}
|
||||||
// to_channel builds the line once and shares it by Arc across members (and adds
|
// to_channel builds the line once and shares it by Arc across members (and
|
||||||
// the server-time tag per recipient) instead of cloning a String per member.
|
// adds the server-time tag per recipient) instead of cloning per member.
|
||||||
let line = format!(":{} NOTICE {chan} :{msg}", s.name);
|
let line = format!(":{} NOTICE {chan} :{msg}", s.name);
|
||||||
s.to_channel(&key, &line, None);
|
s.to_channel(&key, &line, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1034,7 +1034,7 @@ impl Server {
|
||||||
for o in opers {
|
for o in opers {
|
||||||
self.deliver_server_notice(o, msg, &jval);
|
self.deliver_server_notice(o, msg, &jval);
|
||||||
}
|
}
|
||||||
crate::modules::chanlog::tee(self, msg);
|
crate::modules::chanlog::tee(self, cat, msg);
|
||||||
crate::modules::syslog::tee(self, msg);
|
crate::modules::syslog::tee(self, msg);
|
||||||
crate::modules::log_json::tee(self, msg);
|
crate::modules::log_json::tee(self, msg);
|
||||||
}
|
}
|
||||||
|
|
@ -1655,6 +1655,30 @@ mod tests {
|
||||||
assert!(joined.contains("XLINE: Z-line on 192.0.2.5 expired"), "expire: {joined}");
|
assert!(joined.contains("XLINE: Z-line on 192.0.2.5 expired"), "expire: {joined}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chanlog_routes_by_snomask() {
|
||||||
|
use crate::channels::{Channel, Member};
|
||||||
|
let mut s = srv();
|
||||||
|
// #xlog takes only x-line (x) notices; #all takes every category
|
||||||
|
s.raw_config.insert("chanlog".to_string(), vec!["#xlog x".to_string(), "#all".to_string()]);
|
||||||
|
let rx = add_user(&mut s, 1, "logbot");
|
||||||
|
for name in ["#xlog", "#all"] {
|
||||||
|
let mut c = Channel::new(name);
|
||||||
|
c.members.insert(1, Member::default());
|
||||||
|
s.channels.insert(name.to_string(), c);
|
||||||
|
}
|
||||||
|
s.snotice_c('x', "XLINEMSG");
|
||||||
|
s.snotice_c('c', "CONNMSG");
|
||||||
|
let lines: Vec<String> = std::iter::from_fn(|| rx.try_recv().ok()).collect();
|
||||||
|
let logged = |chan: &str, needle: &str| {
|
||||||
|
lines.iter().any(|l| l.contains(&format!("NOTICE {chan} :")) && l.contains(needle))
|
||||||
|
};
|
||||||
|
assert!(logged("#xlog", "XLINEMSG"), "x-line notice goes to #xlog: {lines:?}");
|
||||||
|
assert!(logged("#all", "XLINEMSG"), "x-line notice goes to #all: {lines:?}");
|
||||||
|
assert!(!logged("#xlog", "CONNMSG"), "connect notice filtered out of #xlog: {lines:?}");
|
||||||
|
assert!(logged("#all", "CONNMSG"), "connect notice goes to #all: {lines:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn banned_user_message_shows_expiry() {
|
fn banned_user_message_shows_expiry() {
|
||||||
let mut s = srv();
|
let mut s = srv();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue