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
18impl EventMetadata {
19    /// Validates that all required context fields are provided.
20    /// Panics at compile time with the missing field name if validation fails.
21    pub const fn validate_fields(&self, provided: &[&str]) {
22        let mut i = 0;
23        while i < self.context_fields.len() {
24            let required = self.context_fields[i];
25            if !str_contains(provided, required) {
26                const_panic::concat_panic!(
27                    "log_event! missing required context field: `",
28                    required,
29                    "`"
30                );
31            }
32            i += 1;
33        }
34    }
35}
36
37const fn str_contains(haystack: &[&str], needle: &str) -> bool {
38    let mut i = 0;
39    while i < haystack.len() {
40        if str_eq(haystack[i], needle) {
41            return true;
42        }
43        i += 1;
44    }
45    false
46}
47
48const fn str_eq(a: &str, b: &str) -> bool {
49    let a = a.as_bytes();
50    let b = b.as_bytes();
51    if a.len() != b.len() {
52        return false;
53    }
54    let mut i = 0;
55    while i < a.len() {
56        if a[i] != b[i] {
57            return false;
58        }
59        i += 1;
60    }
61    true
62}
63
64const UNINITIALIZED: u8 = 0;
65const STRUCTURED: u8 = 1;
66const NOT_STRUCTURED: u8 = 2;
67
68static STRUCTURED_LOGGING: AtomicU8 = AtomicU8::new(UNINITIALIZED);
69
70/// Returns true if structured (JSON) logging is enabled.
71/// When true, context should not be embedded in the message to avoid duplication.
72/// Initializes from environment on first call, then caches the result.
73#[inline]
74pub fn is_structured_logging() -> bool {
75    match STRUCTURED_LOGGING.load(Ordering::Relaxed) {
76        STRUCTURED => true,
77        NOT_STRUCTURED => false,
78        _ => is_structured_logging_init(),
79    }
80}
81
82#[cold]
83fn is_structured_logging_init() -> bool {
84    let is_structured = std::env::var("STRUCTURED").is_ok_and(|s| s == "true" || s == "1");
85    STRUCTURED_LOGGING.store(
86        if is_structured {
87            STRUCTURED
88        } else {
89            NOT_STRUCTURED
90        },
91        Ordering::Relaxed,
92    );
93    is_structured
94}