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        //
613        // bug 2072668: Anything recorded at this point runs as the final preinit task on the dispatcher, rather than
614        // directly on this thread. That way it runs after any preinit tasks, and before anything a concurrent
615        // `shutdown()` waits on, so the database is still open. Recording it after the flush could race with
616        // `shutdown()`.
617        let submit_health_ping = !is_test_mode() && internal_pings_enabled;
618        let flushed = dispatcher::flush_init(move |overflow_count| {
619            let sent = core::with_glean(|glean| {
620                // This needs to be recorded before the health ping is submitted, as it is sent in that ping.
621                if overflow_count > 0 {
622                    glean_metrics::error::preinit_tasks_overflow
623                        .add_sync(glean, overflow_count as i32);
624                }
625
626                // Now that Glean is initialized, we can capture the directory info from the pre_init phase and send
627                // it in a health ping with reason "pre_init".
628                submit_health_ping
629                    && record_dir_info_and_submit_health_ping(glean, dir_info, "pre_init")
630            });
631
632            // Need to separate access to the Glean object from access to global state.
633            // `trigger_upload` itself might lock the Glean object and we need to avoid that deadlock.
634            if sent {
635                let state = global_state().lock().unwrap();
636                if let Err(e) = state.callbacks.trigger_upload() {
637                    log::error!("Triggering upload failed. Error: {}", e);
638                }
639            }
640        });
641        if let Err(err) = flushed {
642            log::error!("Unable to flush the preinit queue: {}", err);
643        }
644
645        let state = global_state().lock().unwrap();
646        state.callbacks.initialize_finished();
647    })
648    .expect("Failed to spawn Glean's init thread");
649
650    // For test purposes, store the glean init thread's JoinHandle.
651    INIT_HANDLES.lock().unwrap().push(init_handle);
652
653    // Mark the initialization as called: this needs to happen outside of the
654    // dispatched block!
655    INITIALIZE_CALLED.store(true, Ordering::SeqCst);
656
657    // In test mode we wait for initialization to finish.
658    // This needs to run after we set `INITIALIZE_CALLED`, so it's similar to normal behavior.
659    if dispatcher::global::is_test_mode() {
660        join_init();
661    }
662}
663
664/// Return the heap usage of the `Glean` object and all descendant heap-allocated structures.
665///
666/// Value is in bytes.
667pub fn alloc_size(ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
668    use malloc_size_of::MallocSizeOf;
669    core::with_opt_glean(|glean| glean.size_of(ops)).unwrap_or(0)
670}
671
672/// TEST ONLY FUNCTION
673/// Waits on all the glean.init threads' join handles.
674pub fn join_init() {
675    let mut handles = INIT_HANDLES.lock().unwrap();
676    for handle in handles.drain(..) {
677        handle.join().unwrap();
678    }
679}
680
681/// Call the `shutdown` callback.
682///
683/// This calls the shutdown in a separate thread and waits up to 30s for it to finish.
684/// If not finished in that time frame it continues.
685///
686/// Under normal operation that is fine, as the main process will end
687/// and thus the thread will get killed.
688fn uploader_shutdown() {
689    let timer_id = core::with_glean(|glean| glean.additional_metrics.shutdown_wait.start_sync());
690    let (tx, rx) = unbounded();
691
692    let handle = thread::spawn("glean.shutdown", move || {
693        let state = global_state().lock().unwrap();
694        if let Err(e) = state.callbacks.shutdown() {
695            log::error!("Shutdown callback failed: {e:?}");
696        }
697
698        // Best-effort sending. The other side might have timed out already.
699        let _ = tx.send(()).ok();
700    })
701    .expect("Unable to spawn thread to wait on shutdown");
702
703    // TODO: 30 seconds? What's a good default here? Should this be configurable?
704    // Reasoning:
705    //   * If we shut down early we might still be processing pending pings.
706    //     In this case we wait at most 3 times for 1s = 3s before we upload.
707    //   * If we're rate-limited the uploader sleeps for up to 60s.
708    //     Thus waiting 30s will rarely allow another upload.
709    //   * We don't know how long uploads take until we get data from bug 1814592.
710    let result = rx.recv_timeout(Duration::from_secs(30));
711
712    let stop_time = zeitstempel::now_awake();
713    core::with_glean(|glean| {
714        glean
715            .additional_metrics
716            .shutdown_wait
717            .set_stop_and_accumulate(glean, timer_id, stop_time);
718    });
719
720    if result.is_err() {
721        log::warn!("Waiting for upload failed. We're shutting down.");
722    } else {
723        let _ = handle.join().ok();
724    }
725}
726
727/// Shuts down Glean in an orderly fashion.
728pub fn shutdown() {
729    // Shutdown might have been called
730    // 1) Before init was called
731    //    * (data loss, oh well. Not enough time to do squat)
732    // 2) After init was called, but before it completed
733    //    * (we're willing to wait a little bit for init to complete)
734    // 3) After init completed
735    //    * (we can shut down immediately)
736
737    // Case 1: "Before init was called"
738    if !was_initialize_called() {
739        log::warn!("Shutdown called before Glean is initialized");
740        if let Err(e) = dispatcher::kill() {
741            log::error!("Can't kill dispatcher thread: {:?}", e);
742        }
743        return;
744    }
745
746    // Case 2: "After init was called, but before it completed"
747    if core::global_glean().is_none() {
748        log::warn!("Shutdown called before Glean is initialized. Waiting.");
749        // We can't join on the `glean.init` thread because there's no (easy) way
750        // to do that with a timeout. Instead, we wait for the preinit queue to
751        // empty, which is the last meaningful thing we do on that thread.
752
753        // TODO: Make the timeout configurable?
754        // We don't need the return value, as we're less interested in whether
755        // this times out than we are in whether there's a Global Glean at the end.
756        let _ = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
757    }
758    // We can't shut down Glean if there's no Glean to shut down.
759    if core::global_glean().is_none() {
760        log::warn!("Waiting for Glean initialization timed out. Exiting.");
761        if let Err(e) = dispatcher::kill() {
762            log::error!("Can't kill dispatcher thread: {:?}", e);
763        }
764        return;
765    }
766
767    // Case 3: "After init completed"
768    crate::launch_with_glean_mut(|glean| {
769        glean.cancel_metrics_ping_scheduler();
770        glean.set_dirty_flag(false);
771    });
772
773    // We need to wait for above task to finish,
774    // but we also don't wait around forever.
775    //
776    // TODO: Make the timeout configurable?
777    // The default hang watchdog on Firefox waits 60s,
778    // Glean's `uploader_shutdown` further below waits up to 30s.
779    let timer_id = core::with_glean(|glean| {
780        glean
781            .additional_metrics
782            .shutdown_dispatcher_wait
783            .start_sync()
784    });
785    let blocked = dispatcher::block_on_queue_timeout(Duration::from_secs(10));
786
787    // Always record the dispatcher wait, regardless of the timeout.
788    let stop_time = zeitstempel::now_awake();
789    core::with_glean(|glean| {
790        glean
791            .additional_metrics
792            .shutdown_dispatcher_wait
793            .set_stop_and_accumulate(glean, timer_id, stop_time);
794    });
795    if blocked.is_err() {
796        log::error!(
797            "Timeout while blocking on the dispatcher. No further shutdown cleanup will happen."
798        );
799        return;
800    }
801
802    if let Err(e) = dispatcher::shutdown() {
803        log::error!("Can't shutdown dispatcher thread: {:?}", e);
804    }
805
806    uploader_shutdown();
807
808    // Be sure to call this _after_ draining the dispatcher
809    core::with_glean_mut(|glean| {
810        if let Err(e) = glean.persist_ping_lifetime_data() {
811            log::info!("Can't persist ping lifetime data: {:?}", e);
812        }
813
814        #[cfg(feature = "sqlite")]
815        if let Some(database) = &glean.data_store {
816            if let Err(e) = database.cleanup_submitted_pings(None) {
817                log::info!("Could not clean up submitted_pings table: {:?}", e);
818            }
819            if let Err(e) = database.run_maintenance(false) {
820                log::info!("Can't run database maintenance on shutdown: {:?}", e);
821            }
822        }
823
824        glean.close_db();
825    });
826}
827
828/// Asks the database to persist ping-lifetime data to disk.
829///
830/// Probably expensive to call.
831/// Only has effect when Glean is configured with `delay_ping_lifetime_io: true`.
832/// If Glean hasn't been initialized this will dispatch and return Ok(()),
833/// otherwise it will block until the persist is done and return its Result.
834pub fn glean_persist_ping_lifetime_data() {
835    // This is async, we can't get the Error back to the caller.
836    crate::launch_with_glean(|glean| {
837        let _ = glean.persist_ping_lifetime_data();
838    });
839}
840
841fn initialize_core_metrics(glean: &Glean, client_info: &ClientInfoMetrics) {
842    core_metrics::internal_metrics::app_build.set_sync(glean, &client_info.app_build[..]);
843    core_metrics::internal_metrics::app_display_version
844        .set_sync(glean, &client_info.app_display_version[..]);
845    core_metrics::internal_metrics::app_build_date
846        .set_sync(glean, Some(client_info.app_build_date.clone()));
847    if let Some(app_channel) = client_info.channel.as_ref() {
848        core_metrics::internal_metrics::app_channel.set_sync(glean, app_channel);
849    }
850
851    core_metrics::internal_metrics::os_version.set_sync(glean, &client_info.os_version);
852    core_metrics::internal_metrics::architecture.set_sync(glean, &client_info.architecture);
853
854    if let Some(android_sdk_version) = client_info.android_sdk_version.as_ref() {
855        core_metrics::internal_metrics::android_sdk_version.set_sync(glean, android_sdk_version);
856    }
857    if let Some(windows_build_number) = client_info.windows_build_number.as_ref() {
858        core_metrics::internal_metrics::windows_build_number.set_sync(glean, *windows_build_number);
859    }
860    if let Some(device_manufacturer) = client_info.device_manufacturer.as_ref() {
861        core_metrics::internal_metrics::device_manufacturer.set_sync(glean, device_manufacturer);
862    }
863    if let Some(device_model) = client_info.device_model.as_ref() {
864        core_metrics::internal_metrics::device_model.set_sync(glean, device_model);
865    }
866    if let Some(locale) = client_info.locale.as_ref() {
867        core_metrics::internal_metrics::locale.set_sync(glean, locale);
868    }
869}
870
871/// Checks if [`glean_initialize`] was ever called.
872///
873/// # Returns
874///
875/// `true` if it was, `false` otherwise.
876fn was_initialize_called() -> bool {
877    INITIALIZE_CALLED.load(Ordering::SeqCst)
878}
879
880/// Initialize the logging system based on the target platform. This ensures
881/// that logging is shown when executing the Glean SDK unit tests.
882#[no_mangle]
883pub extern "C" fn glean_enable_logging() {
884    #[cfg(target_os = "android")]
885    {
886        let _ = std::panic::catch_unwind(|| {
887            let filter = android_logger::FilterBuilder::new()
888                .filter_module("glean_ffi", log::LevelFilter::Debug)
889                .filter_module("glean_core", log::LevelFilter::Debug)
890                .filter_module("glean", log::LevelFilter::Debug)
891                .filter_module("glean_core::ffi", log::LevelFilter::Info)
892                .build();
893            android_logger::init_once(
894                android_logger::Config::default()
895                    .with_max_level(log::LevelFilter::Debug)
896                    .with_filter(filter)
897                    .with_tag("libglean_ffi"),
898            );
899            log::trace!("Android logging should be hooked up!")
900        });
901    }
902
903    // On iOS enable logging with a level filter.
904    #[cfg(target_os = "ios")]
905    {
906        // Debug logging in debug mode.
907        // (Note: `debug_assertions` is the next best thing to determine if this is a debug build)
908        #[cfg(debug_assertions)]
909        let level = log::LevelFilter::Debug;
910        #[cfg(not(debug_assertions))]
911        let level = log::LevelFilter::Info;
912
913        let logger = oslog::OsLogger::new("org.mozilla.glean")
914            .level_filter(level)
915            // Filter UniFFI log messages
916            .category_level_filter("glean_core::ffi", log::LevelFilter::Info);
917
918        match logger.init() {
919            Ok(_) => log::trace!("os_log should be hooked up!"),
920            // Please note that this is only expected to fail during unit tests,
921            // where the logger might have already been initialized by a previous
922            // test. So it's fine to print with the "logger".
923            Err(_) => log::warn!("os_log was already initialized"),
924        };
925    }
926
927    // When specifically requested make sure logging does something on non-Android platforms as well.
928    // Use the RUST_LOG environment variable to set the desired log level,
929    // e.g. setting RUST_LOG=debug sets the log level to debug.
930    #[cfg(all(
931        not(target_os = "android"),
932        not(target_os = "ios"),
933        feature = "enable_env_logger"
934    ))]
935    {
936        match env_logger::try_init() {
937            Ok(_) => log::trace!("stdout logging should be hooked up!"),
938            // Please note that this is only expected to fail during unit tests,
939            // where the logger might have already been initialized by a previous
940            // test. So it's fine to print with the "logger".
941            Err(_) => log::warn!("stdout logging was already initialized"),
942        };
943    }
944}
945
946/// **DEPRECATED** Sets whether upload is enabled or not.
947///
948/// **DEPRECATION NOTICE**:
949/// This API is deprecated. Use `set_collection_enabled` instead.
950pub fn glean_set_upload_enabled(enabled: bool) {
951    if !was_initialize_called() {
952        return;
953    }
954
955    crate::launch_with_glean_mut(move |glean| {
956        let state = global_state().lock().unwrap();
957        let original_enabled = glean.is_upload_enabled();
958
959        if !enabled {
960            // Stop the MPS if its handled within Rust.
961            glean.cancel_metrics_ping_scheduler();
962            // Stop wrapper-controlled uploader.
963            if let Err(e) = state.callbacks.cancel_uploads() {
964                log::error!("Canceling upload failed. Error: {}", e);
965            }
966        }
967
968        glean.set_upload_enabled(enabled);
969
970        if !original_enabled && enabled {
971            initialize_core_metrics(glean, &state.client_info);
972        }
973
974        if original_enabled && !enabled {
975            if let Err(e) = state.callbacks.trigger_upload() {
976                log::error!("Triggering upload failed. Error: {}", e);
977            }
978        }
979    })
980}
981
982/// Sets whether collection is enabled or not.
983///
984/// This replaces `set_upload_enabled`.
985pub fn glean_set_collection_enabled(enabled: bool) {
986    glean_set_upload_enabled(enabled)
987}
988
989/// Sets whether Glean should store submitted pings or not.
990pub fn glean_set_store_submitted_pings_enabled(enabled: bool) {
991    if !was_initialize_called() {
992        return;
993    }
994
995    launch_with_glean_mut(move |glean| {
996        glean.store_submitted_pings_enabled = enabled;
997    });
998}
999
1000/// A submitted ping that has been stored by Glean.
1001#[derive(Clone)]
1002pub struct SubmittedPing {
1003    /// The document ID (unique identifier)
1004    pub document_id: String,
1005    /// The ping's name
1006    pub ping: String,
1007    /// RFC3339 datetime string
1008    pub submitted_date: String,
1009    /// Optional RFC3339 datetime string
1010    pub uploaded_date: Option<String>,
1011    /// Whether the upload failed unrecoverably or not
1012    pub upload_failed: Option<String>,
1013    /// The ping's payload
1014    pub payload: Option<JsonValue>,
1015}
1016
1017impl SubmittedPing {
1018    /// Returns the submitted date as a UTC DateTime.
1019    pub fn submitted_date(&self) -> DateTime<Utc> {
1020        DateTime::parse_from_rfc3339(&self.submitted_date)
1021            .map(|d| d.to_utc())
1022            .unwrap()
1023    }
1024
1025    /// Returns the uploaded date as an optional UTC DateTime.
1026    pub fn uploaded_date(&self) -> Option<DateTime<Utc>> {
1027        self.uploaded_date.as_ref().map(|uploaded_date| {
1028            DateTime::parse_from_rfc3339(uploaded_date)
1029                .map(|d| d.to_utc())
1030                .unwrap()
1031        })
1032    }
1033
1034    /// Returns the upload failed date as an optional UTC DateTime.
1035    pub fn upload_failed(&self) -> Option<DateTime<Utc>> {
1036        self.upload_failed.as_ref().map(|upload_failed| {
1037            DateTime::parse_from_rfc3339(upload_failed)
1038                .map(|d| d.to_utc())
1039                .unwrap()
1040        })
1041    }
1042}
1043
1044#[cfg(feature = "sqlite")]
1045impl From<database::sqlite::SubmittedPing> for SubmittedPing {
1046    fn from(value: database::sqlite::SubmittedPing) -> Self {
1047        SubmittedPing {
1048            document_id: value.document_id.clone(),
1049            ping: value.ping.clone(),
1050            submitted_date: value.submitted_date.0.to_rfc3339(),
1051            uploaded_date: value.uploaded_date.as_ref().map(|d| d.0.to_rfc3339()),
1052            upload_failed: value.upload_failed.as_ref().map(|d| d.0.to_rfc3339()),
1053            payload: value.payload(),
1054        }
1055    }
1056}
1057
1058/// Blocks and awaits the Glean Dispatcher before returning a Vec containing the stored pings.
1059pub fn glean_get_all_stored_submitted_pings() -> Vec<SubmittedPing> {
1060    block_on_dispatcher();
1061    core::with_glean(|glean| glean.storage().get_all_submitted_pings())
1062}
1063
1064/// Blocks and awaits the Glean Dispatcher before returning a Vec containing the stored pings with the supplied name.
1065///
1066/// # Arguments
1067///
1068/// * `ping` - The name of the pings that should be returned.
1069pub fn glean_get_stored_submitted_pings_by_name(ping: String) -> Vec<SubmittedPing> {
1070    block_on_dispatcher();
1071    core::with_glean(|glean| glean.storage().get_submitted_pings_by_name(&ping))
1072}
1073
1074/// Clears the stored submitted pings.
1075pub fn glean_clear_stored_submitted_pings() {
1076    launch_with_glean(|glean| {
1077        if let Err(e) = glean
1078            .storage()
1079            .cleanup_submitted_pings(Some(chrono::Utc::now()))
1080        {
1081            log::warn!("Unable to clear stored submitted pings: {:?}", e);
1082        }
1083    });
1084}
1085
1086/// Enable or disable a ping.
1087///
1088/// Disabling a ping causes all data for that ping to be removed from storage
1089/// and all pending pings of that type to be deleted.
1090pub fn set_ping_enabled(ping: &PingType, enabled: bool) {
1091    let ping = ping.clone();
1092    if was_initialize_called() && core::global_glean().is_some() {
1093        crate::launch_with_glean_mut(move |glean| glean.set_ping_enabled(&ping, enabled));
1094    } else {
1095        let m = &PRE_INIT_PING_ENABLED;
1096        let mut lock = m.lock().unwrap();
1097        lock.push((ping, enabled));
1098    }
1099}
1100
1101/// Register a new [`PingType`].
1102pub(crate) fn register_ping_type(ping: &PingType) {
1103    // If this happens after Glean.initialize is called (and returns),
1104    // we dispatch ping registration on the thread pool.
1105    // Registering a ping should not block the application.
1106    // Submission itself is also dispatched, so it will always come after the registration.
1107    if was_initialize_called() && core::global_glean().is_some() {
1108        let ping = ping.clone();
1109        crate::launch_with_glean_mut(move |glean| {
1110            glean.register_ping_type(&ping);
1111        })
1112    } else {
1113        // We need to keep track of pings, so they get re-registered after a reset or
1114        // if ping registration is attempted before Glean initializes.
1115        // This state is kept across Glean resets, which should only ever happen in test mode.
1116        // It's a set and keeping them around forever should not have much of an impact.
1117        let m = &PRE_INIT_PING_REGISTRATION;
1118        let mut lock = m.lock().unwrap();
1119        lock.push(ping.clone());
1120    }
1121}
1122
1123/// Gets a list of currently registered ping names.
1124///
1125/// # Returns
1126///
1127/// The list of ping names that are currently registered.
1128pub fn glean_get_registered_ping_names() -> Vec<String> {
1129    block_on_dispatcher();
1130    core::with_glean(|glean| {
1131        glean
1132            .get_registered_ping_names()
1133            .iter()
1134            .map(|ping| ping.to_string())
1135            .collect()
1136    })
1137}
1138
1139/// Indicate that an experiment is running.  Glean will then add an
1140/// experiment annotation to the environment which is sent with pings. This
1141/// infomration is not persisted between runs.
1142///
1143/// See [`core::Glean::set_experiment_active`].
1144pub fn glean_set_experiment_active(
1145    experiment_id: String,
1146    branch: String,
1147    extra: HashMap<String, String>,
1148) {
1149    launch_with_glean(|glean| glean.set_experiment_active(experiment_id, branch, extra))
1150}
1151
1152/// Indicate that an experiment is no longer running.
1153///
1154/// See [`core::Glean::set_experiment_inactive`].
1155pub fn glean_set_experiment_inactive(experiment_id: String) {
1156    launch_with_glean(|glean| glean.set_experiment_inactive(experiment_id))
1157}
1158
1159/// TEST ONLY FUNCTION.
1160/// Returns the [`RecordedExperiment`] for the given `experiment_id`
1161/// or `None` if the id isn't found.
1162pub fn glean_test_get_experiment_data(experiment_id: String) -> Option<RecordedExperiment> {
1163    block_on_dispatcher();
1164    core::with_glean(|glean| glean.test_get_experiment_data(experiment_id.to_owned()))
1165}
1166
1167/// Set an experimentation identifier dynamically.
1168///
1169/// Note: it's probably a good idea to unenroll from any experiments when identifiers change.
1170pub fn glean_set_experimentation_id(experimentation_id: String) {
1171    launch_with_glean(move |glean| {
1172        glean
1173            .additional_metrics
1174            .experimentation_id
1175            .set(experimentation_id);
1176    });
1177}
1178
1179/// TEST ONLY FUNCTION.
1180/// Gets stored experimentation id annotation.
1181pub fn glean_test_get_experimentation_id() -> Option<String> {
1182    block_on_dispatcher();
1183    core::with_glean(|glean| glean.test_get_experimentation_id())
1184}
1185
1186/// Sets a remote configuration to override metrics' default enabled/disabled
1187/// state
1188///
1189/// See [`core::Glean::apply_server_knobs_config`].
1190pub fn glean_apply_server_knobs_config(json: String) {
1191    // An empty config means it is not set,
1192    // so we avoid logging an error about it.
1193    if json.is_empty() {
1194        return;
1195    }
1196
1197    match RemoteSettingsConfig::try_from(json) {
1198        Ok(cfg) => launch_with_glean(|glean| {
1199            glean.apply_server_knobs_config(cfg);
1200        }),
1201        Err(e) => {
1202            log::error!("Error setting metrics feature config: {:?}", e);
1203        }
1204    }
1205}
1206
1207/// Sets a debug view tag.
1208///
1209/// When the debug view tag is set, pings are sent with a `X-Debug-ID` header with the
1210/// value of the tag and are sent to the ["Ping Debug Viewer"](https://mozilla.github.io/glean/book/dev/core/internal/debug-pings.html).
1211///
1212/// # Arguments
1213///
1214/// * `tag` - A valid HTTP header value. Must match the regex: "[a-zA-Z0-9-]{1,20}".
1215///
1216/// # Returns
1217///
1218/// This will return `false` in case `tag` is not a valid tag and `true` otherwise.
1219/// If called before Glean is initialized it will always return `true`.
1220pub fn glean_set_debug_view_tag(tag: String) -> bool {
1221    if was_initialize_called() && core::global_glean().is_some() {
1222        crate::launch_with_glean_mut(move |glean| {
1223            glean.set_debug_view_tag(&tag);
1224        });
1225        true
1226    } else {
1227        // Glean has not been initialized yet. Cache the provided tag value.
1228        let m = &PRE_INIT_DEBUG_VIEW_TAG;
1229        let mut lock = m.lock().unwrap();
1230        *lock = tag;
1231        // When setting the debug view tag before initialization,
1232        // we don't validate the tag, thus this function always returns true.
1233        true
1234    }
1235}
1236
1237/// Gets the currently set debug view tag.
1238///
1239/// # Returns
1240///
1241/// Return the value for the debug view tag or [`None`] if it hasn't been set.
1242pub fn glean_get_debug_view_tag() -> Option<String> {
1243    block_on_dispatcher();
1244    core::with_glean(|glean| glean.debug_view_tag().map(|tag| tag.to_string()))
1245}
1246
1247/// Sets source tags.
1248///
1249/// Overrides any existing source tags.
1250/// Source tags will show in the destination datasets, after ingestion.
1251///
1252/// **Note** If one or more tags are invalid, all tags are ignored.
1253///
1254/// # Arguments
1255///
1256/// * `tags` - A vector of at most 5 valid HTTP header values. Individual
1257///   tags must match the regex: "[a-zA-Z0-9-]{1,20}".
1258pub fn glean_set_source_tags(tags: Vec<String>) -> bool {
1259    if was_initialize_called() && core::global_glean().is_some() {
1260        crate::launch_with_glean_mut(|glean| {
1261            glean.set_source_tags(tags);
1262        });
1263        true
1264    } else {
1265        // Glean has not been initialized yet. Cache the provided source tags.
1266        let m = &PRE_INIT_SOURCE_TAGS;
1267        let mut lock = m.lock().unwrap();
1268        *lock = tags;
1269        // When setting the source tags before initialization,
1270        // we don't validate the tags, thus this function always returns true.
1271        true
1272    }
1273}
1274
1275/// Sets the log pings debug option.
1276///
1277/// When the log pings debug option is `true`,
1278/// we log the payload of all succesfully assembled pings.
1279///
1280/// # Arguments
1281///
1282/// * `value` - The value of the log pings option
1283pub fn glean_set_log_pings(value: bool) {
1284    if was_initialize_called() && core::global_glean().is_some() {
1285        crate::launch_with_glean_mut(move |glean| {
1286            glean.set_log_pings(value);
1287        });
1288    } else {
1289        PRE_INIT_LOG_PINGS.store(value, Ordering::SeqCst);
1290    }
1291}
1292
1293/// Gets the current log pings value.
1294///
1295/// # Returns
1296///
1297/// Return the value for the log pings debug option.
1298pub fn glean_get_log_pings() -> bool {
1299    block_on_dispatcher();
1300    core::with_glean(|glean| glean.log_pings())
1301}
1302
1303/// Performs the collection/cleanup operations required by becoming active.
1304///
1305/// This functions generates a baseline ping with reason `active`
1306/// and then sets the dirty bit.
1307/// This should be called whenever the consuming product becomes active (e.g.
1308/// getting to foreground).
1309pub fn glean_handle_client_active() {
1310    dispatcher::launch(|| {
1311        core::with_glean_mut(|glean| {
1312            glean.handle_client_active();
1313        });
1314
1315        // The above call may generate pings, so we need to trigger
1316        // the uploader. It's fine to trigger it if no ping was generated:
1317        // it will bail out.
1318        let state = global_state().lock().unwrap();
1319        if let Err(e) = state.callbacks.trigger_upload() {
1320            log::error!("Triggering upload failed. Error: {}", e);
1321        }
1322    });
1323
1324    // The previous block of code may send a ping containing the `duration` metric,
1325    // in `glean.handle_client_active`. We intentionally start recording a new
1326    // `duration` after that happens, so that the measurement gets reported when
1327    // calling `handle_client_inactive`.
1328    core_metrics::internal_metrics::baseline_duration.start();
1329}
1330
1331/// Performs the collection/cleanup operations required by becoming inactive.
1332///
1333/// This functions generates a baseline and an events ping with reason
1334/// `inactive` and then clears the dirty bit.
1335/// This should be called whenever the consuming product becomes inactive (e.g.
1336/// getting to background).
1337pub fn glean_handle_client_inactive() {
1338    // This needs to be called before the `handle_client_inactive` api: it stops
1339    // measuring the duration of the previous activity time, before any ping is sent
1340    // by the next call.
1341    core_metrics::internal_metrics::baseline_duration.stop();
1342
1343    dispatcher::launch(|| {
1344        core::with_glean_mut(|glean| {
1345            glean.handle_client_inactive();
1346        });
1347
1348        // The above call may generate pings, so we need to trigger
1349        // the uploader. It's fine to trigger it if no ping was generated:
1350        // it will bail out.
1351        let state = global_state().lock().unwrap();
1352        if let Err(e) = state.callbacks.trigger_upload() {
1353            log::error!("Triggering upload failed. Error: {}", e);
1354        }
1355    })
1356}
1357
1358/// Starts a session manually.
1359///
1360/// Only has effect in `SessionMode::Manual`. Calling this in `Auto` or
1361/// `Lifecycle` mode is a no-op to prevent corrupting automatic session state.
1362pub fn glean_session_start() {
1363    launch_with_glean_mut(|glean| {
1364        if glean.session_manager.mode == session::SessionMode::Manual {
1365            glean.session_start();
1366        }
1367    });
1368}
1369
1370/// Ends a session manually.
1371///
1372/// Only has effect in `SessionMode::Manual`. Calling this in `Auto` or
1373/// `Lifecycle` mode is a no-op to prevent corrupting automatic session state.
1374///
1375/// `reason` is an optional application-provided string attached to the
1376/// `glean.session_end` boundary event for downstream analysis.
1377pub fn glean_session_end(reason: Option<String>) {
1378    launch_with_glean_mut(move |glean| {
1379        if glean.session_manager.mode == session::SessionMode::Manual {
1380            glean.session_end(reason.as_deref());
1381        }
1382    });
1383}
1384
1385/// Collect and submit a ping for eventual upload by name.
1386pub fn glean_submit_ping_by_name(ping_name: String, reason: Option<String>) {
1387    dispatcher::launch(|| {
1388        let sent =
1389            core::with_glean(move |glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()));
1390
1391        if sent {
1392            let state = global_state().lock().unwrap();
1393            if let Err(e) = state.callbacks.trigger_upload() {
1394                log::error!("Triggering upload failed. Error: {}", e);
1395            }
1396        }
1397    })
1398}
1399
1400/// Collect and submit a ping (by its name) for eventual upload, synchronously.
1401///
1402/// Note: This does not trigger the uploader. The caller is responsible to do this.
1403pub fn glean_submit_ping_by_name_sync(ping_name: String, reason: Option<String>) -> bool {
1404    if !was_initialize_called() {
1405        return false;
1406    }
1407
1408    core::with_opt_glean(|glean| glean.submit_ping_by_name(&ping_name, reason.as_deref()))
1409        .unwrap_or(false)
1410}
1411
1412/// EXPERIMENTAL: Register a listener object to recieve notifications of event recordings.
1413///
1414/// # Arguments
1415///
1416/// * `tag` - A string identifier used to later unregister the listener
1417/// * `listener` - Implements the `GleanEventListener` trait
1418pub fn glean_register_event_listener(tag: String, listener: Box<dyn GleanEventListener>) {
1419    register_event_listener(tag, listener);
1420}
1421
1422/// Unregister an event listener from recieving notifications.
1423///
1424/// Does not panic if the listener doesn't exist.
1425///
1426/// # Arguments
1427///
1428/// * `tag` - The tag used when registering the listener to be unregistered
1429pub fn glean_unregister_event_listener(tag: String) {
1430    unregister_event_listener(tag);
1431}
1432
1433/// **TEST-ONLY Method**
1434///
1435/// Set test mode
1436pub fn glean_set_test_mode(enabled: bool) {
1437    dispatcher::global::TESTING_MODE.store(enabled, Ordering::SeqCst);
1438}
1439
1440/// **TEST-ONLY Method**
1441///
1442/// Destroy the underlying database.
1443pub fn glean_test_destroy_glean(clear_stores: bool, data_path: Option<String>) {
1444    if was_initialize_called() {
1445        // Just because initialize was called doesn't mean it's done.
1446        join_init();
1447
1448        dispatcher::reset_dispatcher();
1449
1450        // Only useful if Glean initialization finished successfully
1451        // and set up the storage.
1452        let has_storage = core::with_opt_glean(|glean| {
1453            // We need to flush the ping lifetime data before a full shutdown.
1454            glean
1455                .storage_opt()
1456                .map(|storage| storage.persist_ping_lifetime_data())
1457                .is_some()
1458        })
1459        .unwrap_or(false);
1460        if has_storage {
1461            uploader_shutdown();
1462        }
1463
1464        if core::global_glean().is_some() {
1465            core::with_glean_mut(|glean| {
1466                if clear_stores {
1467                    glean.test_clear_all_stores()
1468                }
1469                glean.close_db()
1470            });
1471        }
1472
1473        // Allow us to go through initialization again.
1474        INITIALIZE_CALLED.store(false, Ordering::SeqCst);
1475    } else if clear_stores {
1476        if let Some(data_path) = data_path {
1477            let _ = std::fs::remove_dir_all(data_path).ok();
1478        } else {
1479            log::warn!("Asked to clear stores before initialization, but no data path given.");
1480        }
1481    }
1482}
1483
1484/// Get the next upload task
1485pub fn glean_get_upload_task() -> PingUploadTask {
1486    core::with_opt_glean(|glean| glean.get_upload_task()).unwrap_or_else(PingUploadTask::done)
1487}
1488
1489/// Processes the response from an attempt to upload a ping.
1490pub fn glean_process_ping_upload_response(uuid: String, result: UploadResult) -> UploadTaskAction {
1491    core::with_glean(|glean| glean.process_ping_upload_response(&uuid, result))
1492}
1493
1494/// **TEST-ONLY Method**
1495///
1496/// Set the dirty flag
1497pub fn glean_set_dirty_flag(new_value: bool) {
1498    core::with_glean(|glean| glean.set_dirty_flag(new_value))
1499}
1500
1501/// Clears the core attribution data.
1502/// Does not clear glean.attribution.ext (if present).
1503pub fn glean_clear_attribution() {
1504    if was_initialize_called() && core::global_glean().is_some() {
1505        core::with_glean(|glean| glean.clear_attribution());
1506    } else {
1507        PRE_INIT_ATTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1508        _ = PRE_INIT_ATTRIBUTION.lock().unwrap().take()
1509    }
1510}
1511
1512/// Updates attribution fields with new values.
1513/// AttributionMetrics fields with `None` values will not overwrite older values.
1514pub fn glean_update_attribution(attribution: AttributionMetrics) {
1515    if was_initialize_called() && core::global_glean().is_some() {
1516        core::with_glean(|glean| glean.update_attribution(attribution));
1517    } else {
1518        PRE_INIT_ATTRIBUTION
1519            .lock()
1520            .unwrap()
1521            .get_or_insert(Default::default())
1522            .update(attribution);
1523    }
1524}
1525
1526/// **TEST-ONLY Method**
1527///
1528/// Returns the current attribution metrics.
1529/// Panics if called before init.
1530pub fn glean_test_get_attribution() -> AttributionMetrics {
1531    join_init();
1532    core::with_glean(|glean| glean.test_get_attribution())
1533}
1534
1535/// Clears the core distribution data.
1536/// Does not clear glean.distribution.ext (if present).
1537pub fn glean_clear_distribution() {
1538    if was_initialize_called() && core::global_glean().is_some() {
1539        core::with_glean(|glean| glean.clear_distribution());
1540    } else {
1541        PRE_INIT_DISTRIBUTION_CLEARED.store(true, Ordering::SeqCst);
1542        _ = PRE_INIT_DISTRIBUTION.lock().unwrap().take()
1543    }
1544}
1545
1546/// Updates distribution fields with new values.
1547/// DistributionMetrics fields with `None` values will not overwrite older values.
1548pub fn glean_update_distribution(distribution: DistributionMetrics) {
1549    if was_initialize_called() && core::global_glean().is_some() {
1550        core::with_glean(|glean| glean.update_distribution(distribution));
1551    } else {
1552        PRE_INIT_DISTRIBUTION
1553            .lock()
1554            .unwrap()
1555            .get_or_insert(Default::default())
1556            .update(distribution);
1557    }
1558}
1559
1560/// **TEST-ONLY Method**
1561///
1562/// Returns the current distribution metrics.
1563/// Panics if called before init.
1564pub fn glean_test_get_distribution() -> DistributionMetrics {
1565    join_init();
1566    core::with_glean(|glean| glean.test_get_distribution())
1567}
1568
1569#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1570static FD_LOGGER: OnceCell<fd_logger::FdLogger> = OnceCell::new();
1571
1572/// Initialize the logging system to send JSON messages to a file descriptor
1573/// (Unix) or file handle (Windows).
1574///
1575/// Not available on Android and iOS.
1576///
1577/// `fd` is a writable file descriptor (on Unix) or file handle (on Windows).
1578///
1579/// # Safety
1580///
1581/// `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1582/// This function is marked safe,
1583/// because we can't call unsafe functions from generated UniFFI code.
1584#[cfg(all(not(target_os = "android"), not(target_os = "ios")))]
1585pub fn glean_enable_logging_to_fd(fd: u64) {
1586    // SAFETY:
1587    // This functions is unsafe.
1588    // Due to UniFFI restrictions we cannot mark it as such.
1589    //
1590    // `fd` MUST be a valid open file descriptor (Unix) or file handle (Windows).
1591    unsafe {
1592        // Set up logging to a file descriptor/handle. For this usage, the
1593        // language binding should setup a pipe and pass in the descriptor to
1594        // the writing side of the pipe as the `fd` parameter. Log messages are
1595        // written as JSON to the file descriptor.
1596        let logger = FD_LOGGER.get_or_init(|| fd_logger::FdLogger::new(fd));
1597        // Set the level so everything goes through to the language
1598        // binding side where it will be filtered by the language
1599        // binding's logging system.
1600        if log::set_logger(logger).is_ok() {
1601            log::set_max_level(log::LevelFilter::Debug);
1602        }
1603    }
1604}
1605
1606/// Collects information about the data directories used by FOG.
1607fn collect_directory_info(path: &Path) -> Option<serde_json::Value> {
1608    // List of child directories to check
1609    let subdirs = ["db", "events", "pending_pings"];
1610    let mut directories_info: crate::internal_metrics::DataDirectoryInfoObject =
1611        DataDirectoryInfoObject::with_capacity(subdirs.len());
1612
1613    for subdir in subdirs.iter() {
1614        let dir_path = path.join(subdir);
1615
1616        // Initialize a DataDirectoryInfoObjectItem for each directory
1617        let mut directory_info = crate::internal_metrics::DataDirectoryInfoObjectItem {
1618            dir_name: Some(subdir.to_string()),
1619            dir_exists: None,
1620            dir_created: None,
1621            dir_modified: None,
1622            file_count: None,
1623            files: Vec::new(),
1624            error_message: None,
1625        };
1626
1627        // Check if the directory exists
1628        if dir_path.is_dir() {
1629            directory_info.dir_exists = Some(true);
1630
1631            // Get directory metadata
1632            match fs::metadata(&dir_path) {
1633                Ok(metadata) => {
1634                    if let Ok(created) = metadata.created() {
1635                        directory_info.dir_created = Some(
1636                            created
1637                                .duration_since(UNIX_EPOCH)
1638                                .unwrap_or(Duration::ZERO)
1639                                .as_secs() as i64,
1640                        );
1641                    }
1642                    if let Ok(modified) = metadata.modified() {
1643                        directory_info.dir_modified = Some(
1644                            modified
1645                                .duration_since(UNIX_EPOCH)
1646                                .unwrap_or(Duration::ZERO)
1647                                .as_secs() as i64,
1648                        );
1649                    }
1650                }
1651                Err(error) => {
1652                    let msg = format!("Unable to get metadata: {}", error.kind());
1653                    directory_info.error_message = Some(msg.clone());
1654                    log::warn!("{}", msg);
1655                    continue;
1656                }
1657            }
1658
1659            // Read the directory's contents
1660            let mut file_count = 0;
1661            let entries = match fs::read_dir(&dir_path) {
1662                Ok(entries) => entries,
1663                Err(error) => {
1664                    let msg = format!("Unable to read subdir: {}", error.kind());
1665                    directory_info.error_message = Some(msg.clone());
1666                    log::warn!("{}", msg);
1667                    continue;
1668                }
1669            };
1670            for entry in entries {
1671                directory_info.files.push(
1672                    crate::internal_metrics::DataDirectoryInfoObjectItemItemFilesItem {
1673                        file_name: None,
1674                        file_created: None,
1675                        file_modified: None,
1676                        file_size: None,
1677                        error_message: None,
1678                    },
1679                );
1680                // Safely get and unwrap the file_info we just pushed so we can populate it
1681                let file_info = directory_info.files.last_mut().unwrap();
1682                let entry = match entry {
1683                    Ok(entry) => entry,
1684                    Err(error) => {
1685                        let msg = format!("Unable to read file: {}", error.kind());
1686                        file_info.error_message = Some(msg.clone());
1687                        log::warn!("{}", msg);
1688                        continue;
1689                    }
1690                };
1691                let file_name = match entry.file_name().into_string() {
1692                    Ok(file_name) => file_name,
1693                    _ => {
1694                        let msg = "Unable to convert file name to string".to_string();
1695                        file_info.error_message = Some(msg.clone());
1696                        log::warn!("{}", msg);
1697                        continue;
1698                    }
1699                };
1700                let metadata = match entry.metadata() {
1701                    Ok(metadata) => metadata,
1702                    Err(error) => {
1703                        let msg = format!("Unable to read file metadata: {}", error.kind());
1704                        file_info.file_name = Some(file_name);
1705                        file_info.error_message = Some(msg.clone());
1706                        log::warn!("{}", msg);
1707                        continue;
1708                    }
1709                };
1710
1711                // Check if the entry is a file
1712                if metadata.is_file() {
1713                    file_count += 1;
1714
1715                    // Collect file details
1716                    file_info.file_name = Some(file_name);
1717                    file_info.file_created = Some(
1718                        metadata
1719                            .created()
1720                            .unwrap_or(UNIX_EPOCH)
1721                            .duration_since(UNIX_EPOCH)
1722                            .unwrap_or(Duration::ZERO)
1723                            .as_secs() as i64,
1724                    );
1725                    file_info.file_modified = Some(
1726                        metadata
1727                            .modified()
1728                            .unwrap_or(UNIX_EPOCH)
1729                            .duration_since(UNIX_EPOCH)
1730                            .unwrap_or(Duration::ZERO)
1731                            .as_secs() as i64,
1732                    );
1733                    file_info.file_size = Some(metadata.len() as i64);
1734                } else {
1735                    let msg = format!("Skipping non-file entry: {}", file_name.clone());
1736                    file_info.file_name = Some(file_name);
1737                    file_info.error_message = Some(msg.clone());
1738                    log::warn!("{}", msg);
1739                }
1740            }
1741
1742            directory_info.file_count = Some(file_count as i64);
1743        } else {
1744            directory_info.dir_exists = Some(false);
1745        }
1746
1747        // Add the directory info to the final collection
1748        directories_info.push(directory_info);
1749    }
1750
1751    if let Ok(directories_info_json) = serde_json::to_value(directories_info) {
1752        Some(directories_info_json)
1753    } else {
1754        log::error!("Failed to serialize data directory info");
1755        None
1756    }
1757}
1758
1759fn record_dir_info_and_submit_health_ping(
1760    glean: &Glean,
1761    dir_info: Option<serde_json::Value>,
1762    reason: &str,
1763) -> bool {
1764    glean
1765        .health_metrics
1766        .data_directory_info
1767        .set_sync(glean, dir_info.unwrap_or(serde_json::json!({})));
1768    glean.internal_pings.health.submit_sync(glean, Some(reason))
1769}
1770
1771/// Unused function. Not used on Android or iOS.
1772#[cfg(any(target_os = "android", target_os = "ios"))]
1773pub fn glean_enable_logging_to_fd(_fd: u64) {
1774    // intentionally left empty
1775}
1776
1777// UNIFFI - START
1778
1779uniffi::include_scaffolding!("glean");
1780
1781type CowString = Cow<'static, str>;
1782
1783uniffi::custom_type!(CowString, String, {
1784    remote,
1785    lower: |s| s.into_owned(),
1786    try_lift: |s| Ok(Cow::from(s))
1787});
1788
1789type JsonValue = serde_json::Value;
1790
1791uniffi::custom_type!(JsonValue, String, {
1792    remote,
1793    lower: |s| serde_json::to_string(&s).unwrap(),
1794    try_lift: |s| Ok(serde_json::from_str(&s)?)
1795});
1796
1797// UNIFFI - END
1798
1799// Split unit tests to a separate file, to reduce the file of this one.
1800#[cfg(test)]
1801#[path = "lib_unit_tests.rs"]
1802mod tests;