Initializing

Glean needs to be initialized in order to be able to send pings, record metrics and perform maintenance tasks. Thus it is advised that Glean be initialized as soon as possible in an application's lifetime and importantly, before any other libraries in the application start using Glean.

Libraries are not required to initialize Glean

Libraries rely on the same Glean singleton as the application in which they are embedded. Hence, they are not expected to initialize Glean as the application should already do that.

Behavior when uninitialized

Any API called before Glean is initialized is queued and applied at initialization. To avoid unbounded memory growth the queue is bounded (currently to a maximum of 100 tasks), and further calls are dropped.

The number of calls dropped, if any, is recorded in the glean.error.preinit_tasks_overflow metric.

Behavior once initialized

When upload is enabled

Once initialized, if upload is enabled, Glean applies all metric recordings and ping submissions, for both user-defined and builtin metrics and pings.

This always happens asynchronously.

When upload is disabled

If upload is disabled, any persisted metrics, events and pings (other than first_run_date and first_run_hour) are cleared. Pending deletion-request pings are sent. Subsequent calls to record metrics and submit pings will be no-ops. Because Glean does that as part of its initialization, users are required to always initialize Glean.

Glean must be initialized even if upload is disabled.

This does not apply to special builds where telemetry is disabled at build time. In that case, it is acceptable to not call initialize at all.

API

Glean.initialize(configuration)

Initializes Glean.

May only be called once. Subsequent calls to initialize are no-op.

Configuration

The available initialize configuration options may vary depending on the SDK. Below are listed the configuration options available on most SDKs.

  • applicationId: Application identifier. For Android and iOS applications, this is the id used on the platform's respective app store and is extracted automatically from the application context.
  • uploadEnabled: The user preference on whether or not data upload is enabled.
  • appChannel: The application's release channel. When present, the app_channel will be reported in all ping's client_info section.
  • appBuild: A build identifier e.g. the build identifier generated by a CI system (e.g. "1234/A"). If not present, app_build will be reported as "Unknown" on all pings client_info section.
  • appDisplayVersion: The user visible version string for the application running Glean. If not present, app_display_version will be reported as "Unknown" on all pings client_info section.
  • serverEndpoint: The server pings are sent to. Defaults to https://incoming.telemetry.mozilla.org.
  • maxEvents: The maximum number of events the Glean storage will hold on to before submitting the 'events' ping. Defaults to 500. Refer to the events ping documentation for more information on its scheduling.
  • httpUploader: A custom HTTP uploader instance, that will overwrite Glean's provided uploader. Useful for users that wish to use specific uploader implementations. See Custom Uploaders for more information on how and when the use this feature.

To learn about SDK specific configuration options available, refer to the Reference section.

Always initialize Glean with the correct upload preference

Glean must always be initialized with real values.

Always pass the user preference, e.g. Glean.initialize(upload=userSettings.telemetry_enabled) or the equivalent for your application. Never call Glean.initialize(upload=true) if true is a placeholder value that later gets reset by Glean.setUploadEnabled(false) and vice-versa.

Depending on the provided placeholder value, this might trigger the generation of new client ids or the submission of bogus deletion-request pings.

An excellent place to initialize Glean is within the onCreate method of the class that extends Android's Application class.

import org.mozilla.yourApplication.GleanMetrics.GleanBuildInfo
import org.mozilla.yourApplication.GleanMetrics.Pings

class SampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        // If you have custom pings in your application, you must register them
        // using the following command. This command should be omitted for
        // applications not using custom pings.
        Glean.registerPings(Pings)

        // Initialize the Glean library.
        Glean.initialize(
            applicationContext,
            // Here, `settings()` is a method to get user preferences, specific to
            // your application and not part of the Glean API.
            uploadEnabled = settings().isTelemetryEnabled,
            buildInfo = GleanBuildInfo.buildInfo
        )
    }
}

The Glean Kotlin SDK does not support use across multiple processes, and must only be initialized on the application's main process. Initializing in other processes is a no-op.

