1use prometheus_client::registry::Registry;
41use std::sync::{LazyLock, RwLock};
42
43pub static GLOBAL_REGISTRY: LazyLock<RwLock<Registry>> =
49 LazyLock::new(|| RwLock::new(Registry::default()));
50
51pub fn register_metric<M>(name: &str, help: &str, metric: M)
55where
56 M: prometheus_client::encoding::EncodeMetric + Clone + std::fmt::Debug + Send + Sync + 'static,
57{
58 GLOBAL_REGISTRY
59 .write()
60 .unwrap()
61 .register(name, help, metric);
62}
63
64pub use prometheus_client;
66pub use prometheus_derive_macros::prometheus_metrics;
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use prometheus_client::encoding::text::encode;
72 use prometheus_client::metrics::counter::Counter;
73 use prometheus_client::metrics::gauge::Gauge;
74 use std::sync::LazyLock;
75
76 pub static TEST_COUNTER: LazyLock<Counter> = LazyLock::new(|| Counter::default());
78
79 #[derive(Clone, Debug, Hash, PartialEq, Eq, prometheus_client::encoding::EncodeLabelSet)]
80 pub struct TestLabeledCounterLabels {
81 pub method: String,
82 }
83
84 pub static TEST_LABELED_COUNTER: LazyLock<
85 prometheus_client::metrics::family::Family<TestLabeledCounterLabels, Counter>,
86 > = LazyLock::new(|| prometheus_client::metrics::family::Family::default());
87
88 #[test]
89 fn test_metrics_without_registration() {
90 let test_registry = Registry::default();
92 let mut buffer = String::new();
93 encode(&mut buffer, &test_registry).unwrap();
94
95 assert!(
97 !buffer.contains("TEST_COUNTER"),
98 "Metrics should not appear without registration"
99 );
100 assert!(
101 !buffer.contains("TEST_LABELED_COUNTER"),
102 "Metrics should not appear without registration"
103 );
104
105 TEST_COUNTER.inc();
107 TEST_LABELED_COUNTER
108 .get_or_create(&TestLabeledCounterLabels {
109 method: "GET".to_string(),
110 })
111 .inc();
112
113 let mut buffer2 = String::new();
115 encode(&mut buffer2, &test_registry).unwrap();
116 assert!(
117 !buffer2.contains("TEST_COUNTER"),
118 "Metrics should not appear without registration"
119 );
120 assert!(
121 !buffer2.contains("TEST_LABELED_COUNTER"),
122 "Metrics should not appear without registration"
123 );
124 }
125
126 #[test]
127 fn test_global_registry_with_manual_registration() {
128 register_metric("test_counter_total", "Test counter", TEST_COUNTER.clone());
130 register_metric(
131 "test_labeled_counter_total",
132 "Test counter with labels",
133 TEST_LABELED_COUNTER.clone(),
134 );
135
136 TEST_COUNTER.inc();
138 TEST_LABELED_COUNTER
139 .get_or_create(&TestLabeledCounterLabels {
140 method: "GET".to_string(),
141 })
142 .inc();
143
144 let registry = GLOBAL_REGISTRY.read().unwrap();
146 let mut buffer = String::new();
147 encode(&mut buffer, ®istry).unwrap();
148
149 assert!(buffer.contains("test_counter_total"));
151 assert!(buffer.contains("test_labeled_counter_total"));
152 assert!(buffer.contains("method=\"GET\""));
153 }
154
155 #[test]
156 fn test_global_registry_with_generated_registration() {
157 register_metric(
159 "TEST_COUNTER",
160 "Generated metric: TEST_COUNTER",
161 TEST_COUNTER.clone(),
162 );
163 register_metric(
164 "TEST_LABELED_COUNTER",
165 "Generated metric: TEST_LABELED_COUNTER",
166 TEST_LABELED_COUNTER.clone(),
167 );
168
169 TEST_COUNTER.inc();
171 TEST_LABELED_COUNTER
172 .get_or_create(&TestLabeledCounterLabels {
173 method: "POST".to_string(),
174 })
175 .inc();
176
177 let registry = GLOBAL_REGISTRY.read().unwrap();
179 let mut buffer = String::new();
180 encode(&mut buffer, ®istry).unwrap();
181
182 assert!(buffer.contains("TEST_COUNTER"));
184 assert!(buffer.contains("TEST_LABELED_COUNTER"));
185 assert!(buffer.contains("method=\"POST\""));
186 }
187
188 #[test]
189 fn test_multi_module_metrics_in_global_registry() {
190 use prometheus_client::metrics::family::Family;
191
192 static MODULE_A_REQUESTS: LazyLock<Family<ModuleARequestsLabels, Counter>> =
195 LazyLock::new(|| {
196 let family = Family::default();
197 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
198 registry.register(
199 "MODULE_A_REQUESTS",
200 "HTTP requests from module A",
201 family.clone(),
202 );
203 }
204 family
205 });
206 static MODULE_A_MEMORY: LazyLock<Gauge> = LazyLock::new(|| {
207 let gauge = Gauge::default();
208 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
209 registry.register("MODULE_A_MEMORY", "Memory usage in module A", gauge.clone());
210 }
211 gauge
212 });
213
214 static MODULE_B_DB_CONNECTIONS: LazyLock<Family<ModuleBDbConnectionsLabels, Counter>> =
216 LazyLock::new(|| {
217 let family = Family::default();
218 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
219 registry.register(
220 "MODULE_B_DB_CONNECTIONS",
221 "Database connections from module B",
222 family.clone(),
223 );
224 }
225 family
226 });
227 static MODULE_B_CACHE_HIT_RATIO: LazyLock<Gauge> = LazyLock::new(|| {
228 let gauge = Gauge::default();
229 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
230 registry.register(
231 "MODULE_B_CACHE_HIT_RATIO",
232 "Cache hit ratio in module B",
233 gauge.clone(),
234 );
235 }
236 gauge
237 });
238
239 #[derive(
241 Clone, Debug, Hash, PartialEq, Eq, prometheus_client::encoding::EncodeLabelSet,
242 )]
243 struct ModuleARequestsLabels {
244 endpoint: String,
245 }
246
247 #[derive(
248 Clone, Debug, Hash, PartialEq, Eq, prometheus_client::encoding::EncodeLabelSet,
249 )]
250 struct ModuleBDbConnectionsLabels {
251 db_name: String,
252 pool: String,
253 }
254
255 MODULE_A_REQUESTS
257 .get_or_create(&ModuleARequestsLabels {
258 endpoint: "/api/users".to_string(),
259 })
260 .inc();
261 MODULE_A_MEMORY.set(1024);
262
263 MODULE_B_DB_CONNECTIONS
264 .get_or_create(&ModuleBDbConnectionsLabels {
265 db_name: "users".to_string(),
266 pool: "primary".to_string(),
267 })
268 .inc();
269 MODULE_B_CACHE_HIT_RATIO.set(85);
270
271 let registry = GLOBAL_REGISTRY.read().unwrap();
273 let mut buffer = String::new();
274 encode(&mut buffer, ®istry).unwrap();
275
276 assert!(
278 buffer.contains("MODULE_A_REQUESTS"),
279 "Module A requests metric should be in global registry"
280 );
281 assert!(
282 buffer.contains("MODULE_A_MEMORY"),
283 "Module A memory metric should be in global registry"
284 );
285 assert!(
286 buffer.contains("endpoint=\"/api/users\""),
287 "Module A labels should be present"
288 );
289
290 assert!(
292 buffer.contains("MODULE_B_DB_CONNECTIONS"),
293 "Module B DB connections metric should be in global registry"
294 );
295 assert!(
296 buffer.contains("MODULE_B_CACHE_HIT_RATIO"),
297 "Module B cache ratio metric should be in global registry"
298 );
299 assert!(
300 buffer.contains("db_name=\"users\""),
301 "Module B labels should be present"
302 );
303 assert!(
304 buffer.contains("pool=\"primary\""),
305 "Module B pool label should be present"
306 );
307
308 let module_a_count = buffer.matches("MODULE_A").count();
310 let module_b_count = buffer.matches("MODULE_B").count();
311 assert!(
312 module_a_count >= 2,
313 "Should have at least 2 module A metrics"
314 );
315 assert!(
316 module_b_count >= 2,
317 "Should have at least 2 module B metrics"
318 );
319
320 println!("Multi-module test successful! Registry contains metrics from both modules:");
321 println!("{}", buffer);
322 }
323
324 #[test]
325 fn test_web_server_pattern() {
326 static HTTP_REQUESTS_TOTAL: LazyLock<
330 prometheus_client::metrics::family::Family<HttpRequestsLabels, Counter>,
331 > = LazyLock::new(|| {
332 let family = prometheus_client::metrics::family::Family::default();
333 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
334 registry.register("HTTP_REQUESTS_TOTAL", "Total HTTP requests", family.clone());
335 }
336 family
337 });
338
339 static USERS_TOTAL: LazyLock<Counter> = LazyLock::new(|| {
341 let counter = Counter::default();
342 if let Ok(mut registry) = GLOBAL_REGISTRY.write() {
343 registry.register("USERS_TOTAL", "Total number of users", counter.clone());
344 }
345 counter
346 });
347
348 #[derive(
349 Clone, Debug, Hash, PartialEq, Eq, prometheus_client::encoding::EncodeLabelSet,
350 )]
351 struct HttpRequestsLabels {
352 method: String,
353 endpoint: String,
354 status: u16,
355 }
356
357 HTTP_REQUESTS_TOTAL
359 .get_or_create(&HttpRequestsLabels {
360 method: "GET".to_string(),
361 endpoint: "/".to_string(),
362 status: 200,
363 })
364 .inc();
365
366 USERS_TOTAL.inc(); let registry = GLOBAL_REGISTRY.read().unwrap();
370 let mut buffer = String::new();
371 encode(&mut buffer, ®istry).unwrap();
372
373 println!("Web server pattern test - Registry contents:");
374 println!("{}", buffer);
375
376 assert!(
378 buffer.contains("HTTP_REQUESTS_TOTAL"),
379 "Main module metric should be present"
380 );
381 assert!(
382 buffer.contains("USERS_TOTAL"),
383 "Other module metric should be present"
384 );
385 assert!(
386 buffer.contains("method=\"GET\""),
387 "HTTP request labels should be present"
388 );
389 }
390}