steamkit/util/
debug_log.rs1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
6use std::sync::{Arc, OnceLock, RwLock};
7
8use tokio::sync::broadcast;
9
10const BROADCAST_CAPACITY: usize = 1024;
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct DebugLogMessage {
16 pub category: String,
18 pub message: String,
20}
21
22pub trait IDebugListener: Send + Sync {
24 fn write_line(&self, category: &str, msg: &str);
26}
27
28struct DebugLogState {
30 enabled: AtomicBool,
32 listeners: RwLock<Vec<Arc<dyn IDebugListener>>>,
34 functions: RwLock<Vec<(u64, Arc<dyn Fn(&str, &str) + Send + Sync>)>>,
36 next_function_id: AtomicU64,
38 sender: broadcast::Sender<DebugLogMessage>,
40}
41
42impl DebugLogState {
43 fn new() -> DebugLogState {
44 let (sender, _) = broadcast::channel(BROADCAST_CAPACITY);
45
46 DebugLogState {
47 enabled: AtomicBool::new(cfg!(debug_assertions)),
49 listeners: RwLock::new(Vec::new()),
50 functions: RwLock::new(Vec::new()),
51 next_function_id: AtomicU64::new(1),
52 sender,
53 }
54 }
55}
56
57fn state() -> &'static DebugLogState {
59 static STATE: OnceLock<DebugLogState> = OnceLock::new();
60
61 STATE.get_or_init(DebugLogState::new)
62}
63
64#[derive(Debug)]
66pub struct DebugLog;
67
68impl DebugLog {
69 pub fn enabled() -> bool {
72 state().enabled.load(Ordering::Relaxed)
73 }
74
75 pub fn set_enabled(enabled: bool) {
77 state().enabled.store(enabled, Ordering::Relaxed);
78 }
79
80 pub fn add_listener(listener: Arc<dyn IDebugListener>) {
82 state().listeners.write().unwrap_or_else(|error| error.into_inner()).push(listener);
83 }
84
85 pub fn add_listener_function(
88 action: impl Fn(&str, &str) + Send + Sync + 'static,
89 ) -> u64 {
90 let id = state().next_function_id.fetch_add(1, Ordering::Relaxed);
91
92 state()
93 .functions
94 .write()
95 .unwrap_or_else(|error| error.into_inner())
96 .push((id, Arc::new(action)));
97
98 id
99 }
100
101 pub fn remove_listener(listener: &Arc<dyn IDebugListener>) {
103 let mut listeners = state().listeners.write().unwrap_or_else(|error| error.into_inner());
104
105 if let Some(index) = listeners
106 .iter()
107 .position(|registered| Arc::ptr_eq(registered, listener))
108 {
109 listeners.remove(index);
110 }
111 }
112
113 pub fn remove_listener_function(id: u64) {
116 let mut functions = state().functions.write().unwrap_or_else(|error| error.into_inner());
117
118 if let Some(index) = functions.iter().position(|(registered, _)| *registered == id) {
119 functions.remove(index);
120 }
121 }
122
123 pub fn clear_listeners() {
125 state()
126 .listeners
127 .write()
128 .unwrap_or_else(|error| error.into_inner())
129 .clear();
130
131 state()
132 .functions
133 .write()
134 .unwrap_or_else(|error| error.into_inner())
135 .clear();
136 }
137
138 pub fn subscribe() -> broadcast::Receiver<DebugLogMessage> {
141 state().sender.subscribe()
142 }
143
144 pub fn write_line(category: &str, msg: &str) {
146 let state = state();
147
148 if !state.enabled.load(Ordering::Relaxed) {
149 return;
150 }
151
152 let message = DebugLogMessage {
153 category: category.to_string(),
154 message: msg.to_string(),
155 };
156
157 let _ = state.sender.send(message);
159
160 let listeners = state.listeners.read().unwrap_or_else(|error| error.into_inner());
161
162 for listener in listeners.iter() {
163 listener.write_line(category, msg);
164 }
165
166 drop(listeners);
167
168 let functions = state.functions.read().unwrap_or_else(|error| error.into_inner());
169
170 for (_, action) in functions.iter() {
171 action(category, msg);
172 }
173 }
174
175 pub fn write_line_args(category: &str, msg: &str, args: &[&str]) {
179 DebugLog::write_line(category, &format_with_args(msg, args));
180 }
181
182 pub fn assert(condition: bool, category: &str, message: &str) {
184 if !condition {
185 DebugLog::write_line(category, &format!("Assertion Failed! {message}"));
186 }
187 }
188}
189
190pub fn format_with_args(msg: &str, args: &[&str]) -> String {
194 let mut result = String::with_capacity(msg.len());
195 let mut characters = msg.chars().peekable();
196
197 while let Some(character) = characters.next() {
198 match character {
199 '{' => {
200 if characters.peek() == Some(&'{') {
201 characters.next();
202 result.push('{');
203 continue;
204 }
205
206 let mut placeholder = String::new();
207 let mut closed = false;
208
209 for character in characters.by_ref() {
210 if character == '}' {
211 closed = true;
212 break;
213 }
214
215 placeholder.push(character);
216 }
217
218 if !closed {
219 result.push('{');
221 result.push_str(&placeholder);
222 break;
223 }
224
225 match placeholder.parse::<usize>() {
226 Ok(index) => match args.get(index) {
227 Some(value) => result.push_str(value),
228 None => {
229 result.push('{');
230 result.push_str(&placeholder);
231 result.push('}');
232 }
233 },
234 Err(_) => {
235 result.push('{');
236 result.push_str(&placeholder);
237 result.push('}');
238 }
239 }
240 }
241 '}' => {
242 if characters.peek() == Some(&'}') {
243 characters.next();
244 result.push('}');
245 continue;
246 }
247
248 result.push('}');
249 }
250 _ => result.push(character),
251 }
252 }
253
254 result
255}