Skip to content

Logs and telemetry

Every part of Datanotes sends its logs with OpenTelemetry, the vendor-neutral standard: OTLP over HTTP to <endpoint>/v1/logs, encoded as http/protobuf (the OpenTelemetry default) or http/json. Any OpenTelemetry Collector and any backend with an OTLP endpoint receives them — for example SigNoz, Grafana (Alloy, Loki, Grafana Cloud), Elastic, Datadog, Honeycomb, New Relic, Dynatrace or a self-hosted OpenTelemetry Collector. There is no vendor-specific code.

Only logs are sent (no traces or metrics), and only over HTTP: for a backend that takes gRPC only, put an OpenTelemetry Collector in front. When the collector does not answer, records are counted as lost and nothing else is affected.

The engine, the MCP server and the apps all read the standard OpenTelemetry variables:

Variable Meaning
OTEL_EXPORTER_OTLP_ENDPOINT the collector’s base address, e.g. http://127.0.0.1:4318
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT or the full …/v1/logs address
OTEL_EXPORTER_OTLP_HEADERS key=value,key2=value2 (values URL-encoded): the API key or Authorization your backend asks for
OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf (default) or http/json
OTEL_SERVICE_NAME the service name (defaults: datanotes, datanotes-mcp, the app’s name)
OTEL_RESOURCE_ATTRIBUTES extra resource attributes, e.g. deployment.environment=home
OTEL_SDK_DISABLED=true or OTEL_LOGS_EXPORTER=none turn export off

The …_LOGS_… variants of headers and protocol are read too. Without an endpoint nothing is sent.

Instead of the variables, the engine can be configured in its settings file (<vault>/.datanotes/service.json); the variables, when set, take precedence:

{
"telemetryEnabled": true,
"telemetryEndpoint": "http://127.0.0.1:4318",
"telemetryServiceName": "datanotes",
"telemetryProtocol": "http/protobuf",
"telemetryHeaders": {}
}

telemetryHeaders holds what the backend asks for (an API key, Authorization: Bearer …). It is kept in the settings file: to keep a key out of it, use OTEL_EXPORTER_OTLP_HEADERS.

The engine also writes its records to <vault>/.datanotes/logs/ (fileLogEnabled, kept logRetentionDays), whatever the collector does.

What the engine logs: every HTTP request and operation (route, duration, outcome, the actor), the tool calls MCP servers report (tool, duration, result size in characters and tokens, client, model, session), refused requests, the semantic index, git sync, and errors.

  • datanotes-mcp: the variables above, or --otlp-endpoint <url>.
  • The apps: the variables above, or an otlp block in the app’s configuration: {"endpoint": "http://127.0.0.1:4318", "headers": {}, "serviceName": "…"}.

The engine, the MCP server and the apps log the same session (the first 16 characters of the MCP session id) and tool for an agent’s call: filter by session in your backend to see the call in the MCP server, the app tool it reached, and the writes it made in the engine.

datanotes/client exports the exporter they use:

import { OtlpLogExporter, otlpFromEnv } from "datanotes/client";
const otlp = otlpFromEnv(process.env, { serviceName: "my-app" });
const log = otlp ? new OtlpLogExporter(otlp) : null;
log?.log("INFO", "imported receipts", { count: 12 });
await log?.close(); // before exit