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