logforth_append_syslog/
lib.rs1#![cfg_attr(docsrs, feature(doc_cfg))]
32#![deny(missing_docs)]
33
34use std::io;
35use std::sync::Mutex;
36use std::sync::MutexGuard;
37
38use fasyslog::SDElement;
39use fasyslog::format::SyslogContext;
40use fasyslog::sender::SyslogSender;
41use logforth_core::Append;
42use logforth_core::Diagnostic;
43use logforth_core::Error;
44use logforth_core::Layout;
45use logforth_core::record::Level;
46use logforth_core::record::Record;
47
48pub extern crate fasyslog;
49
50#[derive(Debug, Copy, Clone)]
52pub enum SyslogFormat {
53 RFC3164,
57 RFC5424,
61}
62
63#[derive(Debug)]
65pub struct SyslogBuilder {
66 sender: SyslogSender,
67 formatter: SyslogFormatter,
68}
69
70impl SyslogBuilder {
71 pub fn new(sender: SyslogSender) -> Self {
73 Self {
74 sender,
75 formatter: SyslogFormatter {
76 format: SyslogFormat::RFC3164,
77 context: SyslogContext::default(),
78 layout: None,
79 },
80 }
81 }
82
83 pub fn build(self) -> Syslog {
85 let SyslogBuilder { sender, formatter } = self;
86 Syslog::new(sender, formatter)
87 }
88
89 pub fn format(mut self, format: SyslogFormat) -> Self {
91 self.formatter.format = format;
92 self
93 }
94
95 pub fn context(mut self, context: SyslogContext) -> Self {
97 self.formatter.context = context;
98 self
99 }
100
101 pub fn layout(mut self, layout: impl Into<Box<dyn Layout>>) -> Self {
105 self.formatter.layout = Some(layout.into());
106 self
107 }
108
109 pub fn tcp_well_known() -> io::Result<SyslogBuilder> {
111 fasyslog::sender::tcp_well_known()
112 .map(SyslogSender::Tcp)
113 .map(Self::new)
114 }
115
116 pub fn tcp<A: std::net::ToSocketAddrs>(addr: A) -> io::Result<SyslogBuilder> {
118 fasyslog::sender::tcp(addr)
119 .map(SyslogSender::Tcp)
120 .map(Self::new)
121 }
122
123 pub fn udp_well_known() -> io::Result<SyslogBuilder> {
125 fasyslog::sender::udp_well_known()
126 .map(SyslogSender::Udp)
127 .map(Self::new)
128 }
129
130 pub fn udp<L: std::net::ToSocketAddrs, R: std::net::ToSocketAddrs>(
132 local: L,
133 remote: R,
134 ) -> io::Result<SyslogBuilder> {
135 fasyslog::sender::udp(local, remote)
136 .map(SyslogSender::Udp)
137 .map(Self::new)
138 }
139
140 pub fn broadcast_well_known() -> io::Result<SyslogBuilder> {
142 fasyslog::sender::broadcast_well_known()
143 .map(SyslogSender::Udp)
144 .map(Self::new)
145 }
146
147 pub fn broadcast(port: u16) -> io::Result<SyslogBuilder> {
149 fasyslog::sender::broadcast(port)
150 .map(SyslogSender::Udp)
151 .map(Self::new)
152 }
153}
154
155#[cfg(feature = "rustls")]
156mod rustls_ext {
157 use std::io;
158 use std::net::ToSocketAddrs;
159 use std::sync::Arc;
160
161 use fasyslog::sender::SyslogSender;
162 use fasyslog::sender::rustls::ClientConfig;
163
164 use super::SyslogBuilder;
165
166 impl SyslogBuilder {
167 pub fn rustls_well_known<S: Into<String>>(domain: S) -> io::Result<SyslogBuilder> {
169 fasyslog::sender::rustls_well_known(domain)
170 .map(Box::new)
171 .map(SyslogSender::RustlsSender)
172 .map(Self::new)
173 }
174
175 pub fn rustls<A: ToSocketAddrs, S: Into<String>>(
177 addr: A,
178 domain: S,
179 ) -> io::Result<SyslogBuilder> {
180 fasyslog::sender::rustls(addr, domain)
181 .map(Box::new)
182 .map(SyslogSender::RustlsSender)
183 .map(Self::new)
184 }
185
186 pub fn rustls_with<A: ToSocketAddrs, S: Into<String>>(
188 addr: A,
189 domain: S,
190 config: Arc<ClientConfig>,
191 ) -> io::Result<SyslogBuilder> {
192 fasyslog::sender::rustls_with(addr, domain, config)
193 .map(Box::new)
194 .map(SyslogSender::RustlsSender)
195 .map(Self::new)
196 }
197 }
198}
199
200#[cfg(feature = "native-tls")]
201mod native_tls_ext {
202 use std::io;
203 use std::net::ToSocketAddrs;
204
205 use fasyslog::sender::SyslogSender;
206 use fasyslog::sender::native_tls::TlsConnectorBuilder;
207
208 use super::SyslogBuilder;
209
210 impl SyslogBuilder {
211 pub fn native_tls_well_known<S: AsRef<str>>(domain: S) -> io::Result<SyslogBuilder> {
213 fasyslog::sender::native_tls_well_known(domain)
214 .map(SyslogSender::NativeTlsSender)
215 .map(Self::new)
216 }
217
218 pub fn native_tls<A: ToSocketAddrs, S: AsRef<str>>(
220 addr: A,
221 domain: S,
222 ) -> io::Result<SyslogBuilder> {
223 fasyslog::sender::native_tls(addr, domain)
224 .map(SyslogSender::NativeTlsSender)
225 .map(Self::new)
226 }
227
228 pub fn native_tls_with<A: ToSocketAddrs, S: AsRef<str>>(
230 addr: A,
231 domain: S,
232 builder: TlsConnectorBuilder,
233 ) -> io::Result<SyslogBuilder> {
234 fasyslog::sender::native_tls_with(addr, domain, builder)
235 .map(SyslogSender::NativeTlsSender)
236 .map(Self::new)
237 }
238 }
239}
240
241#[cfg(unix)]
242mod unix_ext {
243 use std::io;
244
245 use fasyslog::sender::SyslogSender;
246
247 use super::SyslogBuilder;
248
249 impl SyslogBuilder {
250 pub fn unix_stream(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
252 fasyslog::sender::unix_stream(path)
253 .map(SyslogSender::UnixStream)
254 .map(Self::new)
255 }
256
257 pub fn unix_datagram(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
259 fasyslog::sender::unix_datagram(path)
260 .map(SyslogSender::UnixDatagram)
261 .map(Self::new)
262 }
263
264 pub fn unix(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
269 fasyslog::sender::unix(path).map(Self::new)
270 }
271 }
272}
273
274#[derive(Debug)]
278pub struct Syslog {
279 sender: Mutex<SyslogSender>,
280 formatter: SyslogFormatter,
281}
282
283impl Syslog {
284 fn new(sender: SyslogSender, formatter: SyslogFormatter) -> Self {
286 let sender = Mutex::new(sender);
287 Self { sender, formatter }
288 }
289
290 fn sender(&self) -> MutexGuard<'_, SyslogSender> {
291 self.sender.lock().unwrap_or_else(|e| e.into_inner())
292 }
293}
294
295impl Append for Syslog {
296 fn append(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<(), Error> {
297 let message = self.formatter.format_message(record, diags)?;
298 let mut sender = self.sender();
299 sender
300 .send_formatted(&message)
301 .map_err(Error::from_io_error)?;
302 Ok(())
303 }
304
305 fn flush(&self) -> Result<(), Error> {
306 let mut sender = self.sender();
307 sender.flush().map_err(Error::from_io_error)?;
308 Ok(())
309 }
310}
311
312impl Drop for Syslog {
313 fn drop(&mut self) {
314 let sender = self.sender.get_mut().unwrap_or_else(|e| e.into_inner());
315 let _ = sender.flush();
316 }
317}
318
319#[derive(Debug)]
320struct SyslogFormatter {
321 format: SyslogFormat,
322 context: SyslogContext,
323 layout: Option<Box<dyn Layout>>,
324}
325
326fn log_level_to_syslog_severity(level: Level) -> fasyslog::Severity {
328 match level {
329 Level::Fatal | Level::Fatal2 | Level::Fatal3 | Level::Fatal4 => {
330 fasyslog::Severity::EMERGENCY
331 }
332 Level::Error3 | Level::Error4 => fasyslog::Severity::ALERT,
333 Level::Error2 => fasyslog::Severity::CRITICAL,
334 Level::Error => fasyslog::Severity::ERROR,
335 Level::Warn | Level::Warn2 | Level::Warn3 | Level::Warn4 => fasyslog::Severity::WARNING,
336 Level::Info2 | Level::Info3 | Level::Info4 => fasyslog::Severity::NOTICE,
337 Level::Info => fasyslog::Severity::INFORMATIONAL,
338 Level::Debug
339 | Level::Debug2
340 | Level::Debug3
341 | Level::Debug4
342 | Level::Trace
343 | Level::Trace2
344 | Level::Trace3
345 | Level::Trace4 => fasyslog::Severity::DEBUG,
346 }
347}
348
349impl SyslogFormatter {
350 fn format_message(
351 &self,
352 record: &Record,
353 diags: &[Box<dyn Diagnostic>],
354 ) -> Result<Vec<u8>, Error> {
355 let severity = log_level_to_syslog_severity(record.level());
356
357 let message = match self.format {
358 SyslogFormat::RFC3164 => match self.layout {
359 None => format!(
360 "{}",
361 self.context
362 .format_rfc3164(severity, Some(record.payload()))
363 ),
364 Some(ref layout) => {
365 let message = layout.format(record, diags)?;
366 let message = String::from_utf8_lossy(&message);
367 format!("{}", self.context.format_rfc3164(severity, Some(message)))
368 }
369 },
370 SyslogFormat::RFC5424 => {
371 const EMPTY_MSGID: Option<&str> = None;
372 const EMPTY_STRUCTURED_DATA: Vec<SDElement> = Vec::new();
373
374 match self.layout {
375 None => format!(
376 "{}",
377 self.context.format_rfc5424(
378 severity,
379 EMPTY_MSGID,
380 EMPTY_STRUCTURED_DATA,
381 Some(record.payload())
382 )
383 ),
384 Some(ref layout) => {
385 let message = layout.format(record, diags)?;
386 let message = String::from_utf8_lossy(&message);
387 format!(
388 "{}",
389 self.context.format_rfc5424(
390 severity,
391 EMPTY_MSGID,
392 EMPTY_STRUCTURED_DATA,
393 Some(message)
394 )
395 )
396 }
397 }
398 }
399 };
400
401 Ok(message.into_bytes())
402 }
403}