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