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