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