glean_core/
lib.rs

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