Documentation menu
Reference
API reference
Docs · markdown

Any language (OTLP)

LoopOps ingest is standard OTLP/HTTP — JSON or protobuf, plain or gzip/deflate-compressed — on all three pillars:

  • POST /v1/traces — spans (ExportTraceServiceRequest)
  • POST /v1/logs — logs and events (ExportLogsServiceRequest)
  • POST /v1/metrics — metrics (ExportMetricsServiceRequest)

Any official OpenTelemetry SDK works: configure its OTLP/HTTP exporter with the LoopOps endpoint and your producer key.

Python example:

from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

OTLPSpanExporter(
    endpoint="https://loopops.dev/v1/traces",
    headers={"authorization": f"Bearer {os.environ['LOOPOPS_INGEST_KEY']}"},
)

Or raw HTTP from anywhere:

POST /v1/traces
Authorization: Bearer <producer_key>
Content-Type: application/json

{ "resourceSpans": [{
  "resource": { "attributes": [
    { "key": "service.name", "value": { "stringValue": "checkout-api" } },
    { "key": "service.version", "value": { "stringValue": "1.4.2" } }
  ]},
  "scopeSpans": [{ "spans": [{
    "traceId": "<32 hex>", "spanId": "<16 hex>",
    "name": "charge_card",
    "startTimeUnixNano": "1781856000000000000",
    "status": { "code": 2, "message": "card declined" }
  }] }]
}] }

Cloudflare Workers

Start with Cloudflare's automatic tracing (open beta since 2025-11) and a Workers Observability OTLP destination — created in the Cloudflare dashboard under Workers Observability → Destinations, type Traces (there is no API for this step). Put authorization: Bearer <producer_key> in the destination's custom headers, point it at https://ingest.loopops.dev/v1/traces, and merge its name into Wrangler. If the human approves application-log capture, create a second destination of type Logs at https://ingest.loopops.dev/v1/logs with the same header and merge that name too:

{
  "observability": {
    "traces": {
      "enabled": true,
      "destinations": ["<existing-destination>", "loopops-<project_id>-traces"]
    },
    "logs": {
      "enabled": true,
      "destinations": ["<existing-log-destination>", "loopops-<project_id>-logs"]
    }
  }
}

Cloudflare emits handler, outbound fetch, and binding spans (KV, R2, D1, Durable Objects, Queues, AI) without an app-code wrapper — binding spans carry cloudflare.binding.type/cloudflare.binding.name, which the system map reads as dependency edges. Durable Object and service-binding subrequests arrive as one unified trace (since 2026-05). Span and attribute names are beta and may shift. Use the direct OTLP JSON example above only when the destination is unavailable or for narrow business/model/tool semantics the platform cannot infer. Logs remain opt-in because they include console.*, exceptions, and system-generated logs. Never point a Worker's destination at an OTLP receiver hosted by that same Worker; the export invocations become new telemetry and recurse. LoopOps itself terminates OTLP on a dedicated receiver Worker with no export destinations. Append/de-duplicate each approved destination and preserve existing head_sampling_rate and persist settings; delivery can take a few minutes.

Pitfall (verified 2026-07): if the Worker builds with @cloudflare/vite-plugin (React Router, Remix, Astro), the plugin regenerates the deploy config and flattens observability to { enabled } — silently dropping the traces block, so tracing never activates. Re-apply the full observability object onto the BUILT wrangler.json in a post-build step, and verify the deployed config actually carries traces.destinations before trusting the wiring.

Metrics — the third pillar

POST /v1/metrics accepts OTLP/HTTP metrics (JSON or protobuf) with the same key and the same gates as traces: gauges, sums, histograms, exponential histograms, and summaries. Every data point becomes a telemetry record (signal_type: metric.<name>) with the numeric payload projected flat — value, count, sum, min/max, buckets, quantiles — and exemplars kept, so a slow histogram tail can be drilled to the trace that fell in it. Metrics are the aggregate lane that survives trace sampling: token, latency, and error-rate baselines land here when spans are sampled.

What matters on the wire

  • service.name, service.version, deployment.environment.name

on the resource — workload identity, verify/regression anchoring, environment scoping.

  • Exceptions via span events (recordException semantics) are extracted —

type, message, stacktrace.

  • Span events and links are stored (bounded) — in-span timelines and

cross-trace causality (retry chains, fan-out provenance) survive ingest.

  • Replayable captures: carry gen_ai.* semconv (or loopops.input /

loopops.output / loopops.unit) and failures become replayable evidence.

  • Distributed trace context (W3C traceparent) is preserved — browser spans,

service spans, and model calls correlate into one journey by trace_id.

Not supported (say it plainly)

gRPC OTLP is not accepted; use OTLP/HTTP (JSON or protobuf) on all three pillars — /v1/traces, /v1/logs, /v1/metrics.

Next: Limits, caps & ingest health →