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