> For the complete documentation index, see [llms.txt](https://docs.ionos.com/cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ionos.com/cloud/tutorials/observability/monitoring-service/monitor-openclaw-with-monitoring-service.md).

# Monitor OpenClaw Usage and Cost with the Monitoring Service

## Overview

[OpenClaw](https://github.com/openclaw/openclaw) is an open-source gateway for running AI agents across chat channels, scheduled automation, and tool integrations. This tutorial uses the community-maintained [`otel-observability` plugin](https://github.com/th-m-vogel/openclaw-observability-plugin).

{% hint style="warning" %}
**Important:** The `otel-observability` plugin is a community-maintained project, not an IONOS product, and IONOS does not support it. Review its source and the permissions it requests before installing it, especially the conversation-content access granted in the [<mark style="color:blue;">Install the plugin</mark>](#install-the-plugin) step.
{% endhint %}

The plugin uses **OpenTelemetry**, an open standard for collecting and exporting observability data about software, to report OpenClaw activity: metrics (how many tokens each model consumed, what each request cost, how many tool calls ran, and how often security guardrails were triggered), together with optional traces and logs.

The <code class="expression">space.vars.ionos\_cloud</code> Monitoring, Logging, and Tracing Services accept data through the **OpenTelemetry Protocol (OTLP)**, the wire format OpenTelemetry uses to send observability data. Because these services ingest OTLP natively, no OpenTelemetry Collector or translation layer is required. Each service exposes a dedicated ingestion endpoint: metrics are stored in **Mimir**, logs in **Loki**, and traces in **Tempo**. Mimir, Loki, and Tempo are Grafana Labs' storage backends for these three signal types. You can use the plugin's per-signal configuration to point each signal to its corresponding pipeline.

This tutorial's dashboard and troubleshooting sections focus on Monitoring, since metrics provide the most broadly applicable signal for cost and usage visibility. Logging and Tracing are equally independent pipelines. Each signal has its own <code class="expression">space.vars.ionos\_cloud</code> service subscription, and you can deploy any single signal or any combination: a traces-only or logs-only setup works just as effectively as a metrics-only one, since none of the three depends on the others.

## Target audience

Platform engineers and OpenClaw operators who run one or more gateways and need visibility into model usage and costs, with all telemetry data stored on European infrastructure they control.

## What you learn

You learn how to:

* Configure the OpenClaw `otel-observability` plugin for the <code class="expression">space.vars.ionos\_cloud</code> Monitoring Service.
* Verify metric delivery, and troubleshoot if data fails to arrive.
* Import a Grafana dashboard for token usage, cost, latency, and security signals.
* Activate usage and cost tracking for custom OpenAI-compatible model providers.
* Optionally, configure the plugin to also export logs to the Logging Service and traces to the Tracing Service, each through its own separate pipeline.
* Explore delivered logs and traces directly in Grafana.

## Before you begin

Ensure you have:

* An active <code class="expression">space.vars.ionos\_cloud</code> account. Contract administrators and owners already have the necessary access. All other users require the privilege matching their chosen signal or signals:

  * **Access and manage Monitoring** for metrics
  * **Access and manage Logging** for logs
  * **Access and manage Tracing** for traces

  For more information, see [<mark style="color:blue;">Set User Privileges</mark>](https://docs.ionos.com/cloud/observability/monitoring-service/dcd-how-tos/set-privileges-monitoring-service).
* An OpenClaw gateway you can restart, with shell access to its host, running version `2026.9.8` or newer. The plugin logs a one-time warning instead of failing to load on older installs, but per-call token metrics (`openclaw.llm.tokens.*`) require this minimum to work.
* At least one of the following, matching the signal or signals you want to export. Metrics is the most broadly applicable signal and the focus of this tutorial's dashboard and troubleshooting sections, but a logs-only or traces-only setup, or any combination, works just as well:
  * **Metrics:** a monitoring pipeline with its HTTP endpoint and API key. For more information, see [<mark style="color:blue;">Monitoring Service User Guide</mark>](https://docs.ionos.com/cloud/observability/monitoring-service).
  * **Logs:** a logging pipeline with its HTTP endpoint and API key. For more information, see [<mark style="color:blue;">Logging Service User Guide</mark>](https://docs.ionos.com/cloud/observability/logging-service).
  * **Traces:** a <code class="expression">space.vars.ionos\_cloud</code> API token to create a tracing pipeline (tracing pipelines are currently created through the API only, not the DCD). To generate a token, see [<mark style="color:blue;">Token Manager</mark>](https://docs.ionos.com/cloud/set-up-ionos-cloud/management/identity-access-management/token-manager).
* Access to the Grafana endpoint of the pipeline or pipelines you use. Monitoring privileges govern pipeline access only and do not grant Grafana access.

## Cost considerations

This tutorial uses an existing <code class="expression">space.vars.ionos\_cloud</code> Monitoring Service subscription. The Monitoring Service bills for ingested samples and stored data, so sending OpenClaw metrics adds to your usage.

The plugin exports metrics every 30 seconds by default. Set `metricsIntervalMs` in the plugin's `config` block to change this; a longer interval reduces ingested samples at the cost of coarser time resolution. Beyond that setting, sample volume scales with the number of gateways you run and their activity levels, not at a fixed per-developer rate.

One label drives volume more than any other:

* **Conversation cardinality:** Panels such as **Cost by Agent/Session** group data by `gen_ai_conversation_id`. Each distinct conversation creates a new time series. On a single gateway with long-running sessions, this footprint stays small. However, across many short-lived automated workflows, it can grow rapidly.
* **Per-call sample volume:** Request count, duration, and token metrics (`openclaw_llm_requests`, `openclaw_llm_duration`, `openclaw_llm_tokens_*`) record once per real model API call rather than once per agent turn. This does not add new time series; the label set is unchanged. A turn with several tool-use round-trips now emits one sample per model call instead of one combined sample for the whole turn. `openclaw_llm_cost_usd` is unaffected; it remains turn-level only.

The imported dashboard's cost and token figures are directional rather than exact; see [<mark style="color:blue;">Limitations</mark>](#limitations) below before relying on them for billing reconciliation.

Logs and traces, if you enable them, are separate <code class="expression">space.vars.ionos\_cloud</code> services with their own billing: the Logging Service bills ingested log volume and retention, the Tracing Service bills ingested trace volume and retention. Enabling either is a deliberate opt-in with its own pipeline and its own cost, not a side effect of the metrics setup above.

For pricing information, see [<mark style="color:blue;">Monitoring Service Billing</mark>](https://docs.ionos.com/cloud/observability/monitoring-service/billing), [<mark style="color:blue;">Logging Service Billing</mark>](https://docs.ionos.com/cloud/observability/logging-service/billing), [<mark style="color:blue;">Tracing Service Billing</mark>](https://docs.ionos.com/cloud/observability/tracing-service/billing), and [<mark style="color:blue;">IONOS CLOUD Prices</mark>](https://cloud.ionos.com/prices).

## Architecture

The OpenClaw gateway loads the `otel-observability` plugin as a **hook listener**. A hook is a function the gateway calls at a specific lifecycle point, such as when a model responds or a tool runs. The plugin runs directly within the gateway process, eliminating the need for a separate agent or sidecar. It observes agent, tool, and security events, and exports them over OTLP directly to your configured <code class="expression">space.vars.ionos\_cloud</code> observability pipelines without an intermediate collector.

Monitoring is the most immediately valuable signal, as it drives cost tracking, the dashboard, and the troubleshooting workflows below. However, logging and tracing are fully independent pipelines with their own OTLP endpoints. You can run any combination of the three; a tracing-only setup is just as functional as a metrics-only one.

![Architecture: the OpenClaw gateway's otel-observability plugin exporting metrics, logs, and traces to three independent IONOS CLOUD Observability pipelines (Monitoring/Mimir, Logging/Loki, Tracing/Tempo), queried together in Grafana.](https://3040852435-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEpuEvuLJIyhyeRGhmrv1%2Fuploads%2Fgit-blob-a41e83d737ce2727001eea589adc6589b12ff2ed%2Ftutorial-monitor-openclaw-architecture.png?alt=media)

Each signal follows the same pattern: the plugin exports OTLP straight to its pipeline's ingestion endpoint, the pipeline stores it in the matching Grafana-native backend (Mimir for metrics, Loki for logs, Tempo for traces), and Grafana queries all three - PromQL for metrics, LogQL for logs, TraceQL for traces. One plugin configuration block per signal is all it takes to wire one up.

## Procedure

{% hint style="info" %}
**Note:** If you already have existing pipelines, the following procedure creates a new pipeline per signal, but none of them require a dedicated pipeline. If you already have a Monitoring, Logging, or Tracing pipeline, you can reuse it for this gateway, whether it's your own or a shared one; skip its creation step and carry its existing HTTP endpoint and API key straight into the [<mark style="color:blue;">Configure</mark>](#configure-the-plugin) step below.
{% endhint %}

{% stepper %}
{% step %}

### Create a monitoring pipeline (optional)

Skip this step if you are not exporting metrics.

**Option A: Create in the DCD**

In the Data Center Designer (DCD), create a monitoring pipeline and note its HTTP endpoint and API key. The OTLP metrics path is appended to it:

```
https://<pipeline-id>-metrics.<tenant>.monitoring.<region>.ionos.com/otlp/v1/metrics
```

**Option B: Create through the API**

Replace `<region>` in the URL below with the region where you want to create the pipeline:

```bash
curl --location \
  --request POST 'https://monitoring.<region>.ionos.com/pipelines' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <IONOS_API_TOKEN>' \
  --data '{
    "metadata": {},
    "properties": {
      "name": "openclaw-gateway-monitoring"
    }
  }'
```

The `metadata.httpEndpoint` and `metadata.key` fields in the response provide all the necessary information for the next two steps, eliminating the need for a separate `GET` request. For the full parameter reference, see [<mark style="color:blue;">Create a Monitoring Pipeline Instance</mark>](https://docs.ionos.com/cloud/observability/monitoring-service/api-how-tos/create-monitoring-pipeline).

{% hint style="warning" %}
**Important:** The plugin builds its request URL as `endpoint + "/v1/metrics"`. It has no built-in knowledge of the Monitoring Service's `/otlp` prefix. Set the plugin's `endpoint` value to `https://<pipeline-id>-metrics.<tenant>.monitoring.<region>.ionos.com/otlp` (not the bare host), so the plugin's own suffixing lands on the correct final path. Configuring this endpoint incorrectly fails silently: the pipeline still reports success, no errors appear in the gateway logs, and the only symptom is missing data in Grafana.
{% endhint %}
{% endstep %}

{% step %}

### Create a logging pipeline (optional)

Skip this step if you are not exporting logs.

**Option A: Create in the DCD**

In the DCD, follow [<mark style="color:blue;">Create a Logging Service pipeline</mark>](https://docs.ionos.com/cloud/observability/logging-service/dcd-how-tos/create-logging-service-pipeline) to create a logging pipeline, and note its HTTP endpoint and API key.

**Option B: Create through the API**

Create a pipeline with a single OTLP log stream. Replace `<region>` in the URL below with the region where you want to create the pipeline:

```bash
curl --location \
  --request POST 'https://logging.<region>.ionos.com/pipelines' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <IONOS_API_TOKEN>' \
  --data '{
    "properties": {
      "name": "openclaw-gateway-logging",
      "logs": [
        {
          "tag": "openclaw",
          "protocol": "otlp",
          "source": "generic",
          "destinations": [
            { "type": "loki", "retentionInDays": 7 }
          ]
        }
      ]
    }
  }'
```

The response includes the pipeline's `key` directly. The pipeline starts in a `PROVISIONING` state and can take a few minutes to become ready; retrieve the final `httpAddress` with a follow-up `GET` request once it does (see [<mark style="color:blue;">Retrieve Logging Pipeline Information</mark>](https://docs.ionos.com/cloud/observability/logging-service/api-how-tos/retrieve-logging-pipeline-information)). Logs are ingested at `<httpAddress>/<tag>`.

Unlike the Monitoring Service, the Logging Service's ingestion endpoint does not follow a fixed `/otlp/v1/<signal>` suffix pattern. It ends in the pipeline's own tag instead, for example:

```
https://<pipeline-id>-logs.<tenant>.logging.<region>.ionos.com/<tag>
```

Use the exact endpoint, verbatim, whichever option you used to create it. The Configure step later explains why this matters.
{% endstep %}

{% step %}

### Create a tracing pipeline (optional)

Skip this step if you are not exporting traces.

Tracing pipelines are created through the API only, not the DCD. Send a `POST` request to the regional Tracing Service API endpoint, replacing `<region>` in the URL below with the region where you want to create the pipeline:

```bash
curl --location \
  --request POST 'https://tracing.<region>.ionos.com/pipelines' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <IONOS_API_TOKEN>' \
  --data '{
    "metadata": {},
    "properties": {
      "name": "openclaw-gateway-tracing",
      "protocol": "otlp-http"
    }
  }'
```

The create response returns the pipeline **`id`** and the **ingestion key** (`key`).

{% hint style="warning" %}
**Warning:** Save the key immediately. The Tracing Service returns the ingestion key only once. Store it securely, such as in a password manager. If you lose it, rotate the pipeline key through the [<mark style="color:blue;">Tracing Service API</mark>](https://api.ionos.com/docs/tracing/v1/) to generate a new one.
{% endhint %}

The create response does not include the ingestion endpoint. Retrieve it with a follow-up `GET` request, using the pipeline `id` from the previous response:

```bash
curl --location \
  --request GET 'https://tracing.<region>.ionos.com/pipelines/<pipeline-id>' \
  --header 'Authorization: Bearer <IONOS_API_TOKEN>'
```

From this response, save the ingestion endpoint: the `otlp-http` traces endpoint, of the form `https://<tracing-host>/v1/traces`. For more information on pipelines, endpoints, and regions, see [<mark style="color:blue;">Tracing Pipelines</mark>](https://docs.ionos.com/cloud/observability/tracing-service/overview/tracing-pipelines).
{% endstep %}

{% step %}

### Store the API keys

Copy each pipeline's API key and export it in your shell environment where you edit the configuration. This avoids typing secrets directly into a configuration file that you might accidentally share later. Export only the keys for the signals you are actually setting up:

```bash
# Monitoring (metrics) - optional, only if you created a monitoring pipeline
export IONOS_MONITORING_APIKEY="<monitoring-pipeline-key>"
echo "monitoring key length: ${#IONOS_MONITORING_APIKEY}"

# Logging - optional, only if you created a logging pipeline
export IONOS_LOGGING_APIKEY="<logging-pipeline-key>"
echo "logging key length: ${#IONOS_LOGGING_APIKEY}"

# Tracing - optional, only if you created a tracing pipeline
export IONOS_TRACING_APIKEY="<tracing-pipeline-key>"
echo "tracing key length: ${#IONOS_TRACING_APIKEY}"
```

{% hint style="info" %}
**Note:** A plain shell `export` only lasts for that terminal session, and only reaches a gateway you start from the same shell. If you run the gateway as a managed service (`openclaw gateway install`, or a systemd/launchd/Scheduled Task unit) that service starts independently of your shell and doesn't inherit these variables. For a managed service, write each key to `~/.openclaw/.env` instead (`IONOS_MONITORING_APIKEY=<key>`, one per line); OpenClaw reads this file regardless of how the gateway starts. If a referenced variable is unresolved, OpenClaw leaves `${VAR_NAME}` as a literal, unresolved value, and logs a warning. The plugin then sends no API key, and every request to that pipeline fails with a silent 401.
{% endhint %}

{% hint style="warning" %}
**Warning:** Handle every pipeline key like a password. Each service returns its key exactly once, during pipeline creation. Store each one in a password manager, and do not paste any of them into files, chats, tickets, or version control. If a key is exposed, rotate that pipeline's key to generate a new one. Rotating one signal's key does not affect the others.
{% endhint %}
{% endstep %}

{% step %}

### Install the plugin

Because the plugin is distributed only as TypeScript source code, a standard `npm install` directly from the Git repository fails. Instead, install it from a persistent local directory:

```bash
mkdir -p ~/.openclaw/plugins/otel-observability
cd ~/.openclaw/plugins/otel-observability
git init -q
git remote add origin https://github.com/th-m-vogel/openclaw-observability-plugin.git
git fetch --depth 1 origin 3fe41bde3be02e67b5c69fb4fdd7eb9a39c4621c
git checkout -q FETCH_HEAD
npm install

openclaw plugins install --link ~/.openclaw/plugins/otel-observability --force --accept-capabilities
```

This pins the exact commit the `v0.11.0` tag currently points to, rather than the tag itself, which could move if the release is ever re-tagged. `--force` confirms this is a non-catalog, non-ClawHub source. By design, this community plugin isn't published to either. `--accept-capabilities` is separate consent to the specific permissions the plugin requests: conversation content access via `hooks.allowConversationAccess`, needed because the plugin hooks into session-level diagnostic events and typed hook events to extract usage and cost data.

{% hint style="warning" %}
**Important:** Install the plugin to a persistent directory, such as `~/.openclaw/plugins/<name>/`, rather than a temporary location like `/tmp`. A plugin installed from `/tmp` continues to function until the next restart, after which it silently disappears without producing metrics or error logs.
{% endhint %}
{% endstep %}

{% step %}

### Configure the plugin

`openclaw plugins install --link` already registered the plugin's load path in `openclaw.json` as part of its managed install record; skip adding a `plugins.load.paths` entry yourself. Edit the gateway configuration file `~/.openclaw/openclaw.json` to add the `config` block instead. The minimal form below exports metrics only, using the shared `endpoint`/`headers` fields:

```json
{
  "plugins": {
    "entries": {
      "otel-observability": {
        "enabled": true,
        "hooks": { "allowConversationAccess": true },
        "config": {
          "endpoint": "https://<pipeline-id>-metrics.<tenant>.monitoring.<region>.ionos.com/otlp",
          "headers": { "APIKEY": "${IONOS_MONITORING_APIKEY}" },
          "serviceName": "openclaw-gateway",
          "traces": false,
          "metrics": true,
          "logs": false,
          "captureContent": false
        }
      }
    }
  }
}
```

If you are also exporting logs or traces, replace `endpoint`/`headers` with `signalEndpoints`/`signalHeaders`. One endpoint and one API key per signal, instead of one shared pair:

```json
{
  "plugins": {
    "entries": {
      "otel-observability": {
        "enabled": true,
        "hooks": { "allowConversationAccess": true },
        "config": {
          "serviceName": "openclaw-gateway",
          "traces": true,
          "metrics": true,
          "logs": true,
          "captureContent": false,
          "signalEndpoints": {
            "metrics": "https://<pipeline-id>-metrics.<tenant>.monitoring.<region>.ionos.com/otlp/v1/metrics",
            "logs": "https://<pipeline-id>-logs.<tenant>.logging.<region>.ionos.com/<tag>",
            "traces": "https://<pipeline-id>-traces.<tenant>.tracing.<region>.ionos.com/v1/traces"
          },
          "signalHeaders": {
            "metrics": { "APIKEY": "${IONOS_MONITORING_APIKEY}" },
            "logs": { "APIKEY": "${IONOS_LOGGING_APIKEY}" },
            "traces": { "APIKEY": "${IONOS_TRACING_APIKEY}" }
          }
        }
      }
    }
  }
}
```

`${IONOS_MONITORING_APIKEY}`, `${IONOS_LOGGING_APIKEY}`, and `${IONOS_TRACING_APIKEY}` reference the environment variables you exported in the previous step; OpenClaw substitutes any `${VAR_NAME}` reference in `openclaw.json` with the matching environment variable at load time, so the key itself never needs to be typed into the file.

Set `metrics`/`logs`/`traces` to `false` for any signal you are not exporting. You can leave its `signalEndpoints`/`signalHeaders` entry in place or remove it: a disabled signal never exports, regardless.

{% hint style="warning" %}
**Important:** Per-signal endpoints are used verbatim and are not automatically suffixed. While the shared `endpoint` field automatically appends the `/v1/<signal>` path (as shown in the metrics-only example), `signalEndpoints` entries are used exactly as configured. This is deliberate, as the three <code class="expression">space.vars.ionos\_cloud</code> pipelines have distinct URL structures:

* **Metrics:** Retains the `/otlp` prefix. You must manually append `/v1/metrics` when using a `signalEndpoints` override.
* **Traces:** Omits the `/otlp` prefix entirely.
* **Logs:** Ends with the pipeline's specific tag, rather than `/v1/logs`.

Copy each pipeline's endpoint exactly as provided by the creation step (via DCD or API response). Do not attempt to standardize the URL patterns.
{% endhint %}

{% hint style="warning" %}
**Important:** A signal without its own `signalEndpoints`/`signalHeaders` entry falls back to the shared `endpoint`/`headers` fields, not to a hard failure. If those shared fields aren't set either, it falls back to the plugin's own default (`http://localhost:4318`, no API key), and the export fails silently with nothing in the gateway logs. If you also keep a shared `endpoint` configured for convenience, a signal missing its own `signalEndpoints` entry sends to that endpoint instead. For a metrics `endpoint` this means a logs or traces payload silently lands on the metrics pipeline and is discarded. For every signal you set to `true`, make sure both blocks contain a matching entry.
{% endhint %}

Two settings commonly cause a complete failure regardless of which form you use, and either mistake prevents any data from arriving for any signal:

* **`hooks.allowConversationAccess: true`:** Required for a non-bundled plugin to receive conversation hooks (`llm_output`, `session_*`, and so on). Without it, the plugin registers zero hooks and nothing fires, for any signal.
* **`APIKEY` header name:** Every <code class="expression">space.vars.ionos\_cloud</code> Observability pipeline (Monitoring, Logging, Tracing alike) requires a header named `APIKEY` (header names are case-insensitive, so `apikey` or `ApiKey` work too). A different header name causes a silent 401 Unauthorized, with no error appearing in the gateway logs or from the plugin. This includes `X-API-Key` and an `Authorization` header using the Bearer scheme.

`captureContent: false` keeps prompt and completion bodies out of every exported signal.
{% endstep %}

{% step %}

### Activate usage and cost tracking for custom OpenAI-compatible providers

Skip this step if none of your model providers are custom OpenAI-compatible hosts.

**Rule of thumb:** token usage with no cost figure means OpenClaw doesn't natively support cost calculation for that provider - you supply the price yourself.

OpenClaw core does not automatically activate `stream_options.include_usage` for custom OpenAI-compatible providers, meaning any third-party model host or self-hosted model catalog. Only a fixed "native family" allow list receives this by default. Without it, the plugin emits no token metrics for that provider's models, and no cost metrics either, since cost is derived from token counts.

Even with token metrics flowing, cost stays at `0` for the same provider, for a different reason: the OpenAI Chat Completions response format these providers implement never carries a cost figure, for any model, from any host. OpenClaw core computes cost itself from a built-in price-per-token table, but that table only covers its native providers (OpenAI, Anthropic, and similar). For every other provider, regardless of which one, you supply the price yourself, in USD. You also own keeping it current: OpenClaw has no way to detect a provider's price change, so a configured figure silently goes stale until you update it. For the full custom-provider field reference, including the `cost` block, see OpenClaw's [<mark style="color:blue;">Custom providers and base URLs</mark>](https://docs.openclaw.ai/gateway/config-tools/custom-providers) configuration reference.

Add both `compat.supportsUsageInStreaming: true` and a `cost` block (USD per million tokens, converted from the provider's native currency if needed) to the affected model entry:

```json
{
  "models": {
    "providers": {
      "<provider-id>": {
        "models": [
          {
            "id": "<model-id>",
            "name": "<model-name>",
            "compat": { "supportsUsageInStreaming": true },
            "cost": {
              "input": 0.8,
              "output": 2.4,
              "cacheRead": 0,
              "cacheWrite": 0
            }
          }
        ]
      }
    }
  }
}
```

Use your provider's own published per-token pricing, converted to USD per million tokens, for `input` and `output`. Set `cacheRead`/`cacheWrite` to `0` if your provider doesn't offer a prompt-caching discount. Skip the `compat` flag if all your model providers are already on OpenClaw's native allow list; skip the `cost` block only if you don't need cost figures for that provider.
{% endstep %}

{% step %}

### Restart the gateway and confirm delivery

Configuration changes to `endpoint`/`headers` or `signalEndpoints`/`signalHeaders` require a gateway restart to take effect:

```bash
openclaw gateway restart
```

Confirm that the plugin registered its hooks:

```bash
openclaw plugins inspect otel-observability --runtime --json | jq '.hookCount'
```

**Metrics:** in the Monitoring pipeline's Grafana endpoint, go to **Explore**, select the Mimir data source, and run:

```promql
{__name__=~"openclaw.*"}
```

Metric names are rewritten on ingestion, so dots become underscores: query `openclaw_llm_tokens_total`, not `openclaw.llm.tokens.total`. Metrics land within one to two minutes of a model call.
{% endstep %}

{% step %}

### Explore delivered logs (optional)

Skip this step if you are not exporting logs.

In the Logging pipeline's Grafana endpoint, go to **Explore**, select the Loki data source, and run:

```logql
{pipeline_id="<your-pipeline-id>"}
```

or filter by log content directly, for example `{pipeline_id="<your-pipeline-id>"} |= "model.usage"`. Diagnostic lines such as `[otel] model.usage: ...` and `[otel] Agent turn span started...` confirm delivery.

![The OpenClaw gateway's diagnostic log lines in Grafana Loki Explore](https://3040852435-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEpuEvuLJIyhyeRGhmrv1%2Fuploads%2Fgit-blob-922755b1e85992d02f764c54b073098cde375783%2Ftutorial-monitor-openclaw-logs.png?alt=media)

{% hint style="warning" %}
**Important:** In the Logging Service, every log stream shows `service_name="unknown_service"` instead of `openclaw-gateway`. This is a known backend label-mapping limitation, not a plugin defect or a configuration error. The log content itself is fully intact and can be queried by `pipeline_id` or log body text. For more information, see [<mark style="color:blue;">Limitations</mark>](#limitations).
{% endhint %}

Optionally, import a small Logs dashboard in Grafana (**Dashboards** > **New** > **Import**) for a persistent log view instead of using Explore each time:

{% file src="/files/bCbWuEJ9aKi0sjcqnJvL" %}
Logs dashboard definition
{% endfile %}

Replace `<your-pipeline-id>` in the panel's query with your own logging pipeline's ID, and select your Loki data source when prompted. Logs typically land within one to two minutes of being written.
{% endstep %}

{% step %}

### Explore delivered traces (optional)

Skip this step if you are not exporting traces.

In the Tracing pipeline's Grafana endpoint, go to **Explore**, select the Tempo data source, and search by service name `openclaw-gateway`, or run a TraceQL query such as `{resource.service.name="openclaw-gateway"}`. A trace shows the full span hierarchy for one request: `openclaw.request` → `openclaw.agent.turn` → individual tool and model-call spans. Traces typically land within one to two minutes of a model call or tool invocation.

![A trace for an openclaw.request in Grafana Tempo, showing the agent turn and its child tool spans](https://3040852435-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEpuEvuLJIyhyeRGhmrv1%2Fuploads%2Fgit-blob-b03d5f0de1d5bd92bceddb7b166596a17d1c69e8%2Ftutorial-monitor-openclaw-traces.png?alt=media)
{% endstep %}

{% step %}

### Import the dashboard

Download the dashboard definition below, then in Grafana go to **Dashboards** > **New** > **Import**, upload the file, and select your Mimir data source when prompted.

{% file src="/files/HJj3eD1xQaNbW3YjyVIH" %}
Dashboard definition
{% endfile %}

Once a few model calls arrive, the imported dashboard shows total tokens, implied cost, request count, and error count across the top. Time-series breakdowns by model, token-type distribution charts, per-session costs, tool call activity, and security guardrail signals fill the remaining panels.

This dashboard covers metrics only. If you also enabled logs or traces, they do not appear on this dashboard. The [<mark style="color:blue;">Explore delivered logs</mark>](#explore-delivered-logs-optional) and [<mark style="color:blue;">Explore delivered traces</mark>](#explore-delivered-traces-optional) steps show how to explore them directly in Grafana's Explore view instead.

![The OpenClaw usage and cost dashboard in Grafana](https://3040852435-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEpuEvuLJIyhyeRGhmrv1%2Fuploads%2Fgit-blob-ac1377c734bbf28a5eabce2f50578ae23990742f%2Ftutorial-monitor-openclaw-dashboard.png?alt=media)

{% hint style="info" %}
**Note:** The plugin only sees model calls made through OpenClaw's native model client. A request routed through a different runtime inside OpenClaw (such as the built-in Codex app-server integration) never reaches the cost-carrying diagnostic event the plugin listens to, so it produces no cost or latency metrics and remains absent from the cost and latency panels. Confirm which runtime handles a given model or agent before relying on the dashboard for its usage.
{% endhint %}

This dashboard covers the gateway's own token, cost, latency, and security metrics only. It intentionally has no host-level panels: CPU, network, and disk metrics for the underlying server come from a separate service, the <code class="expression">space.vars.ionos\_cloud</code> Telemetry API, and belong on a dedicated infrastructure dashboard rather than being mixed with application metrics.
{% endstep %}
{% endstepper %}

## Result

OpenClaw gateway activity is stored in the <code class="expression">space.vars.ionos\_cloud</code> Observability pipelines you configured. Metrics are in the Monitoring Service, and optionally logs in the Logging Service and traces in the Tracing Service. The imported dashboard breaks down metrics by model or conversation, reporting request and error counts, total tokens, implied cost, latency percentiles, tool call activity, and security guardrail signals. While metrics appear on the dashboard, logs and traces (if enabled) must be queried directly using the **Explore** tab in Grafana.

See [<mark style="color:blue;">Limitations</mark>](#limitations) below before relying on these figures for billing reconciliation or exact totals.

## Limitations

**Cost figures throughout this dashboard are estimates, not official billing.** OpenClaw calculates every cost figure locally rather than reading it from the provider's response, using a static price-per-token table. For custom providers that table comes entirely from your own `cost` block and can silently go stale; for native providers (OpenAI, Anthropic, and similar) OpenClaw maintains the table itself, but it can still lag behind a provider's own price changes, negotiated or batch discounts, and free-tier usage. For an authoritative dollar amount, use your model provider's own billing dashboard.

The gaps below come from a mix of sources: OpenClaw core, the underlying Logging backend, and the OpenAI Chat Completions format itself. None come from this plugin or the Monitoring Service. Where a workaround exists, it's listed with the gap; where OpenClaw core is the cause, check OpenClaw's own release notes for current status:

* **Custom/self-hosted OpenAI-compatible providers never report cost, only tokens**, unless you add a `cost` block to the model's configuration entry yourself. See [<mark style="color:blue;">Activate usage and cost tracking for custom OpenAI-compatible providers</mark>](#activate-usage-and-cost-tracking-for-custom-openai-compatible-providers) for how.
* **Sessions under a channel- or topic-level model override can show tokens with no cost.** The diagnostic event that carries cost data doesn't fire for these sessions; token counts, tool-call spans, and traces are unaffected. No workaround currently exists short of removing the per-topic override.
* **Some continuation steps inside a multi-call tool-use turn can show tokens with no cost**, correlated with the channel's `messages.groupChat.visibleReplies` setting. Same underlying cause as the model-override gap above (the cost-carrying diagnostic event doesn't fire for that step), and the same lack of workaround.
* **Certain internal or automated session types can show tokens with no cost, or reduced usage telemetry.** This includes OpenClaw's own internal/background agent-turn sessions (scheduled maintenance, memory-effect turns) and its built-in system/custodian agent (the one backing `doctor`, configuration, and setup operations). The custodian agent still emits a top-level turn span, but little else: no cost, and typically no token or tool-call data for its own calls. No workaround exists; this isn't something a dashboard or plugin configuration can compensate for.
* **The `gen_ai_provider_name` label is sometimes missing or inconsistent** across calls to the same model or session. Group Grafana panels by `gen_ai_response_model` instead; it's reliable across all providers.
* **Loki-backed pipelines show `service_name="unknown_service"`** for every log stream regardless of configuration. This is a Logging Service backend limitation, not an OpenClaw core issue: the backend doesn't map the OpenTelemetry `service.name` resource attribute into a Loki stream label. The log content itself is unaffected. Filter by `pipeline_id` or log body text instead of service name.

**Token totals and cost don't come from the same source, and won't always reconcile.** As of plugin `v0.11.0`, `openclaw_llm_tokens_*` (Total Tokens, Tokens by Model) record once per real model call, from a source that's unaffected by every gap above. `openclaw_llm_cost_usd` and the Token Types breakdown (`gen_ai_client_token_usage_sum`) still come from the older turn-level event that every gap above *does* affect. Expect "Token Types - Period Total" to sum to less than "Tokens by Model - Period Total" whenever any of these gaps are in play.

**The top stat-row tiles and "(24h)" panels always show the trailing 24 hours, regardless of the dashboard's selected time range.** Total Tokens, Implied Cost, LLM Requests, LLM Errors, Token Distribution, and both Agent/Session bar gauges all use a fixed 24-hour window. Picking "Last 7 days" in the time picker does not change them; for a total over your selected range, use the Period Total panels instead.

**The "Over Time" and "Token Types" time-series panels have a separate, unrelated accuracy limitation.** All three compute `increase()` over Grafana's auto-scaling `$__rate_interval`, evaluated once per visible time step. These per-step windows overlap by design (`$__rate_interval` is always at least one scrape interval longer than the step), most heavily at narrow zoom levels, where the panel legend's **Sum** adds the overlapping windows together and can inflate the apparent total several-fold. This is standard Prometheus/Mimir `increase()`-plus-Grafana-legend behavior, not a defect in this dashboard. The dedicated "Period Total" panels (Tokens by Model - Period Total, Cost by Model - Period Total) avoid this specific inflation by running a single non-overlapping Instant query instead, though as `increase()`-based queries they can still be slightly high or low at the edges of the selected range. Use them for a much closer total, not an exact one.

**Net effect:** treat this dashboard's cost/token figures as directional, reliable for trends and comparing models but not for exact reconciliation.

## Troubleshooting

**No data appears in Grafana for any signal**

The arrival of data is the only reliable confirmation that the configuration works. Send a prompt through the gateway, wait a minute or two, then work through the following checks in order, because each isolates a different stage.

1. **Confirm the plugin registered its hooks.**

   ```bash
   openclaw plugins inspect otel-observability --runtime --json | jq '.hookCount, .plugin.config.endpoint, .plugin.config.signalEndpoints'
   ```

   A `hookCount` of `0` means `hooks.allowConversationAccess` is missing or `false`.
2. **Check each signal's endpoint URL format:**
   * If you use the shared `endpoint` field (such as in a metrics-only setup), the URL must end with `/otlp`, not the bare pipeline host. The plugin automatically appends `/v1/metrics`.
   * If you use `signalEndpoints`, each entry is used verbatim with no suffix appended. The metrics endpoint requires the full `/otlp/v1/metrics` path. Traces omit the `/otlp` prefix entirely, and logs end with the pipeline's specific tag. Compare your configured value character by character against the exact endpoint provided during the pipeline's creation step (using DCD or the API response). Do not assume all three pipelines follow the same URL structure.
3. **Check the header name.** It must be named `APIKEY` (case-insensitive: `apikey` and `ApiKey` also work). Don't use a differently named header, such as `X-API-Key`, or an `Authorization` header with the Bearer scheme. An incorrect header name results in a silent 401 response, with no errors appearing in the gateway logs or from the plugin.
4. **Check the plugin's install path.** A plugin installed from `/tmp` keeps running until the next restart, then disappears without warning.
5. **Check the region.** If you have pipelines in more than one region, confirm you are querying the Grafana instance belonging to the same pipeline you are sending to.

**Only some enabled signals arrive, not all**

When using `signalEndpoints` and `signalHeaders`, a signal missing its own entry falls back to the shared `endpoint`/`headers` fields rather than failing outright; see the Configure step's warning on this for the full fallback chain. The most common cause here is a typo in the signal name itself (`metrics`, `logs`, or `traces`): a misspelled key silently falls back instead of raising an error, so double-check spelling in both blocks for every enabled pipeline.

**Sessions created before the plugin was installed show tokens without cost**

"Tokens with no cost" is also the symptom of every structural gap covered in [<mark style="color:blue;">Limitations</mark>](#limitations): model overrides, background sessions, continuation steps, a missing `cost` block. This fix applies only to the one cause that's actually fixable: a session that started before the plugin was installed. If the session matches one of the Limitations cases instead, resetting it won't help and loses its conversation history for nothing. Once you've ruled those out, resolve it by resetting the affected session and restarting the gateway:

```bash
openclaw tui --session <affected-session-key> --message "/reset"
openclaw gateway restart
```

**A custom OpenAI-compatible model emits no metrics at all**

Add `compat.supportsUsageInStreaming: true` to the model's configuration entry. For more information, see [<mark style="color:blue;">Activate usage and cost tracking for custom OpenAI-compatible providers</mark>](#activate-usage-and-cost-tracking-for-custom-openai-compatible-providers).

## Decommission resources

To stop all export, disable the plugin entry in `openclaw.json` and restart the gateway. This stops every signal at once, since they all route through the same plugin entry:

```bash
openclaw plugins disable otel-observability
openclaw gateway restart
```

Data that has already been ingested for any signal remains until that pipeline's retention period expires. To stop all charges, delete each pipeline you created: Monitoring and Logging pipelines in the DCD, Tracing pipelines through the API (no DCD option exists for deleting a Tracing pipeline).

## Next steps

* **Add alerting:** The dashboard ships without alerts. A cost-threshold alert on `sum(increase(openclaw_llm_cost_usd[1h])) > <threshold>` and an error-rate alert on `sum(rate(openclaw_llm_errors[5m])) / sum(rate(openclaw_llm_requests[5m])) > 0.05` are good starting points.
* **Combine with infrastructure metrics:** Pair this dashboard with a separate one built on the <code class="expression">space.vars.ionos\_cloud</code> Telemetry API for the gateway's host-level CPU, network, and disk metrics, rather than mixing the two layers into one dashboard.
* **Route through a central OpenTelemetry Collector for multi-gateway deployments:** This gives every gateway a single endpoint and centralizes credential management. It also lets you enrich or filter metrics before they reach the Monitoring Service.
* **Watch conversation cardinality:** Revisit which labels you group by in the **Cost by Agent/Session** panel once more than a few gateways or heavily automated sessions are reporting.


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ionos.com/cloud/tutorials/observability/monitoring-service/monitor-openclaw-with-monitoring-service.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
