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:

Self-host via OTEL export

Export Droid's OpenTelemetry metrics to your own OTLP-compatible collector. Configuration, examples, and troubleshooting, on this page.

Self-host via OTEL export
Exported data reference

Every metric, span, and attribute Droid exports, in both the droid.* and GenAI semantic convention formats.

Exported data reference
Telemetry privacy

Data granularity, message content logging, and what reaches Factory versus your collector.

Telemetry privacy
Factory-hosted Analytics API

Read aggregated usage, cost, and productivity data from Factory's cloud via the Analytics API.

Factory-hosted 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:

  1. 1
    Org-managed settings. A telemetry block in org-managed settings pins the sink for every member of the organization. See Org-managed telemetry settings.
  2. 2
    Environment 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:

JSON
{
  "telemetry": {
    "enabled": true,
    "endpoint": "https://collector.example.com:4318",
    "headers": { "Authorization": "Bearer ${OTEL_COLLECTOR_TOKEN}" },
    "metricExportIntervalMs": 60000,
    "logMessageContent": false,
    "granularity": "user",
    "format": "legacy"
  }
}
enabledboolean

Master switch for the customer telemetry pipeline. true keeps it on even if the machine sets OTEL_CUSTOMER_ENABLED=false; false keeps it off even if the machine sets OTEL_CUSTOMER_ENABLED=true. Omit it to fall back to OTEL_CUSTOMER_ENABLED. In airgapped deployments the pipeline runs only when you have configured a collector of your own; see Airgapped deployments.

endpointstring

Base URL of your OTLP HTTP collector, taking precedence over OTEL_TELEMETRY_ENDPOINT. Must be an http/https URL 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 overrides OTEL_TELEMETRY_ENDPOINT on every machine, use a ${VAR_NAME} reference if machines legitimately need to point at different collectors.

headersobject

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

metricExportIntervalMsinteger

Metric flush interval in milliseconds, taking precedence over OTEL_METRIC_EXPORT_INTERVAL. Capped at 2147483647.

logMessageContentboolean

Whether message content is exported. See Message content logging.

granularity"user" | "aggregate"

Whether exported data carries per-individual identity. Defaults to user. Set aggregate where 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 telemetry block in project, folder, or user settings is ignored with a warning, so a checked-in repository config or a developer's own settings.json cannot 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.
Warning

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.

Note

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.

Bash
export OTEL_TELEMETRY_ENDPOINT="https://your-collector.example.com:4318"
export OTEL_TELEMETRY_HEADERS="Authorization=Bearer <your-token>"
VariableRequiredDescription
OTEL_TELEMETRY_ENDPOINTYesBase 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_HEADERSNoComma-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_CONTENTNoSet 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_INTERVALNoMetric flush interval in milliseconds. Defaults to 60000.
OTEL_CUSTOMER_ENABLEDNoSet 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_FORMATNolegacy or genai. Applies only where the organization has left telemetry.format unset. Anything unrecognized resolves to legacy. See Export formats.
OTEL_TELEMETRY_GRANULARITYNouser or aggregate. Resolved most-restrictive-wins against telemetry.granularity, not by precedence. See Data granularity.
OTEL_RESOURCE_ATTRIBUTESNoStandard OTEL key=value,key=value attributes, applied to your collector only (see Resource attributes).
DROID_PARENT_SESSION_IDNoAdds 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

Bash
export OTEL_TELEMETRY_ENDPOINT="https://collector.example.com:4318"

Datadog via OTLP ingestion

Bash
export OTEL_TELEMETRY_ENDPOINT="http://localhost:4318"

Datadog direct OTLP intake

Bash
export OTEL_TELEMETRY_ENDPOINT="https://otlp.datadoghq.com"
export OTEL_TELEMETRY_HEADERS="dd-api-key=<your-datadog-api-key>"

New Relic

Bash
export OTEL_TELEMETRY_ENDPOINT="https://otlp.nr-data.net:4318"
export OTEL_TELEMETRY_HEADERS="api-key=<your-new-relic-license-key>"

Honeycomb

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