Revision control

Copy as Markdown

# 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.
{{#include ../../../shared/blockquote-info.html}}
## 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 1 million 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`) 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.
Note that on some SDKs some of the options are taken as a configuration object.
Check the respective SDK documentation for details.
| Configuration Option | Default value | Description |
| -------------------- | ------------- | ----------- |
| `applicationId` | On Android/iOS: determined automatically. Otherwise *required*. | 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` | _Required_ | The user preference on whether or not data upload is enabled. |
| `channel` | - | The application's release channel. When present, the `app_channel` will be reported in all pings' [`client_info`](../../user/pings/index.html#the-client_info-section) sections. |
| `appBuild` | On Android/iOS: determined automatically. Otherwise: - | 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`](../../user/pings/index.html#the-client_info-section) sections. |
| `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`](../../user/pings/index.html#the-client_info-section) sections.
| `serverEndpoint` | `https://incoming.telemetry.mozilla.org` | The server pings are sent to.
| `maxEvents` | Glean.js: 1. <br> Other SDKs: 500. | The maximum number of events the Glean storage will hold on to before submitting the 'events' ping. Refer to the [`events` ping documentation](../../user/pings/events.md) 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](#custom-uploaders) for more information on how and when the use this feature. |
| `logLevel` | - | The level for how verbose the internal logging is. The level filter options in order from least to most verbose are: `Off`, `Error`, `Warn`, `Info`, `Debug`, `Trace`. See the [`log` crate docs](https://docs.rs/log/latest/log/) for more information. |
| `enableEventTimestamps` | `true` | Whether to add a wall clock timestamp to all events. |
| `rateLimit` | 15 pings per 60s interval | Specifies the maximum number of pings that can be uploaded per interval of a specified number of seconds. |
| `experimentationId` | - | Optional. An identifier derived by the application to be sent in all pings for the purpose of experimentation. See the experiments API documentation for more information. |
| `enableInternalPings` | `true` | Whether to enable the internal "baseline", "events", and "metrics" pings. |
| `delayPingLifetimeIo` | `false` | Whether Glean should delay persistence of data from metrics with `ping` lifetime. On Android data is automatically persisted every 1000 writes and on backgrounding when enabled. |
To learn about SDK specific configuration options available, refer to the [Reference](#reference) section.
{{#include ../../../shared/blockquote-stop.html}}
##### 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(uploadEnabled=userSettings.telemetryEnabled)` or the equivalent for your application.
>
> Calling `Glean.setUploadEnabled(false)` at a later point will trigger [`deletion-request` pings](../../user/pings/deletion-request.md) and regenerate client IDs.
> This should only be done if the user preference actually changes.
{{#include ../../../shared/tab_header.md}}
<div data-lang="Kotlin" class="tab">
An excellent place to initialize Glean is within the `onCreate` method of the class that extends Android's `Application` class.
```Kotlin
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,
configuration = Configuration(),
buildInfo = GleanBuildInfo.buildInfo
)
}
}
```
The Glean Kotlin SDK supports use across multiple processes. This is enabled by setting a `dataPath` value in the `Glean.Configuration` object passed to `Glean.initialize`. You **do not** need to set a `dataPath` for your main process. This configuration should only be used by a non-main process.
Requirements for a non-main process:
- `Glean.initialize` must be called with the `dataPath` value set in the `Glean.Configuration`.
- The default `dataPath` for Glean is `{context.applicationInfo.dataDir}/glean_data`. If you try to use this path, `Glean.initialize` will fail and throw an error.
- [Set the default process name](https://developer.android.com/reference/androidx/work/Configuration.Builder#setDefaultProcessName(java.lang.String)) as your main process. If this is not set up correctly, pings from the non-main process will not send. `Configuration.Builder().setDefaultProcessName(<main_process_name>)`
**Note**: When initializing from a non-main process with a specified `dataPath`, the lifecycle observers will not be set up. This means you will not receive otherwise scheduled [baseline](../../user/pings/baseline.md) or [metrics](../../user/pings/metrics.md) pings.
### 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:
```Kotlin
// 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
)
```
</div>
<div data-lang="Swift" class="tab">
An excellent place to initialize Glean is within the `application(_:)` method of the class that extends the `UIApplicationDelegate` class.
```Swift
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,
configuration = Configuration(),
buildInfo = GleanMetrics.GleanBuild.info
)
}
}
```
The Glean Swift SDK supports use across multiple processes. This is enabled by setting a `dataPath` value in the `Glean.Configuration` object passed to `Glean.initialize`. You **do not** need to set a `dataPath` for your main process. This configuration should only be used by a non-main process.
Requirements for a non-main process:
- `Glean.initialize` must be called with the `dataPath` value set in the `Glean.Configuration`.
- On iOS devices, Glean stores data in the Application Support directory. The default `dataPath` Glean uses is `{FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]}/glean_data`. If you try to use this path, `Glean.initialize` will fail and throw an error.
**Note**: When initializing from a non-main process with a specified `dataPath`, the lifecycle observers will not be set up. This means you will not receive otherwise scheduled [baseline](../../user/pings/baseline.md) or [metrics](../../user/pings/metrics.md) pings.
</div>
<div data-lang="Python" class="tab">
The main control for the Glean Python SDK is on the `glean.Glean` singleton.
```python
from glean import Glean, Configuration
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(),
configuration=Configuration(),
)
```
Unlike in other implementations, the Python SDK does not automatically send any pings.
See the [custom pings documentation](../../user/pings/custom.md) about adding custom pings and sending them.
</div>
<div data-lang="Rust" class="tab">
The Glean Rust SDK should be initialized as soon as possible.
```Rust
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,
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(),
channel: None,
locale: None,
};
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`](../../../docs/glean/net/trait.PingUploader.html) for details.
</div>
<div data-lang="JavaScript" class="tab">
```js
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"
}
);
```
</div>
<div data-lang="Firefox Desktop" class="tab" data-info="On Firefox Desktop Glean initialization is handled internally."></div>
{{#include ../../../shared/tab_footer.md}}
### 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.
{{#include ../../../shared/tab_header.md}}
<div data-lang="Kotlin" class="tab">
for details on how to implement a custom upload on Kotlin.
</div>
<div data-lang="Swift" class="tab">
See [`HttpPingUploader`](../../../swift/Classes/HttpPingUploader.html)
for details on how to implement a custom upload on Swift.
</div>
<div data-lang="Python" class="tab">
See [`BaseUploader`](../../../python/glean/net/base_uploader.html#glean.net.base_uploader.BaseUploader)
for details on how to implement a custom upload on Python.
</div>
<div data-lang="Rust" class="tab">
See [`PingUploader`](../../../docs/glean/net/trait.PingUploader.html)
for details on how to implement a custom upload on Rust.
</div>
<div data-lang="JavaScript" class="tab">
```ts
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()
}
);
```
</div>
<div data-lang="Firefox Desktop" class="tab"></div>
{{#include ../../../shared/tab_footer.md}}
## 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.
{{#include ../../../shared/tab_header.md}}
<div data-lang="Kotlin" class="tab">
Using the Glean Kotlin SDK's unit testing API requires adding
[Robolectric 4.0 or later](http://robolectric.org/) as a testing dependency.
In Gradle, this can be done by declaring a `testImplementation` dependency:
```groovy
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:
```kotlin
@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.
</div>
<div data-lang="Swift" class="tab">
**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:
```swift
// 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.
</div>
<div data-lang="Python" class="tab">
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](https://pypi.org/project/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:
```python
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")
```
</div>
<div data-lang="Rust" class="tab">
{{#include ../../../shared/blockquote-warning.html}}
##### Note
> Glean uses a global singleton object. Tests need to run single-threaded or need to ensure exclusivity using a lock.
The Glean Rust SDK contains a helper function `test_reset_glean()` for resetting Glean for tests.
It has three required arguments:
1. the configuration to use
2. the client info to use
3. whether to clear stores before initialization
You can call it like below in every test:
```rust
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().to_path_buf();
let glean::Configuration {
data_path: tmpname,
application_id: "app-id".into(),
upload_enabled: true,
max_events: None,
delay_ping_lifetime_io: false,
server_endpoint: Some("invalid-test-host".into()),
uploader: None,
use_core_mps: false,
};
let client_info = glean::ClientInfoMetrics::unknown();
glean::test_reset_glean(cfg, client_info, false);
```
</div>
<div data-lang="JavaScript" class="tab">
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.
```js
import { testResetGlean } from "@mozilla/glean/testing"
describe("myTestSuite", () => {
beforeEach(async () => {
await testResetGlean("my-test-id");
});
});
```
</div>
<div data-lang="Firefox Desktop" class="tab" data-info='Documentation on how to reset Glean in Firefox Desktop tests can be found at <a href="https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/instrumentation_tests.html" target="_blank">"Writing Instrumentation Tests"</a>'></div>
{{#include ../../../shared/tab_footer.md}}
## Reference
- [Swift API docs](../../../swift/Classes/Glean.html#/s:5GleanAAC10initialize13uploadEnabled13configuration9buildInfoySb_AA13ConfigurationVAA05BuildG0VtF)
- [Python API docs](../../../python/glean/index.html#glean.Glean.initialize)
- [Rust API docs](../../../docs/glean/fn.initialize.html)