Additionally, Glean must be initialized on the main (UI) thread of the applications main process. Failure to do so will throw an IllegalThreadStateException.

Consuming Glean through Android Components

When the Glean Kotlin SDK is consumed through Android Components, it is required to configure an HTTP client to be used for upload.

For example:

// Requires `org.mozilla.components:concept-fetch`
import mozilla.components.concept.fetch.Client

// Requires `org.mozilla.components:lib-fetch-httpurlconnection`.
// This can be replaced by other implementations, e.g. `lib-fetch-okhttp`
// or an implementation from `browser-engine-gecko`.
import mozilla.components.lib.fetch.httpurlconnection.HttpURLConnectionClient
import mozilla.components.service.glean.config.Configuration
import mozilla.components.service.glean.net.ConceptFetchHttpUploader

val httpClient = ConceptFetchHttpUploader(lazy { HttpURLConnectionClient() as Client })
val config = Configuration(httpClient = httpClient)
Glean.initialize(
   context,
   uploadEnabled = true,
   configuration = config,
   buildInfo = GleanBuildInfo.buildInfo
)

An excellent place to initialize Glean is within the application(_:) method of the class that extends the UIApplicationDelegate class.

import Glean
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // If you have custom pings in your application, you must register them
        // using the following command. This command should be omitted for
        // applications not using custom pings.
        Glean.shared.registerPings(GleanMetrics.Pings)

        // Initialize the Glean library.
        Glean.shared.initialize(
            // Here, `Settings` is a method to get user preferences specific to
            // your application, and not part of the Glean API.
            uploadEnabled = Settings.isTelemetryEnabled,
            buildInfo = GleanMetrics.GleanBuild.info
        )
    }
}

The Glean Swift SDK does not support use across multiple processes, and must only be initialized on the application's main process.

The main control for the Glean Python SDK is on the glean.Glean singleton.

from glean import Glean

Glean.initialize(
    application_id="my-app-id",
    application_version="0.1.0",
    # Here, `is_telemetry_enabled` is a method to get user preferences specific to
    # your application, and not part of the Glean API.
    upload_enabled=is_telemetry_enabled(),
)

Unlike in other implementations, the Python SDK does not automatically send any pings. See the custom pings documentation about adding custom pings and sending them.

The Glean Rust SDK should be initialized as soon as possible.

use glean::{ClientInfoMetrics, Configuration};
let cfg = Configuration {
    data_path,
    application_id: "my-app-id".into(),
    // Here, `is_telemetry_enabled` is a method to get user preferences specific to
    // your application, and not part of the Glean API.
    upload_enabled: is_telemetry_enabled(),
    max_events: None,
    delay_ping_lifetime_io: false,
    channel: None,
    server_endpoint: Some("https://incoming.telemetry.mozilla.org".into()),
    uploader: None,
    use_core_mps: true,
};

let client_info = ClientInfoMetrics {
    app_build: env!("CARGO_PKG_VERSION").to_string(),
    app_display_version: env!("CARGO_PKG_VERSION").to_string(),
};

glean::initialize(cfg, client_info);

The Glean Rust SDK does not support use across multiple processes, and must only be initialized on the application's main process.

Unlike in other implementations, the Rust SDK does not provide a default uploader. See PingUploader for details.

import Glean from "@mozilla/glean/<platform>";

Glean.initialize(
    "my-app-id",
    // Here, `isTelemetryEnabled` is a method to get user preferences specific to
    // your application, and not part of the Glean API.
    isTelemetryEnabled(),
    {
      appDisplayVersion: "0.1.0"
    }
);

Custom Uploaders

A custom HTTP uploader may be provided at initialization time in order to overwrite Glean's native ping uploader implementation. Each SDK exposes a base class for Glean users to extend into their own custom uploaders.

See BaseUploader for details on how to implement a custom upload on Kotlin.

See HttpPingUploader for details on how to implement a custom upload on Swift.

See BaseUploader for details on how to implement a custom upload on Python.

