> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenTelemetry Tracing

> Export Cognee traces, metrics, and logs to OpenTelemetry backends.

Cognee ships a built-in OpenTelemetry (OTEL) layer. It creates OTEL spans for every `@observe`-decorated function, emits [metrics](#metrics) and [log records](#logs) for the core memory operations, and can export all three to any OTLP-compatible backend — Grafana Tempo, Jaeger, Dash0, Dynatrace, Datadog, Honeycomb, and others.

## How It Works

In Cognee core, the `@observe` decorator maps only to OpenTelemetry. When tracing is enabled, it wraps each decorated function in an OTEL span; when tracing is disabled it is a no-op that passes the call straight through. There is no separate Sentry backend to configure. [Langfuse](#langfuse) is supported not as a separate SDK but as one more OTLP destination on this same pipeline — set your Langfuse keys and Cognee derives the OTLP endpoint and auth for you.

The single switch turns on all three signals: enabling tracing also configures a `MeterProvider` for the [memory operation metrics](#metrics) and attaches an [OTel log bridge](#logs) to Cognee's loggers. Failures while setting up metrics or the log bridge are swallowed, so a missing or partial OpenTelemetry install degrades to traces-only rather than breaking your application.

| Feature          | OTEL                                       |
| ---------------- | ------------------------------------------ |
| Enabled by       | `COGNEE_TRACING_ENABLED=true`              |
| Signals          | Traces, metrics, logs                      |
| Data destination | Any OTLP backend (or in-memory, for spans) |
| Span type        | OTEL span                                  |

## Installation

OTEL support requires OpenTelemetry dependencies. Install them with the `tracing` extra:

```bash theme={null}
pip install 'cognee[tracing]'
```

The extra installs `opentelemetry-sdk` plus the OTLP gRPC and HTTP exporters. No additional packages are needed for the in-memory buffer.

## Quick Start

### 1. Enable tracing via environment variable

```dotenv theme={null}
COGNEE_TRACING_ENABLED=true
```

That's all that is required to activate in-memory span collection. Spans are buffered in a ring buffer (last 50 traces) and can be read programmatically.

### 2. Export to an OTLP backend (optional)

Point cognee at an OTLP-compatible collector:

```dotenv theme={null}
COGNEE_TRACING_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <token>
OTEL_SERVICE_NAME=my-cognee-service   # default: "cognee"
```

<AccordionGroup>
  <Accordion title="Grafana Tempo / Grafana Cloud">
    ```dotenv theme={null}
    COGNEE_TRACING_ENABLED=true
    OTEL_EXPORTER_OTLP_ENDPOINT=https://tempo-us-central1.grafana.net:443
    OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic <base64(instanceId:token)>
    OTEL_SERVICE_NAME=cognee
    ```
  </Accordion>

  <Accordion title="Jaeger (local)">
    ```dotenv theme={null}
    COGNEE_TRACING_ENABLED=true
    OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
    OTEL_SERVICE_NAME=cognee
    ```

    Start Jaeger with the all-in-one image and its OTLP gRPC port (`4317`) exposed.
  </Accordion>

  <Accordion title="Dash0">
    ```dotenv theme={null}
    COGNEE_TRACING_ENABLED=true
    OTEL_EXPORTER_OTLP_ENDPOINT=https://ingress.eu-west-1.aws.dash0.com:4317
    OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <dash0_auth_token>
    OTEL_SERVICE_NAME=cognee
    ```
  </Accordion>

  <Accordion title="Dynatrace">
    ```dotenv theme={null}
    COGNEE_TRACING_ENABLED=true
    OTEL_EXPORTER_OTLP_ENDPOINT=https://<environment-id>.live.dynatrace.com/api/v2/otlp/v1/traces
    OTEL_EXPORTER_OTLP_HEADERS=Authorization=Api-Token <dynatrace_api_token>
    OTEL_SERVICE_NAME=cognee
    ```

    Dynatrace ingests OTLP over HTTP only. Cognee recognizes Dynatrace endpoints and forces the OTLP **HTTP** exporter for them, so you do not need to uninstall the gRPC exporter or set any additional variable (see the note under [Environment Variables Reference](#environment-variables-reference)).
  </Accordion>
</AccordionGroup>

### 3. Using an auto-instrumentation agent

If you launch your application with `opentelemetry-instrument` or an APM agent (Datadog, Dash0, Elastic), it configures its own `TracerProvider` before your code runs. Cognee detects this and **attaches its in-memory exporter to the existing provider** instead of creating a new one — so cognee spans appear inside your existing trace alongside other spans from your application.

```bash theme={null}
opentelemetry-instrument python my_app.py
```

No additional Cognee configuration is required beyond enabling tracing. Your external agent or APM still needs its own exporter configuration if you want spans sent to a remote backend; in this mode Cognee attaches its in-memory exporter to the existing provider so `get_last_trace()` and related helpers still work.

## Langfuse

[Langfuse](https://langfuse.com) is wired into the same OTLP pipeline as any other backend — there is no separate Langfuse SDK. Setting your Langfuse keys is enough: Cognee derives the OTLP endpoint and Basic-auth header, and enables tracing for you. LLM calls emitted as **generation** spans render as generations in the Langfuse dashboard without extra instrumentation.

```dotenv theme={null}
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
# Optional; defaults to https://cloud.langfuse.com
LANGFUSE_HOST=https://us.cloud.langfuse.com
```

You do **not** need to set `COGNEE_TRACING_ENABLED` — providing the keys turns tracing on automatically. This is fully opt-in: with no `LANGFUSE_*` keys set, nothing changes.

To keep the keys in place but stop tracing, set `COGNEE_TRACING_ENABLED` to an explicit off value (`false`, `0`, or `no`). That is authoritative: the keys no longer auto-enable tracing, and set before tracing initializes it means zero OTLP traffic. Flipping it off after tracing was already enabled in the process stops new spans and metric recordings, but the already-attached log bridge and periodic metric reader keep exporting until you call `disable_tracing()`.

<Warning>
  `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` must be provided together. If only one is set, Cognee raises a `ValueError` at startup to prevent a misconfigured exporter.
</Warning>

When the keys are set, Cognee:

* Derives the OTLP endpoint as `{LANGFUSE_HOST}/api/public/otel/v1/traces`.
* Derives the auth header `Authorization=Basic <base64(public_key:secret_key)>`.
* Resolves the host from `LANGFUSE_HOST`, falling back to `LANGFUSE_BASE_URL` when `LANGFUSE_HOST` is unset, and finally to `https://cloud.langfuse.com`.
* Forces the OTLP **HTTP** exporter, because Langfuse ingests OTLP over HTTP only (see the note under [Environment Variables Reference](#environment-variables-reference)).

<Note>
  Explicit `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_EXPORTER_OTLP_HEADERS` take precedence: if either is already set, Cognee does not overwrite it with the Langfuse-derived value. This lets you route Langfuse traffic through a collector or override the endpoint while still using the `LANGFUSE_*` keys.
</Note>

Langfuse support requires the `tracing` extra (`pip install 'cognee[tracing]'`). A runnable example lives at `examples/guides/langfuse_telemetry.py`.

## Programmatic API

You can also control tracing from Python:

```python theme={null}
from cognee.modules.observability.trace_context import (
    enable_tracing,
    disable_tracing,
    is_tracing_enabled,
    get_last_trace,
    get_all_traces,
    clear_traces,
)

# Enable with optional console output for debugging
enable_tracing(console_output=True)

# ... run cognee operations ...

trace = get_last_trace()
if trace:
    print(trace.summary())
    # {'operation': 'cognee.observe.main', 'total_duration_ms': 1234.5,
    #  'span_count': 12, 'breakdown': {...}, 'errors': []}

    for span in trace.spans():
        print(span["name"], span["duration_ms"])

disable_tracing()
```

`enable_tracing()` sets up all three signals — the `TracerProvider` with the in-memory span buffer, the `MeterProvider` for [metrics](#metrics), and the [log bridge](#logs) — and `console_output=True` routes spans, metrics, and log records to the console. `disable_tracing()` shuts all three down. You do not need to call either when `COGNEE_TRACING_ENABLED=true`: the first traced operation initializes them lazily.

### `CogneeTrace` API

| Method      | Returns      | Description                                                |
| ----------- | ------------ | ---------------------------------------------------------- |
| `spans()`   | `list[dict]` | Flat list of span dicts sorted by start time               |
| `summary()` | `dict`       | Root operation, total duration, per-span breakdown, errors |
| `tree()`    | `dict`       | Hierarchical span tree as nested dicts                     |

Each span dict contains: `name`, `trace_id`, `span_id`, `parent_span_id`, `start_time_ns`, `end_time_ns`, `duration_ms`, `status`, `attributes`.

## Memory Operation Spans

Beyond the `@observe`-derived spans, the four core memory operations emit spans named after the memory-semconv v0.1.0 operations, so a single dashboard can chart Cognee alongside any other memory system that follows the same convention:

| Span              | Emitted by  | `memory.operation` |
| ----------------- | ----------- | ------------------ |
| `memory.store`    | `add()`     | `store`            |
| `memory.process`  | `cognify()` | `process`          |
| `memory.retrieve` | `search()`  | `retrieve`         |
| `memory.delete`   | `forget()`  | `delete`           |

<Warning>
  These span names replace the previous `cognee.api.search`, `cognee.api.cognify`, and `cognee.api.forget` names. Dashboards, saved queries, and alerts that match on the old names must be updated. Span *attributes* are backward compatible — the `cognee.*` keys are still set alongside the new `memory.*` keys.
</Warning>

The `memory.process`, `memory.retrieve`, and `memory.delete` spans wrap their operation, so their duration is the operation's duration. On `add()` the `memory.store` span records the operation's attributes only; use the [`memory.operation.duration`](#metrics) metric for end-to-end `add()` latency.

## Span Attributes

### memory-semconv attributes

Set on the [memory operation spans](#memory-operation-spans) above:

| Attribute             | Description                                                                                  |
| --------------------- | -------------------------------------------------------------------------------------------- |
| `memory.system`       | Always `cognee`                                                                              |
| `memory.operation`    | `store`, `process`, `retrieve`, or `delete`                                                  |
| `memory.collection`   | Dataset name on `memory.store`, defaulting to `main_dataset` when no dataset name was passed |
| `memory.query.text`   | Search query text, truncated to the first 500 characters                                     |
| `memory.query.type`   | Search type enum value used by retrieval                                                     |
| `memory.result.count` | Number of results the search returned after permission filtering                             |

<Note>
  Both query-text attributes — `memory.query.text` and `cognee.search.query` — carry only the first 500 characters of the query. This caps attribute cardinality and limits how much user input reaches your telemetry backend, but it is a truncation, not a redaction: whatever appears in the first 500 characters of a query is exported verbatim. Treat trace data as containing user input and apply your backend's retention and access controls accordingly.
</Note>

### Cognee attributes

Cognee sets the following semantic attributes on spans:

| Attribute                   | Description                                                                                                                                                                                                                                                    |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cognee.span.category`      | Value of `as_type=` passed to `@observe`                                                                                                                                                                                                                       |
| `cognee.llm.model`          | LLM model name                                                                                                                                                                                                                                                 |
| `cognee.llm.provider`       | LLM provider                                                                                                                                                                                                                                                   |
| `cognee.pipeline.stage`     | Pipeline stage that issued the LLM call — `extraction`, `summarization`, or `query`; present on LLM spans made within a stage boundary regardless of whether [per-stage routing](/setup-configuration/llm-providers#per-stage-model-routing) overrides are set |
| `cognee.search.type`        | Search type enum value used by retrieval                                                                                                                                                                                                                       |
| `cognee.search.query`       | Search query text used by retrieval                                                                                                                                                                                                                            |
| `cognee.pipeline.task_name` | Pipeline task name                                                                                                                                                                                                                                             |
| `cognee.vector.collection`  | Vector collection name                                                                                                                                                                                                                                         |
| `cognee.db.system`          | Database backend identifier                                                                                                                                                                                                                                    |

### Generation spans

Spans for LLM calls (functions decorated with `@observe(as_type="generation")`) additionally carry the OTel-GenAI semantic conventions plus Langfuse's observation attributes, so any OTLP backend — Langfuse included — renders them as generations. These spans are also marked `SpanKind.CLIENT` (other spans are `SpanKind.INTERNAL`).

| Attribute                     | Description                                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `gen_ai.request.model`        | Requested LLM model name (omitted when the adapter reports no model, e.g. local llama.cpp)                   |
| `gen_ai.system`               | LLM provider name, lowercased                                                                                |
| `langfuse.observation.type`   | Always `generation` on these spans, so they are classified correctly even when the model name is unavailable |
| `langfuse.observation.input`  | JSON of the call's string prompt arguments; secret-redacted and truncated to 8000 characters                 |
| `langfuse.observation.output` | The LLM response; secret-redacted and truncated to 8000 characters                                           |

## Metrics

Enabling tracing also registers memory-semconv v0.1.0 metric instruments on a `cognee` meter. Cognee records the following instruments from the core memory operations:

| Instrument                  | Type      | Unit       | Recorded by                                                 |
| --------------------------- | --------- | ---------- | ----------------------------------------------------------- |
| `memory.operation.duration` | Histogram | `ms`       | `add()`, `cognify()`, `search()`, `forget(everything=True)` |
| `memory.items.stored`       | Counter   | `{item}`   | `add()`                                                     |
| `memory.items.retrieved`    | Counter   | `{item}`   | `search()`                                                  |
| `memory.query.result.count` | Histogram | `{item}`   | `search()`                                                  |
| `memory.vector.searches`    | Counter   | `{search}` | `search()`                                                  |
| `memory.items.deleted`      | Counter   | `{item}`   | `forget(everything=True)`                                   |

Every recording carries `memory.system` and `memory.operation` as attributes; `add()` also attaches `memory.collection`, and the `search()` instruments also attach `memory.query.type` — so you can break latency and result counts down per search type without touching span data.

<Note>
  Two counting details are worth knowing before you build alerts on these. `memory.items.stored` is derived from the length of the `data` argument when it is sized, and `1` otherwise — pass a list to `add()` to get a meaningful per-item count. `forget()` records metrics only on the delete-everything path, where `memory.items.deleted` counts datasets removed; dataset-scoped forget calls record no metrics at all, not even a duration.
</Note>

Four further instruments are registered and exported but not yet recorded by Cognee itself — `memory.data.bytes.stored` (`By`), `memory.graph.nodes.added` (`{node}`), `memory.graph.edges.added` (`{edge}`), and `memory.operation.errors` (`{error}`). They exist so custom instrumentation can feed the same metric names via the helpers in `cognee.modules.observability` (`increment_bytes_stored`, `increment_graph_nodes`, `increment_graph_edges`, `increment_operation_errors`). Expect no data on them out of the box.

### Exporting metrics

Unlike spans, metrics have no in-memory buffer: they are only collected when a reader is attached. Set `OTEL_EXPORTER_OTLP_ENDPOINT` to export them, or pass `console_output=True` to `enable_tracing()` to print them locally. With neither set, the instruments are registered but nothing is collected.

* The metrics endpoint defaults to your traces endpoint with `/v1/traces` rewritten to `/v1/metrics`; if the endpoint contains no `/v1/traces` path it is used as-is. Override it with `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`.
* OTLP metrics are exported every 30 seconds; the console reader exports every 60 seconds.
* Exporter selection follows the same rules as traces — HTTP for Dynatrace, Langfuse, and the other HTTP-only endpoints, gRPC otherwise.
* If an external `MeterProvider` is already configured (for example by `opentelemetry-instrument`), Cognee reuses it instead of creating its own, exactly as it does for the `TracerProvider`.

## Logs

Enabling tracing attaches an OTel `LoggingHandler` (at `DEBUG` level) to the `cognee`, `cognee.api`, `cognee.modules`, `cognee.tasks`, and `cognee.infrastructure` loggers, so Cognee's log output is emitted as OTel log records. Records produced inside a memory operation carry the active span's trace and span IDs, letting your backend pivot from a slow `memory.retrieve` span straight to the log lines it produced.

The bridge is idempotent — repeated enablement will not attach duplicate handlers — and `disable_tracing()` detaches it. Your existing Python logging configuration is untouched: the handler is added alongside whatever handlers you already have, so console and file logging continue to work.

Log export follows the same endpoint rules as metrics: records are exported only when `OTEL_EXPORTER_OTLP_ENDPOINT` is set (or `console_output=True`), and they go to your traces endpoint with `/v1/traces` rewritten to `/v1/logs`, overridable with `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`.

OTLP log records are batched and exported on a background thread (`BatchLogRecordProcessor`), on both the gRPC and the HTTP exporter path, so emitting a log line never waits on a network round trip. Console output (`console_output=True`) is the exception — it still exports each record as it is emitted.

## Environment Variables Reference

| Variable                              | Default                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `COGNEE_TRACING_ENABLED`              | *(unset)*                    | Set to `true` (or `1`/`yes`) to enable OTEL traces, metrics, and the log bridge. An explicit off value — `false`, `0`, or `no` — is authoritative: it wins over `LANGFUSE_*` keys and stops new spans and metric recordings even when tracing was already switched on in the process (already-attached exporters keep running until `disable_tracing()`). Leaving the variable unset is *not* a veto, so Langfuse keys still auto-enable tracing on their own. |
| `OTEL_SERVICE_NAME`                   | `cognee`                     | Service name attached to all spans, metrics, and log records                                                                                                                                                                                                                                                                                                                                                                                                   |
| `OTEL_EXPORTER_OTLP_ENDPOINT`         | *(none)*                     | OTLP collector endpoint for all three signals. Cognee prefers the OTLP gRPC exporter when both gRPC and HTTP exporters are installed, except for the HTTP-only endpoints listed in the note below.                                                                                                                                                                                                                                                             |
| `OTEL_EXPORTER_OTLP_HEADERS`          | *(none)*                     | Auth headers (e.g. `Authorization=Bearer <token>`), applied to the trace, metric, and log exporters                                                                                                                                                                                                                                                                                                                                                            |
| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | *(derived)*                  | Overrides the metrics endpoint. Defaults to `OTEL_EXPORTER_OTLP_ENDPOINT` with `/v1/traces` rewritten to `/v1/metrics`.                                                                                                                                                                                                                                                                                                                                        |
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`    | *(derived)*                  | Overrides the logs endpoint. Defaults to `OTEL_EXPORTER_OTLP_ENDPOINT` with `/v1/traces` rewritten to `/v1/logs`.                                                                                                                                                                                                                                                                                                                                              |
| `OTEL_RESOURCE_ATTRIBUTES`            | *(none)*                     | Comma-separated extra resource attributes attached to the tracer provider resource (e.g. `service.namespace=my-team,service.version=1.0,deployment.environment.name=prod`). Read by the OpenTelemetry SDK itself.                                                                                                                                                                                                                                              |
| `LANGFUSE_PUBLIC_KEY`                 | *(none)*                     | Langfuse public key. Must be set together with `LANGFUSE_SECRET_KEY`; enables tracing and auto-derives the OTLP endpoint and auth header (see [Langfuse](#langfuse)).                                                                                                                                                                                                                                                                                          |
| `LANGFUSE_SECRET_KEY`                 | *(none)*                     | Langfuse secret key. Must be set together with `LANGFUSE_PUBLIC_KEY`.                                                                                                                                                                                                                                                                                                                                                                                          |
| `LANGFUSE_HOST`                       | `https://cloud.langfuse.com` | Langfuse base URL (e.g. a region or self-hosted instance). Falls back to `LANGFUSE_BASE_URL` when unset.                                                                                                                                                                                                                                                                                                                                                       |
| `LANGFUSE_BASE_URL`                   | *(none)*                     | Accepted as an alias for `LANGFUSE_HOST` when `LANGFUSE_HOST` is not set.                                                                                                                                                                                                                                                                                                                                                                                      |

Cognee reads `OTEL_EXPORTER_OTLP_ENDPOINT` directly and passes it to the OTLP exporter. Other standard `OTEL_EXPORTER_OTLP_*` settings such as headers are honored by the underlying exporter library. `OTEL_RESOURCE_ATTRIBUTES` is read by the OpenTelemetry SDK and merged into the provider resource. When Cognee creates its own `TracerProvider`, that includes `service.name`, `service.version`, and `deployment.environment`; in auto-instrumented setups, the external provider's resource configuration applies instead.

<Note>
  Cognee tries the OTLP gRPC exporter first and only falls back to the OTLP HTTP exporter if the gRPC exporter package is unavailable. Because the shipped extras install both exporters, the practical default is gRPC. Use a gRPC-compatible OTLP endpoint unless your endpoint matches one of the HTTP-only patterns below.

  Endpoints known to accept OTLP over HTTP only always use the HTTP exporter, for traces, metrics, and logs alike. Pointing a gRPC exporter at one of these fails silently — the connection is accepted and then closed, so telemetry disappears with no visible error. An endpoint is treated as HTTP-only when it contains any of:

  | Pattern                                                          | Backend                                             |
  | ---------------------------------------------------------------- | --------------------------------------------------- |
  | `/api/public/otel`                                               | Langfuse (including self-hosted on a custom domain) |
  | `dynatrace.com`, `live.dynatrace.com`, `otel.live.dynatrace.com` | Dynatrace                                           |
  | `/api/v2/otlp`                                                   | Dynatrace OTLP path                                 |
  | `:4318`                                                          | Standard OTLP HTTP port, any backend                |
  | `:443/`                                                          | HTTPS with a path — almost always HTTP OTLP         |

  Note the trailing slash in `:443/`: `https://collector:443` still uses gRPC, while `https://collector:443/v1/traces` uses HTTP. If you were relying on gRPC for an HTTPS endpoint that includes a path, that endpoint now exports over HTTP.
</Note>

Span and log export to an OTLP backend is batched — a `BatchSpanProcessor` for spans and a `BatchLogRecordProcessor` for [log records](#logs) — so both are flushed asynchronously on a background thread rather than one request per span or per record. Call `disable_tracing()` before your process exits to flush pending spans, metrics, and log records; because log records are buffered too, skipping it can drop the last batch of them, and the call itself takes as long as the force-flush needs. The in-memory buffer that backs `get_last_trace()` is unaffected and still receives spans as they complete.

<Tip>
  If you are running Cognee as an HTTP server and want to inspect traces or pipeline activity over HTTP, see **Activity and Observability** in [Deploy REST API Server](/guides/deploy-rest-api-server#activity-and-observability).
</Tip>
