ferron/
logging.rs

1pub use ferron_common::logging::*;
2
3use std::{cmp::Ordering, collections::HashMap, net::IpAddr, sync::Arc};
4
5use async_channel::{Receiver, Sender};
6
7use crate::util::{is_localhost, match_hostname};
8
9/// A logger filter
10#[derive(Clone, Eq, PartialEq, Hash)]
11pub struct LoggerFilter {
12  /// The hostname
13  pub hostname: Option<String>,
14
15  /// The IP address
16  pub ip: Option<IpAddr>,
17
18  /// The port
19  pub port: Option<u16>,
20}
21
22impl Ord for LoggerFilter {
23  fn cmp(&self, other: &Self) -> Ordering {
24    self
25      .port
26      .is_some()
27      .cmp(&other.port.is_some())
28      .then_with(|| self.ip.is_some().cmp(&other.ip.is_some()))
29      .then_with(|| {
30        self
31          .hostname
32          .as_ref()
33          .map(|h| !h.starts_with("*."))
34          .cmp(&other.hostname.as_ref().map(|h| !h.starts_with("*.")))
35      }) // Take wildcard hostnames into account
36      .then_with(|| {
37        self
38          .hostname
39          .as_ref()
40          .map(|h| h.trim_end_matches('.').chars().filter(|c| *c == '.').count())
41          .cmp(
42            &other
43              .hostname
44              .as_ref()
45              .map(|h| h.trim_end_matches('.').chars().filter(|c| *c == '.').count()),
46          )
47      }) // Take also amount of dots in hostnames (domain level) into account
48  }
49}
50
51impl PartialOrd for LoggerFilter {
52  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53    Some(self.cmp(other))
54  }
55}
56
57impl LoggerFilter {
58  /// Checks if the logger is global
59  pub fn is_global(&self) -> bool {
60    self.hostname.is_none() && self.ip.is_none() && self.port.is_none()
61  }
62}
63
64/// A builder for the struct that contains loggers as specified by the server configuration
65pub struct LoggersBuilder {
66  pub inner: HashMap<LoggerFilter, (Sender<LogMessage>, Receiver<LogMessage>)>,
67}
68
69impl LoggersBuilder {
70  /// Creates a new `LoggersBuilder` instance.
71  pub fn new() -> Self {
72    Self { inner: HashMap::new() }
73  }
74
75  /// Adds a new logger, if there isn't already a logger.
76  pub fn add(
77    &mut self,
78    filter: LoggerFilter,
79    logger: (Sender<LogMessage>, Receiver<LogMessage>),
80  ) -> (Sender<LogMessage>, Receiver<LogMessage>) {
81    if let Some(existing_logger) = self.inner.get(&filter) {
82      existing_logger.clone()
83    } else {
84      let new_logger = logger.clone();
85      self.inner.insert(filter, logger);
86      new_logger
87    }
88  }
89
90  /// Consumes the builder and returns a `Loggers` instance.
91  #[allow(dead_code)]
92  pub fn build(self) -> Loggers {
93    let mut inner_vector = self.inner.into_iter().map(|(k, v)| (k, v.0)).collect::<Vec<_>>();
94    inner_vector.sort_by(|a, b| a.0.cmp(&b.0));
95    Loggers {
96      inner: Arc::new(inner_vector),
97    }
98  }
99
100  /// Returns a `Loggers` instance from the builder.
101  pub fn build_borrowed(&self) -> Loggers {
102    let mut inner_vector = self
103      .inner
104      .iter()
105      .map(|(k, v)| (k.clone(), v.0.clone()))
106      .collect::<Vec<_>>();
107    inner_vector.reverse();
108    inner_vector.sort_by(|a, b| a.0.cmp(&b.0));
109    Loggers {
110      inner: Arc::new(inner_vector),
111    }
112  }
113}
114
115pub struct Loggers {
116  inner: Arc<Vec<(LoggerFilter, Sender<LogMessage>)>>,
117}
118
119impl Loggers {
120  /// Finds the global logger
121  pub fn find_global_logger(&self) -> Option<Sender<LogMessage>> {
122    self
123      .inner
124      .iter()
125      .find(|logger| logger.0.is_global())
126      .map(|logger| &logger.1)
127      .cloned()
128  }
129
130  /// Finds a specific logger based on request parameters
131  pub fn find_logger(&self, hostname: Option<&str>, ip: IpAddr, port: u16) -> Option<Sender<LogMessage>> {
132    // The inner array is sorted by specifity, so it's easier to find the configurations.
133    // If it was not sorted, we would need to implement the specifity...
134    // Also, the approach mentioned in the line above might be slower...
135    // But there is one thing we're wondering: so many logical operators???
136    self
137      .inner
138      .iter()
139      .rev()
140      .find(|&logger| {
141        match_hostname(logger.0.hostname.as_deref(), hostname)
142          && ((logger.0.ip.is_none() && (!is_localhost(logger.0.ip.as_ref(), logger.0.hostname.as_deref())
143            || ip.to_canonical().is_loopback()))  // With special `localhost` check
144          || logger.0.ip == Some(ip))
145          && (logger.0.port.is_none() || logger.0.port == Some(port))
146      })
147      .map(|logger| &logger.1)
148      .cloned()
149  }
150}
151
152impl Clone for Loggers {
153  /// Clone a `Loggers`.
154  fn clone(&self) -> Self {
155    Self {
156      inner: self.inner.clone(),
157    }
158  }
159}