spdlog/sink/
journald_sink.rs1use std::{io, os::raw::c_int};
2
3use crate::{
4 formatter::{Formatter, FormatterContext, JournaldFormatter},
5 sink::{GetSinkProp, Sink, SinkProp},
6 Error, ErrorHandler, Level, LevelFilter, Record, Result, StdResult, StringBuf,
7};
8
9#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
10enum SyslogLevel {
11 _Emerg = 0,
12 _Alert = 1,
13 Crit = 2,
14 Err = 3,
15 Warning = 4,
16 _Notice = 5,
17 Info = 6,
18 Debug = 7,
19}
20
21#[derive(Clone, Eq, PartialEq, Hash, Debug)]
22struct SyslogLevels([SyslogLevel; Level::count()]);
23
24impl SyslogLevels {
25 #[must_use]
26 const fn new() -> Self {
27 Self([
28 SyslogLevel::Crit, SyslogLevel::Err, SyslogLevel::Warning, SyslogLevel::Info, SyslogLevel::Debug, SyslogLevel::Debug, ])
35 }
36
37 #[must_use]
38 fn level(&self, level: Level) -> SyslogLevel {
39 self.0[level as usize]
40 }
41}
42
43impl Default for SyslogLevels {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49fn journal_send(args: impl Iterator<Item = impl AsRef<str>>) -> StdResult<(), io::Error> {
50 #[cfg(not(doc))] use libsystemd_sys::{const_iovec, journal as ffi};
52
53 let iovecs: Vec<_> = args.map(|a| unsafe { const_iovec::from_str(a) }).collect();
54 let result = unsafe { ffi::sd_journal_sendv(iovecs.as_ptr(), iovecs.len() as c_int) };
55 if result == 0 {
56 Ok(())
57 } else {
58 Err(io::Error::from_raw_os_error(result))
59 }
60}
61
62pub struct JournaldSink {
91 prop: SinkProp,
92}
93
94impl JournaldSink {
95 const SYSLOG_LEVELS: SyslogLevels = SyslogLevels::new();
96
97 #[must_use]
110 pub fn builder() -> JournaldSinkBuilder {
111 let prop = SinkProp::default();
112 prop.set_formatter(JournaldFormatter::new());
113
114 JournaldSinkBuilder { prop }
115 }
116}
117
118impl GetSinkProp for JournaldSink {
119 fn prop(&self) -> &SinkProp {
120 &self.prop
121 }
122}
123
124impl Sink for JournaldSink {
125 fn log(&self, record: &Record) -> Result<()> {
126 let mut string_buf = StringBuf::new();
127 let mut ctx = FormatterContext::new();
128 self.prop
129 .formatter()
130 .format(record, &mut string_buf, &mut ctx)?;
131
132 let kvs = [
133 format!("MESSAGE={string_buf}"),
134 format!(
135 "PRIORITY={}",
136 JournaldSink::SYSLOG_LEVELS.level(record.level()) as u32
137 ),
138 format!("TID={}", record.tid()),
139 ];
140
141 let srcloc_kvs = match record.source_location() {
142 Some(srcloc) => [
143 Some(format!("CODE_FILE={}", srcloc.file_name())),
144 Some(format!("CODE_LINE={}", srcloc.line())),
145 ],
146 None => [None, None],
147 };
148
149 journal_send(kvs.iter().chain(srcloc_kvs.iter().flatten())).map_err(Error::WriteRecord)
150 }
151
152 fn flush(&self) -> Result<()> {
153 Ok(())
154 }
155}
156
157#[allow(missing_docs)]
158pub struct JournaldSinkBuilder {
159 prop: SinkProp,
160}
161
162impl JournaldSinkBuilder {
163 #[must_use]
170 pub fn level_filter(self, level_filter: LevelFilter) -> Self {
171 self.prop.set_level_filter(level_filter);
172 self
173 }
174
175 #[must_use]
179 pub fn formatter<F>(self, formatter: F) -> Self
180 where
181 F: Formatter + 'static,
182 {
183 self.prop.set_formatter(formatter);
184 self
185 }
186
187 #[must_use]
191 pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
192 self.prop.set_error_handler(handler);
193 self
194 }
195
196 pub fn build(self) -> Result<JournaldSink> {
200 let sink = JournaldSink { prop: self.prop };
201 Ok(sink)
202 }
203}