spdlog/
record.rs

1use std::{
2    borrow::{Borrow, Cow},
3    cell::RefCell,
4    time::SystemTime,
5};
6
7use crate::{kv, Level, SourceLocation};
8
9/// Represents a log record.
10///
11/// # Use
12///
13/// `Record` structures are passed as arguments to methods [`Logger::log`].
14/// Loggers forward these structures to its sinks, then sink implementors
15/// manipulate these structures in order to process log records. `Record`s are
16/// automatically created by log macros and so are not seen by log users.
17///
18/// [`Logger::log`]: crate::logger::Logger::log
19/// [`Sink::log`]: crate::sink::Sink::log
20/// [`log!`]: crate::log
21// FIXME: `Record` still owns some data and not just a reference, I'm not sure this is necessary and
22// possible to correct.
23#[derive(Clone, Debug)]
24pub struct Record<'a> {
25    logger_name: Option<&'a str>,
26    payload: Cow<'a, str>,
27    kvs: Cow<'a, [kv::Pair<'a>]>,
28    inner: Cow<'a, RecordInner>,
29}
30
31#[derive(Clone, Debug)]
32struct RecordInner {
33    level: Level,
34    source_location: Option<SourceLocation>,
35    time: SystemTime,
36    tid: u64,
37}
38
39impl<'a> Record<'a> {
40    #[must_use]
41    pub(crate) fn new(
42        level: Level,
43        payload: impl Into<Cow<'a, str>>,
44        srcloc: Option<SourceLocation>,
45        logger_name: Option<&'a str>,
46        kvs: &'a [(kv::Key<'a>, kv::Value<'a>)],
47    ) -> Record<'a> {
48        Record {
49            logger_name,
50            payload: payload.into(),
51            kvs: Cow::Borrowed(kvs),
52            inner: Cow::Owned(RecordInner {
53                level,
54                source_location: srcloc,
55                time: SystemTime::now(),
56                tid: get_current_tid(),
57            }),
58        }
59    }
60
61    /// Creates a [`RecordOwned`] that doesn't have lifetimes.
62    #[must_use]
63    pub fn to_owned(&self) -> RecordOwned {
64        RecordOwned {
65            logger_name: self.logger_name.map(|n| n.to_owned()),
66            payload: self.payload.to_string(),
67            kvs: self
68                .kvs
69                .iter()
70                .map(|(k, v)| (k.to_owned(), v.to_owned()))
71                .collect(),
72            inner: self.inner.clone().into_owned(),
73        }
74    }
75
76    /// Gets the logger name.
77    #[must_use]
78    pub fn logger_name(&self) -> Option<&str> {
79        self.logger_name
80    }
81
82    /// Gets the level.
83    #[must_use]
84    pub fn level(&self) -> Level {
85        self.inner.level
86    }
87
88    /// Gets the payload.
89    #[must_use]
90    pub fn payload(&self) -> &str {
91        self.payload.borrow()
92    }
93
94    /// Gets the source location.
95    #[must_use]
96    pub fn source_location(&self) -> Option<&SourceLocation> {
97        self.inner.source_location.as_ref()
98    }
99
100    /// Gets the time when the record was created.
101    #[must_use]
102    pub fn time(&self) -> SystemTime {
103        self.inner.time
104    }
105
106    /// Gets the TID when the record was created.
107    #[must_use]
108    pub fn tid(&self) -> u64 {
109        self.inner.tid
110    }
111
112    /// Gets the key-values.
113    #[must_use]
114    pub fn key_values(&self) -> kv::KeyValues<'_> {
115        kv::KeyValues::with_borrowed(&self.kvs)
116    }
117
118    // When adding more getters, also add to `RecordOwned`
119
120    #[must_use]
121    pub(crate) fn replace_payload(&'a self, new: impl Into<Cow<'a, str>>) -> Self {
122        Self {
123            logger_name: self.logger_name,
124            payload: new.into(),
125            kvs: self.kvs.clone(),
126            inner: Cow::Borrowed(&self.inner),
127        }
128    }
129
130    #[cfg(test)]
131    pub(crate) fn set_time(&mut self, new: SystemTime) {
132        self.inner.to_mut().time = new;
133    }
134}
135
136/// [`Record`] without lifetimes version.
137// We do not `impl From<&Record> for RecordOwned` because it does not follow the
138// Rust naming convention. Use `record.to_owned()` instead.
139#[derive(Clone, Debug)]
140pub struct RecordOwned {
141    logger_name: Option<String>,
142    payload: String,
143    kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>,
144    inner: RecordInner,
145}
146
147impl RecordOwned {
148    /// References as [`Record`] cheaply.
149    #[must_use]
150    pub fn as_ref(&self) -> Record<'_> {
151        Record {
152            logger_name: self.logger_name.as_deref(),
153            payload: Cow::Borrowed(&self.payload),
154            kvs: Cow::Owned(
155                self.kvs
156                    .iter()
157                    .map(|(k, v)| (k.as_ref(), v.by_ref()))
158                    .collect::<Vec<_>>(),
159            ),
160            inner: Cow::Borrowed(&self.inner),
161        }
162    }
163
164    /// Gets the logger name.
165    #[must_use]
166    pub fn logger_name(&self) -> Option<&str> {
167        self.logger_name.as_deref()
168    }
169
170    /// Gets the level.
171    #[must_use]
172    pub fn level(&self) -> Level {
173        self.inner.level
174    }
175
176    /// Gets the payload.
177    #[must_use]
178    pub fn payload(&self) -> &str {
179        self.payload.borrow()
180    }
181
182    /// Gets the source location.
183    #[must_use]
184    pub fn source_location(&self) -> Option<&SourceLocation> {
185        self.inner.source_location.as_ref()
186    }
187
188    /// Gets the time when the record was created.
189    #[must_use]
190    pub fn time(&self) -> SystemTime {
191        self.inner.time
192    }
193
194    /// Gets the TID when the record was created.
195    #[must_use]
196    pub fn tid(&self) -> u64 {
197        self.inner.tid
198    }
199
200    /// Gets the key-values.
201    #[must_use]
202    pub fn key_values(&self) -> kv::KeyValues<'_> {
203        kv::KeyValues::with_owned(&self.kvs)
204    }
205
206    // When adding more getters, also add to `Record`
207}
208
209#[cfg(feature = "log")]
210#[derive(Clone, Debug)]
211pub(crate) struct LogCrateRecord<'a> {
212    logger_name: Option<&'a str>,
213    payload: Cow<'a, str>,
214    kvs: Vec<(log::kv::Key<'a>, kv::ValueOwned)>,
215    inner: Cow<'a, RecordInner>,
216}
217
218#[cfg(feature = "log")]
219impl<'a> LogCrateRecord<'a> {
220    #[must_use]
221    pub(crate) fn new(
222        logger: &'a crate::Logger,
223        record: &'a log::Record,
224        time: SystemTime,
225    ) -> Self {
226        let args = record.args();
227
228        Self {
229            // If the logger has a name configured, use that name. Otherwise, the name can also be
230            // given by the target of the log record.
231            logger_name: logger.name().or_else(|| Some(record.target())),
232            kvs: {
233                let kvs = record.key_values();
234                let mut cvt = kv::LogCrateConverter::new(kvs.count());
235                assert!(kvs.visit(&mut cvt).is_ok());
236                cvt.finalize()
237            },
238            payload: match args.as_str() {
239                Some(literal_str) => literal_str.into(),
240                None => args.to_string().into(),
241            },
242            inner: Cow::Owned(RecordInner {
243                level: record.level().into(),
244                source_location: SourceLocation::from_log_crate_record(record),
245                time,
246                // For records from `log` crate, they never seem to come from different threads, so
247                // getting the current TID here should be correct
248                tid: get_current_tid(),
249            }),
250        }
251    }
252
253    #[must_use]
254    pub(crate) fn as_record(&self) -> Record<'_> {
255        Record {
256            logger_name: self.logger_name,
257            payload: self.payload.clone(),
258            kvs: self
259                .kvs
260                .iter()
261                .map(|(k, v)| (kv::Key::from_str(k.as_str()), v.by_ref()))
262                .collect(),
263            inner: self.inner.clone(),
264        }
265    }
266}
267
268fn get_current_tid() -> u64 {
269    #[cfg(any(target_os = "linux", target_os = "android"))]
270    #[must_use]
271    fn get_current_tid_inner() -> u64 {
272        // https://github.com/SpriteOvO/spdlog-rs/issues/31
273        //
274        // We don't use `gettid` since earlier glibc versions (before v2.30) did not
275        // provide a wrapper for this system call.
276        let tid = unsafe { libc::syscall(libc::SYS_gettid) };
277        tid as u64
278    }
279
280    #[cfg(target_os = "freebsd")]
281    #[must_use]
282    fn get_current_tid_inner() -> u64 {
283        let tid = unsafe { libc::pthread_getthreadid_np() };
284        tid as u64
285    }
286
287    #[cfg(target_os = "illumos")]
288    #[must_use]
289    fn get_current_tid_inner() -> u64 {
290        let tid = unsafe { libc::thr_self() };
291        tid as u64
292    }
293
294    #[cfg(any(target_os = "macos", target_os = "ios"))]
295    #[must_use]
296    fn get_current_tid_inner() -> u64 {
297        let mut tid = 0;
298        unsafe { libc::pthread_threadid_np(0, &mut tid) };
299        tid
300    }
301
302    #[cfg(target_os = "windows")]
303    #[must_use]
304    fn get_current_tid_inner() -> u64 {
305        let tid = unsafe { winapi::um::processthreadsapi::GetCurrentThreadId() };
306        tid as u64
307    }
308
309    thread_local! {
310        static TID: RefCell<Option<u64>> = const { RefCell::new(None)} ;
311    }
312
313    TID.with(|tid| *tid.borrow_mut().get_or_insert_with(get_current_tid_inner))
314}