> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/observe/get-started-with-observe.md).

# Get started with Observe

Observe has two halves: the server needs a ClickHouse database to store telemetry, and the app needs the expo-observe SDK pointed at your server. Follow the steps in order; each one is a few minutes.

### Step 1: provision ClickHouse

Create a ClickHouse database the server can write to, self-hosted or ClickHouse Cloud. An empty database is enough: the server runs its own ClickHouse migrations at startup.

### Step 2: point the server at it

Set the connection string and restart the server:

```bash
CLICKHOUSE_URL=clickhouse://user:password@host:9000/xprem
```

The `eoas server:init` wizard asks about Observe and pre-fills this variable for you.

{% hint style="info" %}
Without `CLICKHOUSE_URL`, the `/v1/logs` endpoint still feeds the device registry and update health, but metric batches are acknowledged and dropped without being read, and no metric or log is stored, so the Observe dashboards stay empty.
{% endhint %}

### Step 3: install expo-observe in the app

```bash
npx expo install expo-observe
```

It is Expo's open source telemetry SDK, and it needs Expo SDK 55 or later. Log events, and therefore custom events and custom attributes, need SDK 56 or later.

### Step 4: point the SDK at your server

In `app.json` (or `app.config.js`):

```json
{
  "expo": {
    "extra": {
      "eas": {
        "projectId": "<your-eas-project-id>",
        "observe": {
          "endpointUrl": "https://<your-ota-domain>/observe/<app-id>"
        }
      }
    }
  }
}
```

`<app-id>` is your app's id in the xprem dashboard; it is what identifies your app to the server.

{% hint style="info" %}
`projectId` is a leftover of the SDK's origins on Expo's backend: the SDK refuses to start without one, but xprem ignores its value. If you build with EAS, keep the project id you already have. If you do not use EAS, put any non-empty string, for example `"xprem"`. Nothing is sent to Expo either way; the SDK only talks to the `endpointUrl` you configured.
{% endhint %}

### Step 5: report app starts and JS crashes

This step is what makes update health work for JS crashes, and both events matter as a pair: `xprem_js_crash` marks the running update as faulty, and `app_started` marks it healthy again. If you skip `app_started`, a crashed update stays flagged forever, and expo-updates' own signals only cover crashes at startup, not crashes that happen later.

Log `app_started` in your root component, so it fires on every successful launch, once the JS bundle is actually up and rendering. With expo-router that is `app/_layout.tsx`; otherwise your `App.tsx`:

```tsx
// app/_layout.tsx (expo-router) or App.tsx
import { useEffect } from 'react';
import { Observe } from 'expo-observe';

export default function RootLayout() {
  useEffect(() => {
    Observe.logEvent('app_started');
  }, []);

  // ...the rest of your root component
}
```

Log `xprem_js_crash` from the global error handler, in your entry file (`index.js`, or the top of `App.tsx`), so it catches fatal errors from anywhere in the app:

```ts
// index.js, before the app is registered
import { Observe } from 'expo-observe';

const previousHandler = ErrorUtils.getGlobalHandler();
ErrorUtils.setGlobalHandler((error, isFatal) => {
  if (isFatal) {
    Observe.logEvent('xprem_js_crash', {
      severity: 'fatal',
      attributes: { message: String(error?.message ?? error) },
    });
  }
  previousHandler?.(error, isFatal);
});
```

Fatal events are persisted on the device and shipped on the next launch, so the crash report survives the crash itself.

### Step 6: rebuild and verify

Rebuild the app: expo-observe is a native module, so it needs a development build or a store build, not Expo Go.

Then launch the app, put it in the background, and check the Observe section of the dashboard: startup metrics, update download times and JS errors are collected automatically, and batches are shipped when the app goes to the background, not continuously.

{% hint style="warning" %}
Debug builds do not send anything by default. To test the pipeline from a debug build, opt in with `Observe.configure({ dispatchInDebug: true })`.
{% endhint %}

### Going further

* **Custom attributes**: attach your own metadata to devices with the `$set`, `$set_once` and `$unset` events; see [Custom attributes](/xprem/observe/custom-attributes.md).
* **Sampling**: on large fleets, `Observe.configure({ sampleRate: 0.1 })` makes a stable 10% of installs report; the choice is deterministic per device.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/observe/get-started-with-observe.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
