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