Skip to main content

xmtp_common/event_logging/
utils.rs

1use std::sync::atomic::{AtomicU8, Ordering};
2
3use crate::Event;
4
5/// Metadata about a log event variant, including its doc comment and required context fields.
6/// This struct is used by proc macros to access event metadata at compile time.
7#[derive(Debug, Clone, Copy)]
8pub struct EventMetadata {
9    /// The name of the enum variant
10    pub name: &'static str,
11    pub event: Event,
12    /// The doc comment describing the event
13    pub doc: &'static str,
14    /// The required context fields for this event
15    pub context_fields: &'static [&'static str],
16
17    pub icon: &'static str,
18}
19
20impl EventMetadata {
21    /// Validates that all required context fields are provided.
22    /// Panics at compile time with the missing field name if validation fails.
23    pub const fn validate_fields(&self, provided: &[&str]) {
24        let mut i = 0;
25        while i < self.context_fields.len() {
26            let required = self.context_fields[i];
27            if !str_contains(provided, required) {
28                const_panic::concat_panic!(
29                    "log_event! missing required context field: `",
30                    required,
31                    "`"
32                );
33            }
34            i += 1;
35        }
36    }
37}
38
39const fn str_contains(haystack: &[&str], needle: &str) -> bool {
40    let mut i = 0;
41    while i < haystack.len() {
42        if str_eq(haystack[i], needle) {
43            return true;
44        }
45        i += 1;
46    }
47    false
48}
49
50const fn str_eq(a: &str, b: &str) -> bool {
51    let a = a.as_bytes();
52    let b = b.as_bytes();
53    if a.len() != b.len() {
54        return false;
55    }
56    let mut i = 0;
57    while i < a.len() {
58        if a[i] != b[i] {
59            return false;
60        }
61        i += 1;
62    }
63    true
64}
65
66const UNINITIALIZED: u8 = 0;
67const STRUCTURED: u8 = 1;
68const NOT_STRUCTURED: u8 = 2;
69
70static STRUCTURED_LOGGING: AtomicU8 = AtomicU8::new(UNINITIALIZED);
71
72/// Returns true if structured (JSON) logging is enabled.
73/// When true, context should not be embedded in the message to avoid duplication.
74/// Initializes from environment on first call, then caches the result.
75#[inline]
76pub fn is_structured_logging() -> bool {
77    match STRUCTURED_LOGGING.load(Ordering::Relaxed) {
78        STRUCTURED => true,
79        NOT_STRUCTURED => false,
80        _ => is_structured_logging_init(),
81    }
82}
83
84#[cold]
85fn is_structured_logging_init() -> bool {
86    let is_structured = std::env::var("STRUCTURED").is_ok_and(|s| s == "true" || s == "1");
87    STRUCTURED_LOGGING.store(
88        if is_structured {
89            STRUCTURED
90        } else {
91            NOT_STRUCTURED
92        },
93        Ordering::Relaxed,
94    );
95    is_structured
96}