See PingUploader for details on how to implement a custom upload on Rust.

import { Uploader, UploadResult, UploadResultStatus } from "@mozilla/glean/uploader";
import Glean from "@mozilla/glean/<platform>";

/**
 * My custom uploader implementation
 */
export class MyCustomUploader extends Uploader {
  async post(url: string, body: string, headers): Promise<UploadResult> {
    // My custom POST request code
  }
}

Glean.initialize(
    "my-app-id",
    // Here, `isTelemetryEnabled` is a method to get user preferences specific to
    // your application, and not part of the Glean API.
    isTelemetryEnabled(),
    {
      httpClient: new MyCustomUploader()
    }
);

Testing API

When unit testing metrics and pings, Glean needs to be put in testing mode. Initializing Glean for tests is referred to as "resetting". It is advised that Glean is reset before each unit test to prevent side effects of one unit test impacting others.

How to do that and the definition of "testing mode" varies per Glean SDK. Refer to the information below for SDK specific information.

Using the Glean Kotlin SDK's unit testing API requires adding Robolectric 4.0 or later as a testing dependency. In Gradle, this can be done by declaring a testImplementation dependency:

dependencies {
    testImplementation "org.robolectric:robolectric:4.3.1"
}

In order to put the Glean Kotlin SDK into testing mode apply the JUnit GleanTestRule to your test class. Testing mode will prevent issues with async calls when unit testing the Glean SDK on Kotlin. It also enables uploading and clears the recorded metrics at the beginning of each test run.

The rule can be used as shown:

@RunWith(AndroidJUnit4::class)
class ActivityCollectingDataTest {
    // Apply the GleanTestRule to set up a disposable Glean instance.
    // Please note that this clears the Glean data across tests.
    @get:Rule
    val gleanRule = GleanTestRule(ApplicationProvider.getApplicationContext())

    @Test
    fun checkCollectedData() {
      // The Glean Kotlin SDK testing APIs can be called here.
    }
}

This will ensure that metrics are done recording when the other test functions are used.

Note: There's no automatic test rule for Glean tests implemented in Swift.

In order to prevent issues with async calls when unit testing the Glean SDK, it is important to put the Glean Swift SDK into testing mode. When the Glean Swift SDK is in testing mode, it enables uploading and clears the recorded metrics at the beginning of each test run.

Activate it by resetting Glean in your test's setup:

// All pings and metrics testing APIs are marked as `internal`
// so you need to import `Glean` explicitly in test mode.
import XCTest

class GleanUsageTests: XCTestCase {
    override func setUp() {
        Glean.shared.resetGlean(clearStores: true)
    }

    // ...
}

This will ensure that metrics are done recording when the other test functions are used.

The Glean Python SDK contains a helper function glean.testing.reset_glean() for resetting Glean for tests. It has two required arguments: the application ID, and the application version.

Each reset of the Glean Python SDK will create a new temporary directory for Glean to store its data in. This temporary directory is automatically cleaned up the next time the Glean Python SDK is reset or when the testing framework finishes.

The instructions below assume you are using pytest as the test runner. Other test-running libraries have similar features, but are different in the details.

Create a file conftest.py at the root of your test directory, and add the following to reset Glean at the start of every test in your suite:

import pytest
from glean import testing

@pytest.fixture(name="reset_glean", scope="function", autouse=True)
def fixture_reset_glean():
    testing.reset_glean(application_id="my-app-id", application_version="0.1.0")

The Glean JavaScript SDK contains a helper function testResetGlean() for resetting Glean for tests. It expects the same list of arguments as Glean.initialize.

Each reset of the Glean JavaScript SDK will clear stores. Calling testResetGlean will also make metrics and pings testing APIs available and replace ping uploading with a mock implementation that does not make real HTTP requests.

import { testResetGlean } from "@mozilla/glean/testing"

describe("myTestSuite", () => {
    beforeEach(async () => {
        await testResetGlean("my-test-id");
    });
});

Reference