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