Telemetry & Analytics
Measure Droid adoption, activity, and cost by exporting OpenTelemetry metrics to your own collector or reading Factory's hosted Analytics API.
Enterprise adoption requires more than a good developer experience. You need to understand who is using Droid, on what, and at what cost. There are two complementary ways to measure that, and you can use either or both.
This page covers setting up the self-hosted export. The rest of the story lives on its own pages:
Export Droid's OpenTelemetry metrics to your own OTLP-compatible collector. Configuration, examples, and troubleshooting, on this page.
Every metric, span, and attribute Droid exports, in both the droid.* and GenAI semantic convention formats.
Data granularity, message content logging, and what reaches Factory versus your collector.
Read aggregated usage, cost, and productivity data from Factory's cloud via the Analytics API.
Customer-side export is metrics only by default; message content can optionally be exported as trace spans to your own endpoint (see Message content logging). Factory's internal tracing is what powers the hosted Analytics API; it is never sent to your collectors.
Self-hosted OTEL metrics export
Droid can export OpenTelemetry (OTEL) metrics to your own OTLP-compatible collector, giving you visibility into Droid activity within your existing observability stack. In connected deployments, metrics are sent to both Factory's collector and yours in the same export cycle. If your collector is unreachable, Factory's own export is not affected.
Spans are yours alone. Factory's collector never receives a span, in any configuration. Droid builds a trace pipeline only where you have configured a collector of your own that is distinct from Factory's, so with no endpoint set no span is recorded anywhere.
Configuration
There are two places the sink can be configured, and they take precedence in this order:
- 1Org-managed settings. A
telemetryblock in org-managed settings pins the sink for every member of the organization. See Org-managed telemetry settings. - 2Environment variables on the developer's machine, used for any value the organization has not pinned. See Machine environment variables.
Org-managed telemetry settings
Environment variables live on the developer's machine, which makes them advisory: anyone can export OTEL_TELEMETRY_ENDPOINT=... to redirect the stream or switch it off. Organizations that need the sink enforced can pin it centrally with a telemetry block in org-managed settings, edited under Settings → Enterprise Controls → Raw Configuration:
{
"telemetry": {
"enabled": true,
"endpoint": "https://collector.example.com:4318",
"headers": { "Authorization": "Bearer ${OTEL_COLLECTOR_TOKEN}" },
"metricExportIntervalMs": 60000,
"logMessageContent": false,
"granularity": "user",
"format": "legacy"
}
}enabledbooleanMaster switch for the customer telemetry pipeline.
truekeeps it on even if the machine setsOTEL_CUSTOMER_ENABLED=false;falsekeeps it off even if the machine setsOTEL_CUSTOMER_ENABLED=true. Omit it to fall back toOTEL_CUSTOMER_ENABLED. In airgapped deployments the pipeline runs only when you have configured a collector of your own; see Airgapped deployments.endpointstringBase URL of your OTLP HTTP collector, taking precedence over
OTEL_TELEMETRY_ENDPOINT. Must be anhttp/httpsURL with no query string or fragment, or a${VAR_NAME}reference standing in for the whole URL. Invalid values are rejected when you save, not on member machines. Because it overridesOTEL_TELEMETRY_ENDPOINTon every machine, use a${VAR_NAME}reference if machines legitimately need to point at different collectors.headersobjectStatic OTLP headers sent on every export, as a map of header name to value. Values may contain
${VAR_NAME}references. Headers are transport-only — used to authenticate and route the HTTP export — and are never attached to exported spans or metrics as attributes.metricExportIntervalMsintegerMetric flush interval in milliseconds, taking precedence over
OTEL_METRIC_EXPORT_INTERVAL. Capped at2147483647.logMessageContentbooleanWhether message content is exported. See Message content logging.
granularity"user" | "aggregate"Whether exported data carries per-individual identity. Defaults to
user. Setaggregatewhere a works council agreement or a jurisdictional rule forbids per-individual analytics. See Data granularity.format"legacy" | "genai"Which convention your collector receives. Defaults to
legacy. See Export formats. Factory's own collector is unaffected by this field.
Three properties of the block are worth calling out:
- It is read from the organization level only. A
telemetryblock in project, folder, or user settings is ignored with a warning, so a checked-in repository config or a developer's ownsettings.jsoncannot redirect the stream. - It is distributed to every member, not just Managers and Owners, because the CLI reads it on each member's machine. Treat every value in it as readable by the whole organization.
- It configures your sink only. Factory's own collector is not affected by it.
Prefer a ${VAR_NAME} reference over a literal collector credential in headers, so the secret is distributed to machines out of band (MDM, a secret manager) and never stored in Factory's control plane.
If a referenced variable is not set on a machine, Droid disables the customer sink for that session and logs an error naming the missing variable, rather than shipping the literal ${VAR_NAME} to your collector.
When endpoint is set in the block, the OTEL_* header environment variables never apply to it, because a credential sitting in a developer's shell for unrelated tooling must not be shipped to the organization's collector. Omitting headers therefore means the org sink is called with none; put machine-local values in ${VAR_NAME} references inside headers instead.
The block is also read once, at startup. A session that begins while logged out has no org settings to read and runs on the OTEL_* environment for the life of that process.
Machine environment variables
Set these before launching Droid. Each one configures the sink only where the organization has left that value unpinned; a telemetry block in org-managed settings overrides the matching variable.
export OTEL_TELEMETRY_ENDPOINT="https://your-collector.example.com:4318"
export OTEL_TELEMETRY_HEADERS="Authorization=Bearer <your-token>"| Variable | Required | Description |
|---|---|---|
OTEL_TELEMETRY_ENDPOINT | Yes | Base URL of your OTLP HTTP collector. Metrics are sent to {endpoint}/v1/metrics. Falls back to OTEL_EXPORTER_OTLP_ENDPOINT. Not required when the organization pins telemetry.endpoint. |
OTEL_TELEMETRY_HEADERS | No | Comma-separated key=value pairs sent as HTTP headers on every export. Values may contain = (e.g. base64 tokens). Falls back to OTEL_EXPORTER_OTLP_HEADERS. Never applied to an org-pinned endpoint. |
OTEL_LOG_MESSAGE_CONTENT | No | Set to true or 1 to export message content (user/assistant messages, tool input/results) as trace spans to OTEL_TELEMETRY_ENDPOINT. Content is dropped if no customer endpoint is set. |
OTEL_METRIC_EXPORT_INTERVAL | No | Metric flush interval in milliseconds. Defaults to 60000. |
OTEL_CUSTOMER_ENABLED | No | Set to false to opt this machine out of customer telemetry entirely (useful in tests and local development). This is the same pipeline pinned by telemetry.enabled, not a separate stream. |
OTEL_TELEMETRY_FORMAT | No | legacy or genai. Applies only where the organization has left telemetry.format unset. Anything unrecognized resolves to legacy. See Export formats. |
OTEL_TELEMETRY_GRANULARITY | No | user or aggregate. Resolved most-restrictive-wins against telemetry.granularity, not by precedence. See Data granularity. |
OTEL_RESOURCE_ATTRIBUTES | No | Standard OTEL key=value,key=value attributes, applied to your collector only (see Resource attributes). |
DROID_PARENT_SESSION_ID | No | Adds parent_session.id to metrics and content spans, for linking headless/CI sub-sessions to a parent. |
How it works
- Once an endpoint is resolved, metrics are sent to it in the same export cycle via a fan-out exporter: no extra timers, no duplication of metric readers.
- Failures to your collector do not affect Factory's own export. Each endpoint is isolated.
- Metrics use delta temporality: each export contains only new values since the last flush (60-second intervals by default). Sum the deltas; do not read a last value.
For what arrives on the wire, including the metric catalogs and every attribute, see the exported data reference.
Example configurations
Generic OTEL collector / Grafana Alloy
export OTEL_TELEMETRY_ENDPOINT="https://collector.example.com:4318"Datadog via OTLP ingestion
export OTEL_TELEMETRY_ENDPOINT="http://localhost:4318"Datadog direct OTLP intake
export OTEL_TELEMETRY_ENDPOINT="https://otlp.datadoghq.com"
export OTEL_TELEMETRY_HEADERS="dd-api-key=<your-datadog-api-key>"New Relic
export OTEL_TELEMETRY_ENDPOINT="https://otlp.nr-data.net:4318"
export OTEL_TELEMETRY_HEADERS="api-key=<your-new-relic-license-key>"Honeycomb
export OTEL_TELEMETRY_ENDPOINT="https://api.honeycomb.io"
export OTEL_TELEMETRY_HEADERS="x-honeycomb-team=<your-api-key>,x-honeycomb-dataset=droid-metrics"Troubleshooting
Airgapped deployments
Airgapped deployments export to your collector only. Droid builds no Factory-bound leg at all, so nothing is sent outward and nothing is queued waiting for a network that is not there.
This makes telemetry.endpoint (or OTEL_TELEMETRY_ENDPOINT) the single telemetry surface in an airgap, and the only one. Without a usable collector of your own, the pipeline does not run: there is no Factory fallback to degrade to. An endpoint that Droid rejects, such as one with an unresolved ${VAR_NAME} reference or a query string, counts as no collector for this purpose.
The telemetry.enabled switch still applies on top, and airgapped deployments do not use Factory-hosted analytics.
Factory-hosted Analytics API
In cloud-managed deployments, Factory provides a hosted analytics view for platform and leadership teams. It is backed by Factory's internal tracing, which feeds an aggregation pipeline; this internal tracing is separate from the metrics you export above and is never sent to your collectors.
The Analytics API exposes aggregated, org-level data, including:
- Adoption metrics by org, team, and user.
- Model usage and performance trends.
- Token consumption and cost estimates for LLM usage.
- Tool usage and productivity signals.
Use the Analytics API when you want a hosted, aggregated view, particularly for token and cost data, which is not part of the customer OTEL metric set.
Related resources
Every metric, span, and attribute Droid exports, in both formats.
Data granularity, message content logging, and what reaches Factory.
Map telemetry signals to audit trails and regulatory workflows.
The org-managed settings surface that pins the telemetry block.