use serde::Deserialize;
use super::RulesError;
const PATTERN_SIZE_LIMIT: usize = 1 << 20;
const LINE_CAP: usize = 1_000;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum LogStream {
Out,
Err,
#[default]
Both,
}
impl LogStream {
fn reads_out(self) -> bool {
matches!(self, Self::Out | Self::Both)
}
fn reads_err(self) -> bool {
matches!(self, Self::Err | Self::Both)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Source {
Out,
Err,
}
impl Source {
fn word(self) -> &'static str {
match self {
Self::Out => "stdout",
Self::Err => "stderr",
}
}
}
#[derive(Debug)]
pub(super) struct LogRule {
pub(super) idx: usize,
stream: LogStream,
regex: regex::Regex,
}
impl LogRule {
pub(super) fn compile(
index: usize,
pattern: &str,
stream: LogStream,
) -> Result<Self, RulesError> {
let refused = |reason: String| RulesError::BadPattern { index, reason };
if pattern.is_empty() {
return Err(refused(
"the pattern is empty and would match every line".to_owned(),
));
}
let regex = regex::RegexBuilder::new(pattern)
.size_limit(PATTERN_SIZE_LIMIT)
.build()
.map_err(|err| refused(err.to_string()))?;
Ok(Self {
idx: index,
stream,
regex,
})
}
pub(super) fn matches(&self, source: Source, line: &str) -> bool {
let reads = match source {
Source::Out => self.stream.reads_out(),
Source::Err => self.stream.reads_err(),
};
reads && self.regex.is_match(line)
}
pub(super) fn topics(rules: &[Self]) -> Vec<String> {
let mut topics = Vec::new();
if rules.iter().any(|rule| rule.stream.reads_out()) {
topics.push("log.out".to_owned());
}
if rules.iter().any(|rule| rule.stream.reads_err()) {
topics.push("log.err".to_owned());
}
topics
}
}
pub(super) fn message(name: &str, source: Source, line: &str) -> String {
let mut end = line.len().min(LINE_CAP);
while !line.is_char_boundary(end) {
end -= 1;
}
format!("{name} logged to {}: {}", source.word(), &line[..end])
}