1use std::{
2 backtrace::{Backtrace, BacktraceStatus},
3 cell::RefCell,
4 cmp::min,
5 fmt::{self, Formatter},
6 marker::PhantomData,
7 mem::take,
8 sync::{Condvar, Mutex},
9 thread,
10};
11
12use yansi::{Condition, Paint};
13
14use crate::Record;
15
16thread_local! {
17 static ACTUAL_LOCAL: RefCell<Option<Vec<Record>>> = const { RefCell::new(None) };
18}
19
20static ACTUAL_GLOBAL: Mutex<Option<Vec<Record>>> = Mutex::new(None);
21static ACTUAL_GLOBAL_CONDVAR: Condvar = Condvar::new();
22
23pub trait Thread {
24 fn init() -> Self;
25 fn take_actual(&self) -> Records;
26}
27
28pub struct Local(PhantomData<*mut ()>);
29
30impl Thread for Local {
31 fn init() -> Self {
32 ACTUAL_LOCAL.with(|actual| {
33 let mut actual = actual.borrow_mut();
34 if actual.is_some() {
35 panic!("CallRecorder::new_local() is already called in this thread");
36 }
37 *actual = Some(Vec::new());
38 });
39 Self(PhantomData)
40 }
41 fn take_actual(&self) -> Records {
42 Records(ACTUAL_LOCAL.with(|actual| take(actual.borrow_mut().as_mut().unwrap())))
43 }
44}
45impl Drop for Local {
46 fn drop(&mut self) {
47 ACTUAL_LOCAL.with(|actual| actual.borrow_mut().take());
48 }
49}
50
51#[non_exhaustive]
52pub struct Global {}
53
54impl Thread for Global {
55 fn init() -> Self {
56 let mut actual = ACTUAL_GLOBAL.lock().unwrap();
57 while actual.is_some() {
58 actual = ACTUAL_GLOBAL_CONDVAR.wait(actual).unwrap();
59 }
60 *actual = Some(Vec::new());
61 Self {}
62 }
63 fn take_actual(&self) -> Records {
64 Records(take(ACTUAL_GLOBAL.lock().unwrap().as_mut().unwrap()))
65 }
66}
67impl Drop for Global {
68 fn drop(&mut self) {
69 ACTUAL_GLOBAL.lock().unwrap().take();
70 ACTUAL_GLOBAL_CONDVAR.notify_all();
71 }
72}
73
74#[derive(Debug)]
75pub struct Records(pub(crate) Vec<Record>);
76
77impl Records {
78 pub(crate) fn empty() -> Self {
79 Self(Vec::new())
80 }
81
82 #[track_caller]
83 pub fn push(id: String, file: &'static str, line: u32) {
84 let record = Record {
85 id,
86 file,
87 line,
88 backtrace: Backtrace::capture(),
89 thread_id: thread::current().id(),
90 };
91 if let Err(e) = ACTUAL_LOCAL.with(|actual| {
92 if let Some(actual) = &mut *actual.borrow_mut() {
93 actual.push(record);
94 Ok(())
95 } else if let Some(seq) = ACTUAL_GLOBAL.lock().unwrap().as_mut() {
96 seq.push(record);
97 Ok(())
98 } else {
99 let id = record.id;
100 Err(format!(
101 "`CallRecorder` is not initialized. (\"{id}\")\n{file}:{line}"
102 ))
103 }
104 }) {
105 panic!("{e}");
106 }
107 }
108
109 fn id(&self, index: usize) -> &str {
110 if let Some(a) = self.0.get(index) {
111 &a.id
112 } else {
113 "(end)"
114 }
115 }
116
117 pub(crate) fn fmt_summary(
118 &self,
119 f: &mut Formatter,
120 mismatch_index: usize,
121 around: usize,
122 color: bool,
123 ) -> fmt::Result {
124 let mut start = 0;
125 let end = self.0.len();
126 if mismatch_index > around {
127 start = mismatch_index - around;
128 }
129 let end = min(mismatch_index + around + 1, end);
130 if start > 0 {
131 writeln!(f, " ...(previous {start} calls omitted)")?;
132 }
133 for index in start..end {
134 self.fmt_item_summary(f, mismatch_index == index, self.id(index), color)?;
135 }
136 if end == self.0.len() {
137 self.fmt_item_summary(f, mismatch_index == self.0.len(), "(end)", color)?;
138 } else {
139 writeln!(f, " ...(following {} calls omitted)", self.0.len() - end)?;
140 }
141 Ok(())
142 }
143 fn fmt_item_summary(
144 &self,
145 f: &mut Formatter,
146 is_mismatch: bool,
147 id: &str,
148 color: bool,
149 ) -> fmt::Result {
150 let head = if is_mismatch { "*" } else { " " };
151 let cond = if is_mismatch && color {
152 Condition::ALWAYS
153 } else {
154 Condition::NEVER
155 };
156 writeln!(f, "{}", format_args!("{head} {id}").red().whenever(cond))
157 }
158 pub(crate) fn fmt_backtrace(
159 &self,
160 f: &mut Formatter,
161 mismatch_index: usize,
162 around: usize,
163 ) -> fmt::Result {
164 let mut start = 0;
165 let end = self.0.len();
166 if mismatch_index > around {
167 start = mismatch_index - around;
168 }
169 let end = min(mismatch_index + 1, end);
170 if start > 0 {
171 writeln!(f, "# ...(previous {start} calls omitted)")?;
172 }
173 for index in start..end {
174 let r = &self.0[index];
175 writeln!(f, "# {}", r.id)?;
176 writeln!(f, "{}:{}", r.file, r.line)?;
177 writeln!(f, "thread: {:?}", r.thread_id)?;
178 writeln!(f, "{}", r.backtrace)?;
179 }
180
181 if end == self.0.len() {
182 writeln!(f, "# (end)")?;
183 } else {
184 writeln!(f, " ...(following {} calls omitted)", self.0.len() - end)?;
185 }
186 Ok(())
187 }
188
189 pub(crate) fn has_bakctrace(&self) -> bool {
190 self.0
191 .iter()
192 .any(|r| r.backtrace.status() == BacktraceStatus::Captured)
193 }
194}