Skip to main content
Cognee ships a built-in OpenTelemetry (OTEL) layer. It creates OTEL spans for every @observe-decorated function, emits metrics and log records 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 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 and attaches an OTel log bridge 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.

Installation

OTEL support requires OpenTelemetry dependencies. Install them with the tracing extra:
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

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:
Start Jaeger with the all-in-one image and its OTLP gRPC port (4317) exposed.
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).

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.
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 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.
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().
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.
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).
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.
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:
enable_tracing() sets up all three signals — the TracerProvider with the in-memory span buffer, the MeterProvider for metrics, and the log bridge — 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

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:
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.
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 metric for end-to-end add() latency.

Span Attributes

memory-semconv attributes

Set on the memory operation spans above:
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.

Cognee attributes

Cognee sets the following semantic attributes on spans:

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).

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: 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.
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.
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

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.
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: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.
Span and log export to an OTLP backend is batched — a BatchSpanProcessor for spans and a BatchLogRecordProcessor for log records — 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.
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.