@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 thetracing extra:
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
2. Export to an OTLP backend (optional)
Point cognee at an OTLP-compatible collector:Grafana Tempo / Grafana Cloud
Grafana Tempo / Grafana Cloud
Jaeger (local)
Jaeger (local)
4317) exposed.Dash0
Dash0
Dynatrace
Dynatrace
3. Using an auto-instrumentation agent
If you launch your application withopentelemetry-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.
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.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().
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 toLANGFUSE_BASE_URLwhenLANGFUSE_HOSTis unset, and finally tohttps://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.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:
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 acognee 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.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. SetOTEL_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/tracesrewritten to/v1/metrics; if the endpoint contains no/v1/tracespath it is used as-is. Override it withOTEL_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
MeterProvideris already configured (for example byopentelemetry-instrument), Cognee reuses it instead of creating its own, exactly as it does for theTracerProvider.
Logs
Enabling tracing attaches an OTelLoggingHandler (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.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.