Skip to main content

glean_core/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5#![allow(clippy::doc_overindented_list_items)]
6#![allow(clippy::significant_drop_in_scrutinee)]
7#![allow(clippy::uninlined_format_args)]
8#![deny(rustdoc::broken_intra_doc_links)]
9#![deny(missing_docs)]
10
11//! Glean is a modern approach for recording and sending Telemetry data.
12//!
13//! It's in use at Mozilla.
14//!
15//! All documentation can be found online:
16//!
17//! ## [The Glean SDK Book](https://mozilla.github.io/glean)
18
19use std::borrow::Cow;
20use std::collections::HashMap;
21use std::path::Path;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::{Arc, Mutex};
24use std::time::{Duration, UNIX_EPOCH};
25use std::{fmt, fs};
26
27use crossbeam_channel::unbounded;
28use log::LevelFilter;
29use malloc_size_of_derive::MallocSizeOf;
30use once_cell::sync::{Lazy, OnceCell};
31use uuid::Uuid;
32
33use metrics::RemoteSettingsConfig;
34
35mod common_metric_data;
36mod core;
37mod core_metrics;
38mod database;
39mod debug;
40#[cfg(feature = "benchmark")]
41#[doc(hidden)]
42pub mod dispatcher;
43#[cfg(not(feature = "benchmark"))]
44mod dispatcher;
45mod error;
46mod error_recording;
47mod event_database;
48mod glean_metrics;
49mod histogram;
50mod internal_metrics;
51mod internal_pings;
52pub mod metrics;
53pub mod ping;
54mod scheduler;
55pub(crate) mod session;
56pub mod storage;
57mod system;
58#[doc(hidden)]
59pub mod thread;
60pub mod traits;
61pub mod upload;
62mod util;
63
64#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
65mod fd_logger;
66
67pub use crate::common_metric_data::{CommonMetricData, DynamicLabelType, Lifetime};
68pub use crate::core::Glean;
69pub use crate::core_metrics::{AttributionMetrics, ClientInfoMetrics, DistributionMetrics};
70use crate::dispatcher::is_test_mode;
71pub use crate::error::{Error, ErrorKind, Result};
72pub use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
73pub use crate::histogram::HistogramType;
74use crate::internal_metrics::DataDirectoryInfoObject;
75pub use crate::metrics::labeled::{
76    AllowLabeled, LabeledBoolean, LabeledCounter, LabeledCustomDistribution,
77    LabeledMemoryDistribution, LabeledMetric, LabeledMetricData, LabeledQuantity, LabeledString,
78    LabeledTimingDistribution,
79};
80pub use crate::metrics::{
81    BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric,
82    DenominatorMetric, DistributionData, DualLabeledCounterMetric, EventMetric,
83    LocalCustomDistribution, LocalMemoryDistribution, LocalTimingDistribution,
84    MemoryDistributionMetric, MemoryUnit, NumeratorMetric, ObjectMetric, PingType, QuantityMetric,
85    Rate, RateMetric, RecordedEvent, RecordedExperiment, StringListMetric, StringMetric,
86    TestGetValue, TextMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric,
87    UrlMetric, UuidMetric,
88};
89pub use crate::session::{SessionManager, SessionMetadata, SessionMode};
90pub use crate::upload::{PingRequest, PingUploadTask, UploadResult, UploadTaskAction};
91
92const GLEAN_VERSION: &str = env!("CARGO_PKG_VERSION");
93const GLEAN_SCHEMA_VERSION: u32 = 1;
94const DEFAULT_MAX_EVENTS: u32 = 500;
95static KNOWN_CLIENT_ID: Lazy<Uuid> =
96    Lazy::new(|| Uuid::parse_str("c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0").unwrap());
97
98// The names of the pings directories.
99pub(crate) const PENDING_PINGS_DIRECTORY: &str = "pending_pings";
100pub(crate) const DELETION_REQUEST_PINGS_DIRECTORY: &str = "deletion_request";
101
102/// Set when `glean::initialize()` returns.
103/// This allows to detect calls that happen before `glean::initialize()` was called.
104/// Note: The initialization might still be in progress, as it runs in a separate thread.
105static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false);
106
107/// Keep track of the debug features before Glean is initialized.
108static PRE_INIT_DEBUG_VIEW_TAG: Mutex<String> = Mutex::new(String::new());
109static PRE_INIT_LOG_PINGS: AtomicBool = AtomicBool::new(false);
110static PRE_INIT_SOURCE_TAGS: Mutex<Vec<String>> = Mutex::new(Vec::new());
111
112/// Keep track of pings registered before Glean is initialized.
113static PRE_INIT_PING_REGISTRATION: Mutex<Vec<metrics::PingType>> = Mutex::new(Vec::new());
114static PRE_INIT_PING_ENABLED: Mutex<Vec<(metrics::PingType, bool)>> = Mutex::new(Vec::new());
115
116/// Keep track of attribution and distribution supplied before Glean is initialized.
117static PRE_INIT_ATTRIBUTION: Mutex<Option<AttributionMetrics>> = Mutex::new(None);
118static PRE_INIT_DISTRIBUTION: Mutex<Option<DistributionMetrics>> = Mutex::new(None);
119
120/// Global singleton of the handles of the glean.init threads.
121/// For joining. For tests.
122/// (Why a Vec? There might be more than one concurrent call to initialize.)
123static INIT_HANDLES: Lazy<Arc<Mutex<Vec<std::thread::JoinHandle<()>>>>> =
124    Lazy::new(|| Arc::new(Mutex::new(Vec::new())));
125
126/// Configuration for Glean
127#[derive(Debug, Clone, MallocSizeOf)]
128pub struct InternalConfiguration {
129    /// Whether upload should be enabled.
130    pub upload_enabled: bool,
131    /// Path to a directory to store all data in.
132    pub data_path: String,
133    /// The application ID (will be sanitized during initialization).
134    pub application_id: String,
135    /// The name of the programming language used by the binding creating this instance of Glean.
136    pub language_binding_name: String,
137    /// The maximum number of events to store before sending a ping containing events.
138    pub max_events: Option<u32>,
139    /// Whether Glean should delay persistence of data from metrics with ping lifetime.
140    pub delay_ping_lifetime_io: bool,
141    /// The application's build identifier. If this is different from the one provided for a previous init,
142    /// and use_core_mps is `true`, we will trigger a "metrics" ping.
143    pub app_build: String,
144    /// Whether Glean should schedule "metrics" pings.
145    pub use_core_mps: bool,
146    /// Whether Glean should, on init, trim its event storage to only the registered pings.
147    pub trim_data_to_registered_pings: bool,
148    /// The internal logging level.
149    /// ignore
150    #[ignore_malloc_size_of = "external non-allocating type"]
151    pub log_level: Option<LevelFilter>,
152    /// The rate at which pings may be uploaded before they are throttled.
153    pub rate_limit: Option<PingRateLimit>,
154    /// Whether to add a wallclock timestamp to all events.
155    pub enable_event_timestamps: bool,
156    /// An experimentation identifier derived by the application to be sent with all pings, it should
157    /// be noted that this has an underlying StringMetric and so should conform to the limitations that
158    /// StringMetric places on length, etc.
159    pub experimentation_id: Option<String>,
160    /// Whether to enable internal pings. Default: true
161    pub enable_internal_pings: bool,
162    /// A ping schedule map.
163    /// Maps a ping name to a list of pings to schedule along with it.
164    /// Only used if the ping's own ping schedule list is empty.
165    pub ping_schedule: HashMap<String, Vec<String>>,
166
167    /// Write count threshold when to auto-flush. `0` disables it.
168    pub ping_lifetime_threshold: u64,
169    /// After what time to auto-flush. 0 disables it.
170    pub ping_lifetime_max_time: u64,
171    /// Maximum number of pending pings on disk. Overrides the default when set.
172    pub max_pending_pings_count: Option<u64>,
173    /// Maximum size in bytes of the pending pings directory. Overrides the default when set.
174    pub max_pending_pings_directory_size: Option<u64>,
175    /// Session management mode. Default: `Auto`.
176    pub session_mode: session::SessionMode,
177    /// The fraction of sessions to sample (0.0–1.0). Default: `1.0` (all sessions).
178    pub session_sample_rate: f64,
179    /// Inactivity timeout in milliseconds for AUTO mode before a new session starts.
180    /// Default: 1 800 000 ms (30 minutes).
181    pub session_inactivity_timeout_ms: u64,
182}
183
184/// How to specify the rate at which pings may be uploaded before they are throttled.
185#[derive(Debug, Clone, MallocSizeOf)]
186pub struct PingRateLimit {
187    /// Length of time in seconds of a ping uploading interval.
188    pub seconds_per_interval: u64,
189    /// Number of pings that may be uploaded in a ping uploading interval.
190    pub pings_per_interval: u32,
191}
192
193/// Launches a new task on the global dispatch queue with a reference to the Glean singleton.
194fn launch_with_glean(callback: impl FnOnce(&Glean) + Send + 'static) {
195    dispatcher::launch(|| core::with_glean(callback));
196}
197
198/// Launches a new task on the global dispatch queue with a mutable reference to the
199/// Glean singleton.
200fn launch_with_glean_mut(callback: impl FnOnce(&mut Glean) + Send + 'static) {
201    dispatcher::launch(|| core::with_glean_mut(callback));
202}
203
204/// Block on the dispatcher emptying.
205///
206/// This will panic if called before Glean is initialized.
207fn block_on_dispatcher() {
208    dispatcher::block_on_queue()
209}
210
211/// Returns a timestamp corresponding to "now" with millisecond precision, awake time only.
212pub fn get_awake_timestamp_ms() -> u64 {
213    const NANOS_PER_MILLI: u64 = 1_000_000;
214    zeitstempel::now_awake() / NANOS_PER_MILLI
215}
216
217/// Returns a timestamp corresponding to "now" with millisecond precision.
218pub fn get_timestamp_ms() -> u64 {
219    const NANOS_PER_MILLI: u64 = 1_000_000;
220    zeitstempel::now() / NANOS_PER_MILLI
221}
222
223/// State to keep track for the Rust Language bindings.
224///
225/// This is useful for setting Glean SDK-owned metrics when
226/// the state of the upload is toggled.
227struct State {
228    /// Client info metrics set by the application.
229    client_info: ClientInfoMetrics,
230
231    callbacks: Box<dyn OnGleanEvents>,
232}
233
234/// A global singleton storing additional state for Glean.
235///
236/// Requires a Mutex, because in tests we can actual reset this.
237static STATE: OnceCell<Mutex<State>> = OnceCell::new();
238
239/// Get a reference to the global state object.
240///
241/// Panics if no global state object was set.
242#[track_caller] // If this fails we're interested in the caller.
243fn global_state() -> &'static Mutex<State> {
244    STATE.get().unwrap()
245}
246
247/// Attempt to get a reference to the global state object.
248///
249/// If it hasn't been set yet, we return None.
250#[track_caller] // If this fails we're interested in the caller.
251fn maybe_global_state() -> Option<&'static Mutex<State>> {
252    STATE.get()
253}
254
255/// Set or replace the global bindings State object.
256fn setup_state(state: State) {
257    // The `OnceCell` type wrapping our state is thread-safe and can only be set once.
258    // Therefore even if our check for it being empty succeeds, setting it could fail if a
259    // concurrent thread is quicker in setting it.
260    // However this will not cause a bigger problem, as the second `set` operation will just fail.
261    // We can log it and move on.
262    //
263    // For all wrappers this is not a problem, as the State object is intialized exactly once on
264    // calling `initialize` on the global singleton and further operations check that it has been
265    // initialized.
266    if STATE.get().is_none() {
267        if STATE.set(Mutex::new(state)).is_err() {
268            log::error!(
269                "Global Glean state object is initialized already. This probably happened concurrently."
270            );
271        }
272    } else {
273        // We allow overriding the global State object to support test mode.
274        // In test mode the State object is fully destroyed and recreated.
275        // This all happens behind a mutex and is therefore also thread-safe.
276        let mut lock = STATE.get().unwrap().lock().unwrap();
277        *lock = state;
278    }
279}
280
281/// A global singleton that stores listener callbacks registered with Glean
282/// to receive event recording notifications.
283static EVENT_LISTENERS: OnceCell<Mutex<HashMap<String, Box<dyn GleanEventListener>>>> =
284    OnceCell::new();
285
286fn event_listeners() -> &'static Mutex<HashMap<String, Box<dyn GleanEventListener>>> {
287    EVENT_LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
288}
289
290fn register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
291    let mut lock = event_listeners().lock().unwrap();
292    lock.insert(tag, listener);
293}
294
295fn unregister_event_listener(tag: String) {
296    let mut lock = event_listeners().lock().unwrap();
297    lock.remove(&tag);
298}
299
300/// An error returned from callbacks.
301#[derive(Debug)]
302pub enum CallbackError {
303    /// An unexpected error occured.
304    UnexpectedError,
305}
306
307impl fmt::Display for CallbackError {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        write!(f, "Unexpected error")
310    }
311}
312
313impl std::error::Error for CallbackError {}
314
315impl From<uniffi::UnexpectedUniFFICallbackError> for CallbackError {
316    fn from(_: uniffi::UnexpectedUniFFICallbackError) -> CallbackError {
317        CallbackError::UnexpectedError
318    }
319}
320
321/// A callback object used to trigger actions on the foreign-language side.
322///
323/// A callback object is stored in glean-core for the entire lifetime of the application.
324pub trait OnGleanEvents: Send {
325    /// Initialization finished.
326    ///
327    /// The language SDK can do additional things from within the same initializer thread,
328    /// e.g. starting to observe application events for foreground/background behavior.
329    /// The observer then needs to call the respective client activity API.
330    fn initialize_finished(&self);
331
332    /// Trigger the uploader whenever a ping was submitted.
333    ///
334    /// This should not block.
335    /// The uploader needs to asynchronously poll Glean for new pings to upload.
336    fn trigger_upload(&self) -> Result<(), CallbackError>;
337
338    /// Start the Metrics Ping Scheduler.
339    fn start_metrics_ping_scheduler(&self) -> bool;
340
341    /// Called when upload is disabled and uploads should be stopped
342    fn cancel_uploads(&self) -> Result<(), CallbackError>;
343
344    /// Called on shutdown, before glean-core is fully shutdown.
345    ///
346    /// * This MUST NOT put any new tasks on the dispatcher.
347    ///   * New tasks will be ignored.
348    /// * This SHOULD NOT block arbitrarily long.
349    ///   * Shutdown waits for a maximum of 30 seconds.
350    fn shutdown(&self) -> Result<(), CallbackError> {
351        // empty by default
352        Ok(())
353    }
354}
355
356/// A callback handler that receives the base identifier of recorded events
357/// The identifier is in the format: `<category>.<name>`
358pub trait GleanEventListener: Send {
359    /// Called when an event is recorded, indicating the id of the event
360    fn on_event_recorded(&self, id: String);
361}
362
363/// Initializes Glean.
364///
365/// # Arguments
366///
367/// * `cfg` - the [`InternalConfiguration`] options to initialize with.
368/// * `client_info` - the [`ClientInfoMetrics`] values used to set Glean
369///   core metrics.
370/// * `callbacks` - A callback object, stored for the entire application lifetime.
371pub fn glean_initialize(
372    cfg: InternalConfiguration,
373    client_info: ClientInfoMetrics,
374    callbacks: Box<dyn OnGleanEvents>,
375) {
376    initialize_inner(cfg, client_info, callbacks);
377}
378
379/// Shuts down Glean in an orderly fashion.
380pub fn glean_shutdown() {
381    shutdown();
382}
383
384/// Creates and initializes a new Glean object for use in a subprocess.
385///
386/// Importantly, this will not send any pings at startup, since that
387/// sort of management should only happen in the main process.
388pub fn glean_initialize_for_subprocess(cfg: InternalConfiguration) -> bool {
389    let glean = match Glean::new_for_subprocess(&cfg, true) {
390        Ok(glean) => glean,
391        Err(err) => {
392            log::error!("Failed to initialize Glean: {}", err);
393            return false;
394        }
395    };
396    if core::setup_glean(glean).is_err() {
397        return false;
398    }
399    log::info!("Glean initialized for subprocess");
400    true
401}
402
403fn initialize_inner(
404    cfg: InternalConfiguration,
405    client_info: ClientInfoMetrics,
406    callbacks: Box<dyn OnGleanEvents>,
407) {
408    if was_initialize_called() {
409        log::error!("Glean should not be initialized multiple times");
410        return;
411    }
412
413    let init_handle = thread::spawn("glean.init", move || {
414        let upload_enabled = cfg.upload_enabled;
415        let trim_data_to_registered_pings = cfg.trim_data_to_registered_pings;
416
417        // Set the internal logging level.
418        if let Some(level) = cfg.log_level {
419            log::set_max_level(level)
420        }
421
422        let data_path_str = cfg.data_path.clone();
423        let data_path = Path::new(&data_path_str);
424        let internal_pings_enabled = cfg.enable_internal_pings;
425        let dir_info = if !is_test_mode() && internal_pings_enabled {
426            collect_directory_info(Path::new(&data_path))
427        } else {
428            None
429        };
430
431        let glean = match Glean::new(cfg) {
432            Ok(glean) => glean,
433            Err(err) => {
434                log::error!("Failed to initialize Glean: {}", err);
435                return;
436            }
437        };
438        if core::setup_glean(glean).is_err() {
439            return;
440        }
441
442        log::info!("Glean initialized");
443
444        core::with_glean(|glean| {
445            glean.health_metrics.init_count.add_sync(glean, 1);
446        });
447
448        setup_state(State {
449            client_info,
450            callbacks,
451        });
452
453        let mut is_first_run = false;
454        let mut dirty_flag = false;
455        let mut pings_submitted = false;
456        core::with_glean_mut(|glean| {
457            // The debug view tag might have been set before initialize,
458            // get the cached value and set it.
459            let debug_tag = PRE_INIT_DEBUG_VIEW_TAG.lock().unwrap();
460            if !debug_tag.is_empty() {
461                glean.set_debug_view_tag(&debug_tag);
462            }
463
464            // The log pings debug option might have been set before initialize,
465            // get the cached value and set it.
466            let log_pigs = PRE_INIT_LOG_PINGS.load(Ordering::SeqCst);
467            if log_pigs {
468                glean.set_log_pings(log_pigs);
469            }
470
471            // The source tags might have been set before initialize,
472            // get the cached value and set them.
473            let source_tags = PRE_INIT_SOURCE_TAGS.lock().unwrap();
474            if !source_tags.is_empty() {
475                glean.set_source_tags(source_tags.to_vec());
476            }
477
478            // Get the current value of the dirty flag so we know whether to
479            // send a dirty startup baseline ping below.  Immediately set it to
480            // `false` so that dirty startup pings won't be sent if Glean
481            // initialization does not complete successfully.
482            dirty_flag = glean.is_dirty_flag_set();
483            glean.set_dirty_flag(false);
484
485            // Session crash recovery: if the dirty flag was set, the previous
486            // run ended abnormally. Emit a synthetic session_end for any
487            // persisted session.
488            if dirty_flag {
489                glean.recover_session_on_dirty_flag();
490            }
491
492            // Perform registration of pings that were attempted to be
493            // registered before init.
494            let pings = PRE_INIT_PING_REGISTRATION.lock().unwrap();
495            for ping in pings.iter() {
496                glean.register_ping_type(ping);
497            }
498            let pings = PRE_INIT_PING_ENABLED.lock().unwrap();
499            for (ping, enabled) in pings.iter() {
500                glean.set_ping_enabled(ping, *enabled);
501            }
502
503            // The attribution and distribution might have been set before initialize,
504            // take the cached values and set them.
505            if let Some(attribution) = PRE_INIT_ATTRIBUTION.lock().unwrap().take() {
506                glean.update_attribution(attribution);
507            }
508            if let Some(distribution) = PRE_INIT_DISTRIBUTION.lock().unwrap().take() {
509                glean.update_distribution(distribution);
510            }
511
512            // If this is the first time ever the Glean SDK runs, make sure to set
513            // some initial core metrics in case we need to generate early pings.
514            // The next times we start, we would have them around already.
515            is_first_run = glean.is_first_run();
516            if is_first_run {
517                let state = global_state().lock().unwrap();
518                initialize_core_metrics(glean, &state.client_info);
519            }
520
521            // Deal with any pending events so we can start recording new ones
522            pings_submitted = glean.on_ready_to_submit_pings(trim_data_to_registered_pings);
523        });
524
525        {
526            let state = global_state().lock().unwrap();
527            // We need to kick off upload in these cases:
528            // 1. Pings were submitted through Glean and it is ready to upload those pings;
529            // 2. Upload is disabled, to upload a possible deletion-request ping.
530            if pings_submitted || !upload_enabled {
531                if let Err(e) = state.callbacks.trigger_upload() {
532                    log::error!("Triggering upload failed. Error: {}", e);
533                }
534            }
535        }
536
537        core::with_glean(|glean| {
538            // Start the MPS if its handled within Rust.
539            glean.start_metrics_ping_scheduler();
540        });
541
542        // The metrics ping scheduler might _synchronously_ submit a ping
543        // so that it runs before we clear application-lifetime metrics further below.
544        // For that it needs access to the `Glean` object.
545        // Thus we need to unlock that by leaving the context above,
546        // then re-lock it afterwards.
547        // That's safe because user-visible functions will be queued and thus not execute until
548        // we unblock later anyway.
549        {
550            let state = global_state().lock().unwrap();
551
552            // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
553            // ping startup check should be performed before any other ping, since it relies
554            // on being dispatched to the API context before any other metric.
555            if state.callbacks.start_metrics_ping_scheduler() {
556                if let Err(e) = state.callbacks.trigger_upload() {
557                    log::error!("Triggering upload failed. Error: {}", e);
558                }
559            }
560        }
561
562        core::with_glean_mut(|glean| {
563            let state = global_state().lock().unwrap();
564
565            // Check if the "dirty flag" is set. That means the product was probably
566            // force-closed. If that's the case, submit a 'baseline' ping with the
567            // reason "dirty_startup". We only do that from the second run.
568            if !is_first_run && dirty_flag {
569                // The `submit_ping_by_name_sync` function cannot be used, otherwise
570                // startup will cause a dead-lock, since that function requests a
571                // write lock on the `glean` object.
572                // Note that unwrapping below is safe: the function will return an
573                // `Ok` value for a known ping.
574                if glean.submit_ping_by_name("baseline", Some("dirty_startup")) {
575                    if let Err(e) = state.callbacks.trigger_upload() {
576                        log::error!("Triggering upload failed. Error: {}", e);
577                    }
578                }
579            }
580
581            // From the second time we run, after all startup pings are generated,
582            // make sure to clear `lifetime: application` metrics and set them again.
583            // Any new value will be sent in newly generated pings after startup.
584            if !is_first_run {
585                glean.clear_application_lifetime_metrics();
586                initialize_core_metrics(glean, &state.client_info);
587            }
588        });
589
590        // Signal Dispatcher that init is complete
591        // bug 1839433: It is important that this happens after any init tasks
592        // that shutdown() depends on. At time of writing that's only setting up
593        // the global Glean, but it is probably best to flush the preinit queue
594        // as late as possible in the glean.init thread.
595        match dispatcher::flush_init() {
596            Ok(task_count) if task_count > 0 => {
597                core::with_glean(|glean| {
598                    glean_metrics::error::preinit_tasks_overflow.add_sync(glean, task_count as i32);
599                });
600            }
601            Ok(_) => {}
602            Err(err) => log::error!("Unable to flush the preinit queue: {}", err),
603        }
604
605        if !is_test_mode() && internal_pings_enabled {
606            // Now that Glean is initialized, we can capture the directory info from the pre_init phase and send it in
607            // a health ping with reason "pre_init".
608            record_dir_info_and_submit_health_ping(dir_info, "pre_init");
609
610            let state = global_state().lock().unwrap();
611            if let Err(e) = state.callbacks.trigger_upload() {
612                log::error!("Triggering upload failed. Error: {}", e);
613            }
614        }
615        let state = global_state().lock().unwrap();
616        state.callbacks.initialize_finished();
617    })
618    .expect("Failed to spawn Glean's init thread");
619
620    // For test purposes, store the glean init thread's JoinHandle.
621    INIT_HANDLES.lock().unwrap().push(init_handle);
622
623    // Mark the initialization as called: this needs to happen outside of the
624    // dispatched block!
625    INITIALIZE_CALLED.store(true, Ordering::SeqCst);
626
627    // In test mode we wait for initialization to finish.
628    // This needs to run after we set `INITIALIZE_CALLED`, so it's similar to normal behavior.
629    if dispatcher::global::is_test_mode() {
630        join_init();
631    }
632}
633
634/// Return the heap usage of the `Glean` object and all descendant heap-allocated structures.
635///
636/// Value is in bytes.
637pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
638    use malloc_size_of::MallocSizeOf;
639    core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
640}
641
642/// TEST ONLY FUNCTION
643/// Waits on all the glean.init threads' join handles.
644pub fn join_init() {
645    let mut handles = INIT_HANDLES.lock().unwrap();
646    for handle in handles.drain(..) {
647        handle.join().unwrap();
648    }
649}
650
651/// Call the `shutdown` callback.
652///
653/// This calls the shutdown in a separate thread and waits up to 30s for it to finish.
654/// If not finished in that time frame it continues.
655///
656/// Under normal operation that is fine, as the main process will end
657/// and thus the thread will get killed.
658fn uploader_shutdown() {
659    let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
660    let (tx, rx) = unbounded();
661
662    let handle = thread::spawn("glean.shutdown", move || {
663        let state = global_state().lock().unwrap();
664        if let Err(e) = state.callbacks.shutdown() {
665            log::error!("Shutdown callback failed: {e:?}");
666        }
667
668        // Best-effort sending. The other side might have timed out already.
669        let _ = tx.send(()).ok();
670    })
671    .expect("Unable to spawn thread to wait on shutdown");
672
673    // TODO: 30 seconds? What's a good default here? Should this be configurable?
674    // Reasoning:
675    //   * If we shut down early we might still be processing pending pings.
676    //     In this case we wait at most 3 times for 1s = 3s before we upload.
677    //   * If we're rate-limited the uploader sleeps for up to 60s.
678    //     Thus waiting 30s will rarely allow another upload.
679    //   * We don't know how long uploads take until we get data from bug 1814592.
680    let result = rx.recv_timeout(Duration::from_secs(30));
681
682    let stop_time = zeitstempel::now_awake();
683    core::with_glean(|glean| {
684        glean
685            .additional_metrics
686            .shutdown_wait
687            .set_stop_and_accumulate(glean, timer_id, stop_time);
688    });
689
690    if result.is_err() {
691        log::warn!("Waiting for upload failed. We're shutting down.");
692    } else {
693        let _ = handle.join().ok();
694    }
695}
696
697/// Shuts down Glean in an orderly fashion.
698pub fn shutdown() {
699    // Shutdown might have been called
700    // 1) Before init was called
701    //    * (data loss, oh well. Not enough time to do squat)
702    // 2) After init was called, but before it completed
703    //    * (we're willing to wait a little bit for init to complete)
704    // 3) After init completed
705    //    * (we can shut down immediately)
706
707    // Case 1: "Before init was called"
708    if !was_initialize_called() {
709        log::warn!("Shutdown called before Glean is initialized");
710        if let Err(e) = dispatcher::kill() {
711            log::error!("Can't kill dispatcher thread: {:?}", e);
712        }
713        return;
714    }
715
716    // Case 2: "After init was called, but before it completed"
717    if core::global_glean().is_none() {
718        log::warn!("Shutdown called before Glean is initialized. Waiting.");
719        // We can't join on the `glean.init` thread because there's no (easy) way
720        // to do that with a timeout. Instead, we wait for the preinit queue to
721        // empty, which is the last meaningful thing we do on that thread.
722
723        // TODO: Make the timeout configurable?
724        // We don't need the return value, as we're less interested in whether
725        // this times out than we are in whether there's a Global Glean at the end.
726        let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
727    }
728    // We can't shut down Glean if there's no Glean to shut down.
729    if core::global_glean().is_none() {
730        log::warn!("Waiting for Glean initialization timed out. Exiting.");
731        if let Err(e) = dispatcher::kill() {
732            log::error!("Can't kill dispatcher thread: {:?}", e);
733        }
734        return;
735    }
736
737    // Case 3: "After init completed"
738    crate::launch_with_glean_mut(|glean| {
739        glean.cancel_metrics_ping_scheduler();
740        glean.set_dirty_flag(false);
741    });
742
743    // We need to wait for above task to finish,
744    // but we also don't wait around forever.
745    //
746    // TODO: Make the timeout configurable?
747    // The default hang watchdog on Firefox waits 60s,
748    // Glean's `uploader_shutdown` further below waits up to 30s.
749    let timer_id = core::with_glean(|glean| {
750        glean
751            .additional_metrics
752            .shutdown_dispatcher_wait
753            .start_sync()
754    });
755    let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
756
757    // Always record the dispatcher wait, regardless of the timeout.
758    let stop_time = zeitstempel::now_awake();
759    core::with_glean(|glean| {
760        glean
761            .additional_metrics
762            .shutdown_dispatcher_wait
763            .set_stop_and_accumulate(glean, timer_id, stop_time);
764    });
765    if blocked.is_err() {
766        log::error!(
767            "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
768        );
769        return;
770    }
771
772    if let Err(e) = dispatcher::shutdown() {
773        log::error!("Can't shutdown dispatcher thread: {:?}", e);
774    }
775
776    uploader_shutdown();
777
778    // Be sure to call this _after_ draining the dispatcher
779    core::with_glean(|glean| {
780        if let Err(e) = glean.persist_ping_lifetime_data() {
781            log::info!("Can't persist ping lifetime data: {:?}", e);
782        }
783    });
784}
785
786/// Asks the database to persist ping-lifetime data to disk.
787///
788/// Probably expensive to call.
789/// Only has effect when Glean is configured with `delay_ping_lifetime_io: true`.
790/// If Glean hasn't been initialized this will dispatch and return Ok(()),
791/// otherwise it will block until the persist is done and return its Result.
792pub fn glean_persist_ping_lifetime_data() {
793    // This is async, we can't get the Error back to the caller.
794    crate::launch_with_glean(|glean| {
795        let _ = glean.persist_ping_lifetime_data();
796    });
797}
798
799fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
800    core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
801    core_metrics::internal_metrics::app_display_version
802        .set_sync(glean, &client_info.app_display_version[..]);
803    core_metrics::internal_metrics::app_build_date
804        .set_sync(glean, Some(client_info.app_build_date.clone()));
805    if let Some(app_channel) = client_info.channel.as_ref() {
806        core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
807    }
808
809    core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
810    core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
811
812    if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
813        core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
814    }
815    if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
816        core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
817    }
818    if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
819        core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
820    }
821    if let Some(device_model) = client_info.device_model.as_ref() {
822        core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
823    }
824    if let Some(locale) = client_info.locale.as_ref() {
825        core_metrics::internal_metrics::locale.set_sync(glean, locale);
826    }
827}
828
829/// Checks if [`glean_initialize`] was ever called.
830///
831/// # Returns
832///
833/// `true` if it was, `false` otherwise.
834fn was_initialize_called() -> bool {
835    INITIALIZE_CALLED.load(Ordering::SeqCst)
836}
837
838/// Initialize the logging system based on the target platform. This ensures
839/// that logging is shown when executing the Glean SDK unit tests.
840#[no_mangle]
841pub extern "C" fn glean_enable_logging() {
842    #[cfg(target_os = "android")]
843    {
844        let _ = std::panic::catch_unwind(|| {
845            let filter = android_logger::FilterBuilder::new()
846                .filter_module("glean_ffi", log::LevelFilter::Debug)
847                .filter_module("glean_core", log::LevelFilter::Debug)
848                .filter_module("glean", log::LevelFilter::Debug)
849                .filter_module("glean_core::ffi", log::LevelFilter::Info)
850                .build();
851            android_logger::init_once(
852                android_logger::Config::default()
853                    .with_max_level(log::LevelFilter::Debug)
854                    .with_filter(filter)
855                    .with_tag("libglean_ffi"),
856            );
857            log::trace!("Android logging should be hooked up!")
858        });
859    }
860
861    // On iOS enable logging with a level filter.
862    #[cfg(target_os = "ios")]
863    {
864        // Debug logging in debug mode.
865        // (Note: `debug_assertions` is the next best thing to determine if this is a debug build)
866        #[cfg(debug_assertions)]
867        let level = log::LevelFilter::Debug;
868        #[cfg(not(debug_assertions))]
869        let level = log::LevelFilter::Info;
870
871        let logger = oslog::OsLogger::new("org.mozilla.glean")
872            .level_filter(level)
873            // Filter UniFFI log messages
874            .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
875
876        match logger.init() {
877            Ok(_) => log::trace!("os_log should be hooked up!"),
878            // Please note that this is only expected to fail during unit tests,
879            // where the logger might have already been initialized by a previous
880            // test. So it's fine to print with the "logger".
881            Err(_) => log::warn!("os_log was already initialized"),
882        };
883    }
884
885    // When specifically requested make sure logging does something on non-Android platforms as well.
886    // Use the RUST_LOG environment variable to set the desired log level,
887    // e.g. setting RUST_LOG=debug sets the log level to debug.
888    #[cfg(all(
889        not(target_os = "android"),
890        not(target_os = "ios"),
891        feature = "enable_env_logger"
892    ))]
893    {
894        match env_logger::try_init() {
895            Ok(_) => log::trace!("stdout logging should be hooked up!"),
896            // Please note that this is only expected to fail during unit tests,
897            // where the logger might have already been initialized by a previous
898            // test. So it's fine to print with the "logger".
899            Err(_) => log::warn!("stdout logging was already initialized"),
900        };
901    }
902}
903
904/// **DEPRECATED** Sets whether upload is enabled or not.
905///
906/// **DEPRECATION NOTICE**:
907/// This API is deprecated. Use `set_collection_enabled` instead.
908pub fn glean_set_upload_enabled(enabled: bool) {
909    if !was_initialize_called() {
910        return;
911    }
912
913    crate::launch_with_glean_mut(move |glean| {
914        let state = global_state().lock().unwrap();
915        let original_enabled = glean.is_upload_enabled();
916
917        if !enabled {
918            // Stop the MPS if its handled within Rust.
919            glean.cancel_metrics_ping_scheduler();
920            // Stop wrapper-controlled uploader.
921            if let Err(e) = state.callbacks.cancel_uploads() {
922                log::error!("Canceling upload failed. Error: {}", e);
923            }
924        }
925
926        glean.set_upload_enabled(enabled);
927
928        if !original_enabled && enabled {
929            initialize_core_metrics(glean, &state.client_info);
930        }
931
932        if original_enabled && !enabled {
933            if let Err(e) = state.callbacks.trigger_upload() {
934                log::error!("Triggering upload failed. Error: {}", e);
935            }
936        }
937    })
938}
939
940/// Sets whether collection is enabled or not.
941///
942/// This replaces `set_upload_enabled`.
943pub fn glean_set_collection_enabled(enabled: bool) {
944    glean_set_upload_enabled(enabled)
945}
946
947/// Enable or disable a ping.
948///
949/// Disabling a ping causes all data for that ping to be removed from storage
950/// and all pending pings of that type to be deleted.
951pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
952    let ping = ping.clone();
953    if was_initialize_called() && core::global_glean().is_some() {
954        crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
955    } else {
956        let m = &PRE_INIT_PING_ENABLED;
957        let mut lock = m.lock().unwrap();
958        lock.push((ping, enabled));
959    }
960}
961
962/// Register a new [`PingType`].
963pub(crate) fn register_ping_type(ping: &PingType) {
964    // If this happens after Glean.initialize is called (and returns),
965    // we dispatch ping registration on the thread pool.
966    // Registering a ping should not block the application.
967    // Submission itself is also dispatched, so it will always come after the registration.
968    if was_initialize_called() && core::global_glean().is_some() {
969        let ping = ping.clone();
970        crate::launch_with_glean_mut(move |glean| {
971            glean.register_ping_type(&ping);
972        })
973    } else {
974        // We need to keep track of pings, so they get re-registered after a reset or
975        // if ping registration is attempted before Glean initializes.
976        // This state is kept across Glean resets, which should only ever happen in test mode.
977        // It's a set and keeping them around forever should not have much of an impact.
978        let m = &PRE_INIT_PING_REGISTRATION;
979        let mut lock = m.lock().unwrap();
980        lock.push(ping.clone());
981    }
982}
983
984/// Gets a list of currently registered ping names.
985///
986/// # Returns
987///
988/// The list of ping names that are currently registered.
989pub fn glean_get_registered_ping_names() -> Vec<String> {
990    block_on_dispatcher();
991    core::with_glean(|glean| {
992        glean
993            .get_registered_ping_names()
994            .iter()
995            .map(|ping| ping.to_string())
996            .collect()
997    })
998}
999
1000/// Indicate that an experiment is running.  Glean will then add an
1001/// experiment annotation to the environment which is sent with pings. This
1002/// infomration is not persisted between runs.
1003///
1004/// See [`core::Glean::set_experiment_active`].
1005pub fn glean_set_experiment_active(
1006    experiment_id: String,
1007    branch: String,
1008    extra: HashMap<String, String>,
1009) {
1010    launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1011}
1012
1013/// Indicate that an experiment is no longer running.
1014///
1015/// See [`core::Glean::set_experiment_inactive`].
1016pub fn glean_set_experiment_inactive(experiment_id: String) {
1017    launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1018}
1019
1020/// TEST ONLY FUNCTION.
1021/// Returns the [`RecordedExperiment`] for the given `experiment_id`
1022/// or `None` if the id isn't found.
1023pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1024    block_on_dispatcher();
1025    core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1026}
1027
1028/// Set an experimentation identifier dynamically.
1029///
1030/// Note: it's probably a good idea to unenroll from any experiments when identifiers change.
1031pub fn glean_set_experimentation_id(experimentation_id: String) {
1032    launch_with_glean(move |glean| {
1033        glean
1034            .additional_metrics
1035            .experimentation_id
1036            .set(experimentation_id);
1037    });
1038}
1039
1040/// TEST ONLY FUNCTION.
1041/// Gets stored experimentation id annotation.
1042pub fn glean_test_get_experimentation_id() -> Option<String> {
1043    block_on_dispatcher();
1044    core::with_glean(|glean| glean.test_get_experimentation_id())
1045}
1046
1047/// Sets a remote configuration to override metrics' default enabled/disabled
1048/// state
1049///
1050/// See [`core::Glean::apply_server_knobs_config`].
1051pub fn glean_apply_server_knobs_config(json: String) {
1052    // An empty config means it is not set,
1053    // so we avoid logging an error about it.
1054    if json.is_empty() {
1055        return;
1056    }
1057
1058    match RemoteSettingsConfig::try_from(json) {
1059        Ok(cfg) => launch_with_glean(|glean| {
1060            glean.apply_server_knobs_config(cfg);
1061        }),
1062        Err(e) => {
1063            log::error!("Error setting metrics feature config: {:?}", e);
1064        }
1065    }
1066}
1067
1068/// Sets a debug view tag.
1069///
1070/// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the
1071/// value of the tag and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
1072///
1073/// # Arguments
1074///
1075/// * `tag` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
1076///
1077/// # Returns
1078///
1079/// This will return `false` in case `tag` is not a valid tag and `true` otherwise.
1080/// If called before Glean is initialized it will always return `true`.
1081pub fn glean_set_debug_view_tag(tag: String) -> bool {
1082    if was_initialize_called() && core::global_glean().is_some() {
1083        crate::launch_with_glean_mut(move |glean| {
1084            glean.set_debug_view_tag(&tag);
1085        });
1086        true
1087    } else {
1088        // Glean has not been initialized yet. Cache the provided tag value.
1089        let m = &PRE_INIT_DEBUG_VIEW_TAG;
1090        let mut lock = m.lock().unwrap();
1091        *lock = tag;
1092        // When setting the debug view tag before initialization,
1093        // we don't validate the tag, thus this function always returns true.
1094        true
1095    }
1096}
1097
1098/// Gets the currently set debug view tag.
1099///
1100/// # Returns
1101///
1102/// Return the value for the debug view tag or [`None`] if it hasn't been set.
1103pub fn glean_get_debug_view_tag() -> Option<String> {
1104    block_on_dispatcher();
1105    core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1106}
1107
1108/// Sets source tags.
1109///
1110/// Overrides any existing source tags.
1111/// Source tags will show in the destination datasets, after ingestion.
1112///
1113/// **Note** If one or more tags are invalid, all tags are ignored.
1114///
1115/// # Arguments
1116///
1117/// * `tags` - A vector of at most 5 valid HTTP header values. Individual
1118///   tags must match the regex: "[a-zA-Z0-9-]{1,20}".
1119pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1120    if was_initialize_called() && core::global_glean().is_some() {
1121        crate::launch_with_glean_mut(|glean| {
1122            glean.set_source_tags(tags);
1123        });
1124        true
1125    } else {
1126        // Glean has not been initialized yet. Cache the provided source tags.
1127        let m = &PRE_INIT_SOURCE_TAGS;
1128        let mut lock = m.lock().unwrap();
1129        *lock = tags;
1130        // When setting the source tags before initialization,
1131        // we don't validate the tags, thus this function always returns true.
1132        true
1133    }
1134}
1135
1136/// Sets the log pings debug option.
1137///
1138/// When the log pings debug option is `true`,
1139/// we log the payload of all succesfully assembled pings.
1140///
1141/// # Arguments
1142///
1143/// * `value` - The value of the log pings option
1144pub fn glean_set_log_pings(value: bool) {
1145    if was_initialize_called() && core::global_glean().is_some() {
1146        crate::launch_with_glean_mut(move |glean| {
1147            glean.set_log_pings(value);
1148        });
1149    } else {
1150        PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1151    }
1152}
1153
1154/// Gets the current log pings value.
1155///
1156/// # Returns
1157///
1158/// Return the value for the log pings debug option.
1159pub fn glean_get_log_pings() -> bool {
1160    block_on_dispatcher();
1161    core::with_glean(|glean| glean.log_pings())
1162}
1163
1164/// Performs the collection/cleanup operations required by becoming active.
1165///
1166/// This functions generates a baseline ping with reason `active`
1167/// and then sets the dirty bit.
1168/// This should be called whenever the consuming product becomes active (e.g.
1169/// getting to foreground).
1170pub fn glean_handle_client_active() {
1171    dispatcher::launch(|| {
1172        core::with_glean_mut(|glean| {
1173            glean.handle_client_active();
1174        });
1175
1176        // The above call may generate pings, so we need to trigger
1177        // the uploader. It's fine to trigger it if no ping was generated:
1178        // it will bail out.
1179        let state = global_state().lock().unwrap();
1180        if let Err(e) = state.callbacks.trigger_upload() {
1181            log::error!("Triggering upload failed. Error: {}", e);
1182        }
1183    });
1184
1185    // The previous block of code may send a ping containing the `duration` metric,
1186    // in `glean.handle_client_active`. We intentionally start recording a new
1187    // `duration` after that happens, so that the measurement gets reported when
1188    // calling `handle_client_inactive`.
1189    core_metrics::internal_metrics::baseline_duration.start();
1190}
1191
1192/// Performs the collection/cleanup operations required by becoming inactive.
1193///
1194/// This functions generates a baseline and an events ping with reason
1195/// `inactive` and then clears the dirty bit.
1196/// This should be called whenever the consuming product becomes inactive (e.g.
1197/// getting to background).
1198pub fn glean_handle_client_inactive() {
1199    // This needs to be called before the `handle_client_inactive` api: it stops
1200    // measuring the duration of the previous activity time, before any ping is sent
1201    // by the next call.
1202    core_metrics::internal_metrics::baseline_duration.stop();
1203
1204    dispatcher::launch(|| {
1205        core::with_glean_mut(|glean| {
1206            glean.handle_client_inactive();
1207        });
1208
1209        // The above call may generate pings, so we need to trigger
1210        // the uploader. It's fine to trigger it if no ping was generated:
1211        // it will bail out.
1212        let state = global_state().lock().unwrap();
1213        if let Err(e) = state.callbacks.trigger_upload() {
1214            log::error!("Triggering upload failed. Error: {}", e);
1215        }
1216    })
1217}
1218
1219/// Starts a session manually.
1220///
1221/// Only has effect in `SessionMode::Manual`. Calling this in `Auto` or
1222/// `Lifecycle` mode is a no-op to prevent corrupting automatic session state.
1223pub fn glean_session_start() {
1224    launch_with_glean_mut(|glean| {
1225        if glean.session_manager.mode == session::SessionMode::Manual {
1226            glean.session_start();
1227        }
1228    });
1229}
1230
1231/// Ends a session manually.
1232///
1233/// Only has effect in `SessionMode::Manual`. Calling this in `Auto` or
1234/// `Lifecycle` mode is a no-op to prevent corrupting automatic session state.
1235///
1236/// `reason` is an optional application-provided string attached to the
1237/// `glean.session_end` boundary event for downstream analysis.
1238pub fn glean_session_end(reason: Option<String>) {
1239    launch_with_glean_mut(move |glean| {
1240        if glean.session_manager.mode == session::SessionMode::Manual {
1241            glean.session_end(reason.as_deref());
1242        }
1243    });
1244}
1245
1246/// Collect and submit a ping for eventual upload by name.
1247pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1248    dispatcher::launch(|| {
1249        let sent =
1250            core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1251
1252        if sent {
1253            let state = global_state().lock().unwrap();
1254            if let Err(e) = state.callbacks.trigger_upload() {
1255                log::error!("Triggering upload failed. Error: {}", e);
1256            }
1257        }
1258    })
1259}
1260
1261/// Collect and submit a ping (by its name) for eventual upload, synchronously.
1262///
1263/// Note: This does not trigger the uploader. The caller is responsible to do this.
1264pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1265    if !was_initialize_called() {
1266        return false;
1267    }
1268
1269    core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1270        .unwrap_or(false)
1271}
1272
1273/// EXPERIMENTAL: Register a listener object to recieve notifications of event recordings.
1274///
1275/// # Arguments
1276///
1277/// * `tag` - A string identifier used to later unregister the listener
1278/// * `listener` - Implements the `GleanEventListener` trait
1279pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1280    register_event_listener(tag, listener);
1281}
1282
1283/// Unregister an event listener from recieving notifications.
1284///
1285/// Does not panic if the listener doesn't exist.
1286///
1287/// # Arguments
1288///
1289/// * `tag` - The tag used when registering the listener to be unregistered
1290pub fn glean_unregister_event_listener(tag: String) {
1291    unregister_event_listener(tag);
1292}
1293
1294/// **TEST-ONLY Method**
1295///
1296/// Set test mode
1297pub fn glean_set_test_mode(enabled: bool) {
1298    dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1299}
1300
1301/// **TEST-ONLY Method**
1302///
1303/// Destroy the underlying database.
1304pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1305    if was_initialize_called() {
1306        // Just because initialize was called doesn't mean it's done.
1307        join_init();
1308
1309        dispatcher::reset_dispatcher();
1310
1311        // Only useful if Glean initialization finished successfully
1312        // and set up the storage.
1313        let has_storage = core::with_opt_glean(|glean| {
1314            // We need to flush the ping lifetime data before a full shutdown.
1315            glean
1316                .storage_opt()
1317                .map(|storage| storage.persist_ping_lifetime_data())
1318                .is_some()
1319        })
1320        .unwrap_or(false);
1321        if has_storage {
1322            uploader_shutdown();
1323        }
1324
1325        if core::global_glean().is_some() {
1326            core::with_glean_mut(|glean| {
1327                if clear_stores {
1328                    glean.test_clear_all_stores()
1329                }
1330                glean.destroy_db()
1331            });
1332        }
1333
1334        // Allow us to go through initialization again.
1335        INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1336    } else if clear_stores {
1337        if let Some(data_path) = data_path {
1338            let _ = std::fs::remove_dir_all(data_path).ok();
1339        } else {
1340            log::warn!("Asked to clear stores before initialization, but no data path given.");
1341        }
1342    }
1343}
1344
1345/// Get the next upload task
1346pub fn glean_get_upload_task() -> PingUploadTask {
1347    core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1348}
1349
1350/// Processes the response from an attempt to upload a ping.
1351pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1352    core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1353}
1354
1355/// **TEST-ONLY Method**
1356///
1357/// Set the dirty flag
1358pub fn glean_set_dirty_flag(new_value: bool) {
1359    core::with_glean(|glean| glean.set_dirty_flag(new_value))
1360}
1361
1362/// Updates attribution fields with new values.
1363/// AttributionMetrics fields with `None` values will not overwrite older values.
1364pub fn glean_update_attribution(attribution: AttributionMetrics) {
1365    if was_initialize_called() && core::global_glean().is_some() {
1366        core::with_glean(|glean| glean.update_attribution(attribution));
1367    } else {
1368        PRE_INIT_ATTRIBUTION
1369            .lock()
1370            .unwrap()
1371            .get_or_insert(Default::default())
1372            .update(attribution);
1373    }
1374}
1375
1376/// **TEST-ONLY Method**
1377///
1378/// Returns the current attribution metrics.
1379/// Panics if called before init.
1380pub fn glean_test_get_attribution() -> AttributionMetrics {
1381    join_init();
1382    core::with_glean(|glean| glean.test_get_attribution())
1383}
1384
1385/// Updates distribution fields with new values.
1386/// DistributionMetrics fields with `None` values will not overwrite older values.
1387pub fn glean_update_distribution(distribution: DistributionMetrics) {
1388    if was_initialize_called() && core::global_glean().is_some() {
1389        core::with_glean(|glean| glean.update_distribution(distribution));
1390    } else {
1391        PRE_INIT_DISTRIBUTION
1392            .lock()
1393            .unwrap()
1394            .get_or_insert(Default::default())
1395            .update(distribution);
1396    }
1397}
1398
1399/// **TEST-ONLY Method**
1400///
1401/// Returns the current distribution metrics.
1402/// Panics if called before init.
1403pub fn glean_test_get_distribution() -> DistributionMetrics {
1404    join_init();
1405    core::with_glean(|glean| glean.test_get_distribution())
1406}
1407
1408#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1409static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1410
1411/// Initialize the logging system to send JSON messages to a file descriptor
1412/// (Unix) or file handle (Windows).
1413///
1414/// Not available on Android and iOS.
1415///
1416/// `fd` is a writable file descriptor (on Unix) or file handle (on Windows).
1417///
1418/// # Safety
1419///
1420/// `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1421/// This function is marked safe,
1422/// because we can't call unsafe functions from generated UniFFI code.
1423#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1424pub fn glean_enable_logging_to_fd(fd: u64) {
1425    // SAFETY:
1426    // This functions is unsafe.
1427    // Due to UniFFI restrictions we cannot mark it as such.
1428    //
1429    // `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1430    unsafe {
1431        // Set up logging to a file descriptor/handle. For this usage, the
1432        // language binding should setup a pipe and pass in the descriptor to
1433        // the writing side of the pipe as the `fd` parameter. Log messages are
1434        // written as JSON to the file descriptor.
1435        let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1436        // Set the level so everything goes through to the language
1437        // binding side where it will be filtered by the language
1438        // binding's logging system.
1439        if log::set_logger(logger).is_ok() {
1440            log::set_max_level(log::LevelFilter::Debug);
1441        }
1442    }
1443}
1444
1445/// Collects information about the data directories used by FOG.
1446fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1447    // List of child directories to check
1448    let subdirs = ["db", "events", "pending_pings"];
1449    let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1450        DataDirectoryInfoObject::with_capacity(subdirs.len());
1451
1452    for subdir in subdirs.iter() {
1453        let dir_path = path.join(subdir);
1454
1455        // Initialize a DataDirectoryInfoObjectItem for each directory
1456        let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1457            dir_name: Some(subdir.to_string()),
1458            dir_exists: None,
1459            dir_created: None,
1460            dir_modified: None,
1461            file_count: None,
1462            files: Vec::new(),
1463            error_message: None,
1464        };
1465
1466        // Check if the directory exists
1467        if dir_path.is_dir() {
1468            directory_info.dir_exists = Some(true);
1469
1470            // Get directory metadata
1471            match fs::metadata(&dir_path) {
1472                Ok(metadata) => {
1473                    if let Ok(created) = metadata.created() {
1474                        directory_info.dir_created = Some(
1475                            created
1476                                .duration_since(UNIX_EPOCH)
1477                                .unwrap_or(Duration::ZERO)
1478                                .as_secs() as i64,
1479                        );
1480                    }
1481                    if let Ok(modified) = metadata.modified() {
1482                        directory_info.dir_modified = Some(
1483                            modified
1484                                .duration_since(UNIX_EPOCH)
1485                                .unwrap_or(Duration::ZERO)
1486                                .as_secs() as i64,
1487                        );
1488                    }
1489                }
1490                Err(error) => {
1491                    let msg = format!("Unable to get metadata: {}", error.kind());
1492                    directory_info.error_message = Some(msg.clone());
1493                    log::warn!("{}", msg);
1494                    continue;
1495                }
1496            }
1497
1498            // Read the directory's contents
1499            let mut file_count = 0;
1500            let entries = match fs::read_dir(&dir_path) {
1501                Ok(entries) => entries,
1502                Err(error) => {
1503                    let msg = format!("Unable to read subdir: {}", error.kind());
1504                    directory_info.error_message = Some(msg.clone());
1505                    log::warn!("{}", msg);
1506                    continue;
1507                }
1508            };
1509            for entry in entries {
1510                directory_info.files.push(
1511                    crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1512                        file_name: None,
1513                        file_created: None,
1514                        file_modified: None,
1515                        file_size: None,
1516                        error_message: None,
1517                    },
1518                );
1519                // Safely get and unwrap the file_info we just pushed so we can populate it
1520                let file_info = directory_info.files.last_mut().unwrap();
1521                let entry = match entry {
1522                    Ok(entry) => entry,
1523                    Err(error) => {
1524                        let msg = format!("Unable to read file: {}", error.kind());
1525                        file_info.error_message = Some(msg.clone());
1526                        log::warn!("{}", msg);
1527                        continue;
1528                    }
1529                };
1530                let file_name = match entry.file_name().into_string() {
1531                    Ok(file_name) => file_name,
1532                    _ => {
1533                        let msg = "Unable to convert file name to string".to_string();
1534                        file_info.error_message = Some(msg.clone());
1535                        log::warn!("{}", msg);
1536                        continue;
1537                    }
1538                };
1539                let metadata = match entry.metadata() {
1540                    Ok(metadata) => metadata,
1541                    Err(error) => {
1542                        let msg = format!("Unable to read file metadata: {}", error.kind());
1543                        file_info.file_name = Some(file_name);
1544                        file_info.error_message = Some(msg.clone());
1545                        log::warn!("{}", msg);
1546                        continue;
1547                    }
1548                };
1549
1550                // Check if the entry is a file
1551                if metadata.is_file() {
1552                    file_count += 1;
1553
1554                    // Collect file details
1555                    file_info.file_name = Some(file_name);
1556                    file_info.file_created = Some(
1557                        metadata
1558                            .created()
1559                            .unwrap_or(UNIX_EPOCH)
1560                            .duration_since(UNIX_EPOCH)
1561                            .unwrap_or(Duration::ZERO)
1562                            .as_secs() as i64,
1563                    );
1564                    file_info.file_modified = Some(
1565                        metadata
1566                            .modified()
1567                            .unwrap_or(UNIX_EPOCH)
1568                            .duration_since(UNIX_EPOCH)
1569                            .unwrap_or(Duration::ZERO)
1570                            .as_secs() as i64,
1571                    );
1572                    file_info.file_size = Some(metadata.len() as i64);
1573                } else {
1574                    let msg = format!("Skipping non-file entry: {}", file_name.clone());
1575                    file_info.file_name = Some(file_name);
1576                    file_info.error_message = Some(msg.clone());
1577                    log::warn!("{}", msg);
1578                }
1579            }
1580
1581            directory_info.file_count = Some(file_count as i64);
1582        } else {
1583            directory_info.dir_exists = Some(false);
1584        }
1585
1586        // Add the directory info to the final collection
1587        directories_info.push(directory_info);
1588    }
1589
1590    if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1591        Some(directories_info_json)
1592    } else {
1593        log::error!("Failed to serialize data directory info");
1594        None
1595    }
1596}
1597
1598fn record_dir_info_and_submit_health_ping(dir_info: Option<serde_json::Value>, reason: &str) {
1599    core::with_glean(|glean| {
1600        glean
1601            .health_metrics
1602            .data_directory_info
1603            .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1604        glean.internal_pings.health.submit_sync(glean, Some(reason));
1605    });
1606}
1607
1608/// Unused function. Not used on Android or iOS.
1609#[cfg(any(target_os = "android", target_os = "ios"))]
1610pub fn glean_enable_logging_to_fd(_fd: u64) {
1611    // intentionally left empty
1612}
1613
1614#[allow(missing_docs)]
1615// uniffi-generated code should not be checked.
1616#[allow(clippy::all)]
1617mod ffi {
1618    use super::*;
1619    uniffi::include_scaffolding!("glean");
1620
1621    type CowString = Cow<'static, str>;
1622
1623    uniffi::custom_type!(CowString, String, {
1624        remote,
1625        lower: |s| s.into_owned(),
1626        try_lift: |s| Ok(Cow::from(s))
1627    });
1628
1629    type JsonValue = serde_json::Value;
1630
1631    uniffi::custom_type!(JsonValue, String, {
1632        remote,
1633        lower: |s| serde_json::to_string(&s).unwrap(),
1634        try_lift: |s| Ok(serde_json::from_str(&s)?)
1635    });
1636}
1637pub use ffi::*;
1638
1639// Split unit tests to a separate file, to reduce the file of this one.
1640#[cfg(test)]
1641#[path = "lib_unit_tests.rs"]
1642mod tests;