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