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