spdlog/
record.rs

1use std::{
2    borrow::{Borrow, Cow},
3    cell::RefCell,
4    time::SystemTime,
5};
6
7use crate::{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<Cow<'a, str>>,
26    payload: Cow<'a, str>,
27    inner: Cow<'a, RecordInner>,
28}
29
30#[derive(Clone, Debug)]
31struct RecordInner {
32    level: Level,
33    source_location: Option<SourceLocation>,
34    time: SystemTime,
35    tid: u64,
36}
37
38impl<'a> Record<'a> {
39    #[must_use]
40    pub(crate) fn new(
41        level: Level,
42        payload: impl Into<Cow<'a, str>>,
43        srcloc: Option<SourceLocation>,
44        logger_name: Option<&'a str>,
45    ) -> Record<'a> {
46        Record {
47            logger_name: logger_name.map(Cow::Borrowed),
48            payload: payload.into(),
49            inner: Cow::Owned(RecordInner {
50                level,
51                source_location: srcloc,
52                time: SystemTime::now(),
53                tid: get_current_tid(),
54            }),
55        }
56    }
57
58    /// Creates a [`RecordOwned`] that doesn't have lifetimes.
59    #[must_use]
60    pub fn to_owned(&self) -> RecordOwned {
61        RecordOwned {
62            logger_name: self.logger_name.clone().map(|n| n.into_owned()),
63            payload: self.payload.to_string(),
64            inner: self.inner.clone().into_owned(),
65        }
66    }
67
68    /// Gets the logger name.
69    #[must_use]
70    pub fn logger_name(&self) -> Option<&str> {
71        self.logger_name.as_ref().map(|n| n.as_ref())
72    }
73
74    /// Gets the level.
75    #[must_use]
76    pub fn level(&self) -> Level {
77        self.inner.level
78    }
79
80    /// Gets the payload.
81    #[must_use]
82    pub fn payload(&self) -> &str {
83        self.payload.borrow()
84    }
85
86    /// Gets the source location.
87    #[must_use]
88    pub fn source_location(&self) -> Option<&SourceLocation> {
89        self.inner.source_location.as_ref()
90    }
91
92    /// Gets the time when the record was created.
93    #[must_use]
94    pub fn time(&self) -> SystemTime {
95        self.inner.time
96    }
97
98    /// Gets the TID when the record was created.
99    #[must_use]
100    pub fn tid(&self) -> u64 {
101        self.inner.tid
102    }
103
104    // When adding more getters, also add to `RecordOwned`
105
106    #[must_use]
107    pub(crate) fn replace_payload(&'a self, new: impl Into<Cow<'a, str>>) -> Self {
108        Self {
109            logger_name: self.logger_name.clone(),
110            payload: new.into(),
111            inner: Cow::Borrowed(&self.inner),
112        }
113    }
114
115    #[cfg(feature = "log")]
116    #[must_use]
117    pub(crate) fn from_log_crate_record(
118        logger: &'a crate::Logger,
119        record: &log::Record,
120        time: SystemTime,
121    ) -> Self {
122        let args = record.args();
123
124        Self {
125            // If the logger has a name configured, use that name. Otherwise, the name can also be
126            // given by the target of the log record.
127            logger_name: logger.name().map(Cow::Borrowed).or_else(|| {
128                let log_target = record.target();
129                if log_target.is_empty() {
130                    None
131                } else {
132                    Some(Cow::Owned(String::from(log_target)))
133                }
134            }),
135            payload: match args.as_str() {
136                Some(literal_str) => literal_str.into(),
137                None => args.to_string().into(),
138            },
139            inner: Cow::Owned(RecordInner {
140                level: record.level().into(),
141                source_location: SourceLocation::from_log_crate_record(record),
142                time,
143                // For records from `log` crate, they never seem to come from different threads, so
144                // getting the current TID here should be correct
145                tid: get_current_tid(),
146            }),
147        }
148    }
149
150    #[cfg(test)]
151    pub(crate) fn set_time(&mut self, new: SystemTime) {
152        self.inner.to_mut().time = new;
153    }
154}
155
156/// [`Record`] without lifetimes version.
157// We do not `impl From<&Record> for RecordOwned` because it does not follow the
158// Rust naming convention. Use `record.to_owned()` instead.
159#[derive(Clone, Debug)]
160pub struct RecordOwned {
161    logger_name: Option<String>,
162    payload: String,
163    inner: RecordInner,
164}
165
166impl RecordOwned {
167    /// References as [`Record`] cheaply.
168    #[must_use]
169    pub fn as_ref(&self) -> Record {
170        Record {
171            logger_name: self.logger_name.as_deref().map(Cow::Borrowed),
172            payload: Cow::Borrowed(&self.payload),
173            inner: Cow::Borrowed(&self.inner),
174        }
175    }
176
177    /// Gets the logger name.
178    #[must_use]
179    pub fn logger_name(&self) -> Option<&str> {
180        self.logger_name.as_deref()
181    }
182
183    /// Gets the level.
184    #[must_use]
185    pub fn level(&self) -> Level {
186        self.inner.level
187    }
188
189    /// Gets the payload.
190    #[must_use]
191    pub fn payload(&self) -> &str {
192        self.payload.borrow()
193    }
194
195    /// Gets the source location.
196    #[must_use]
197    pub fn source_location(&self) -> Option<&SourceLocation> {
198        self.inner.source_location.as_ref()
199    }
200
201    /// Gets the time when the record was created.
202    #[must_use]
203    pub fn time(&self) -> SystemTime {
204        self.inner.time
205    }
206
207    /// Gets the TID when the record was created.
208    #[must_use]
209    pub fn tid(&self) -> u64 {
210        self.inner.tid
211    }
212
213    // When adding more getters, also add to `Record`
214}
215
216fn get_current_tid() -> u64 {
217    #[cfg(any(target_os = "linux", target_os = "android"))]
218    #[must_use]
219    fn get_current_tid_inner() -> u64 {
220        // https://github.com/SpriteOvO/spdlog-rs/issues/31
221        //
222        // We don't use `gettid` since earlier glibc versions (before v2.30) did not
223        // provide a wrapper for this system call.
224        let tid = unsafe { libc::syscall(libc::SYS_gettid) };
225        tid as u64
226    }
227
228    #[cfg(target_os = "freebsd")]
229    #[must_use]
230    fn get_current_tid_inner() -> u64 {
231        let tid = unsafe { libc::pthread_getthreadid_np() };
232        tid as u64
233    }
234
235    #[cfg(target_os = "illumos")]
236    #[must_use]
237    fn get_current_tid_inner() -> u64 {
238        let tid = unsafe { libc::thr_self() };
239        tid as u64
240    }
241
242    #[cfg(any(target_os = "macos", target_os = "ios"))]
243    #[must_use]
244    fn get_current_tid_inner() -> u64 {
245        let mut tid = 0;
246        unsafe { libc::pthread_threadid_np(0, &mut tid) };
247        tid
248    }
249
250    #[cfg(target_os = "windows")]
251    #[must_use]
252    fn get_current_tid_inner() -> u64 {
253        let tid = unsafe { winapi::um::processthreadsapi::GetCurrentThreadId() };
254        tid as u64
255    }
256
257    thread_local! {
258        static TID: RefCell<Option<u64>> = const { RefCell::new(None)} ;
259    }
260
261    TID.with(|tid| *tid.borrow_mut().get_or_insert_with(get_current_tid_inner))
262}