# Telemetry

> What Fixter collects and why: logs, traces, and metrics, what each one answers, what you get without writing code, and what happens to it once it arrives.

Fixter takes OpenTelemetry over OTLP. There is no proprietary agent and no Fixter SDK:
your services send the same telemetry they would send anywhere else, to
`https://ingest.fixter.dev`.

Three signals arrive, plus LLM spans where your application calls a model API. They are
not interchangeable, and what you leave out you lose later.

## Logs

The text your code already writes. Every entry keeps its timestamp, service, severity,
and whatever attributes you attached, and all of it is searchable seconds after it
arrives.

**Send them as JSON.** Plain-text logs arrive and are stored, but their contents stay
opaque: you get a string to grep instead of fields to filter on, and filtering is the
point. A log bridge from your existing logging library does this, or you configure your
logger to write JSON.

## Traces

A trace is one request's path across your services, with timing and outcome on every
hop. Each hop is a span.

**This is what makes a failure attributable.** With logs alone you learn that checkout
is returning 500s. With spans you learn that checkout is fine and the payment provider
it calls is timing out. When an investigation looks for a root cause it walks the
trace's service-dependency edges, because the component that broke is usually one or two
hops downstream of the service that alerted.

**Some alerts cannot exist without spans.** Alert rules read one source, and the
measures available differ per source. Error rate, request count, and both SLO budget
burn rates are computed from spans only. A service that sends logs and no traces cannot
have an error-rate rule or a burn-rate rule at all, no matter how good its logging is.

## Metrics

Numbers sampled over time: counters, gauges, and histograms from your application and
runtime.

**Metrics catch what logs structurally cannot.** Thread-pool and connection-pool
exhaustion, memory pressure, CPU saturation, queue depth. A service under that kind of
pressure often logs nothing unusual at all until it fails, so an investigation into a
saturation symptom goes to metrics first.

## LLM spans

If your application calls OpenAI, Anthropic, LangChain or similar, those calls are
instrumented as spans like any other outbound call, so model latency and failures sit in
the same trace as the request that triggered them. There is no separate LLM product
surface; it is the trace you already have.

## What you get without writing code

Auto-instrumentation covers the usual sources, so you are not writing spans by hand:

- your **web framework**, so every inbound request is a trace
- your **database driver**, so queries appear as spans with their timing
- **outbound HTTP calls**, which is how a failing dependency becomes visible

On Kubernetes the Fixter collector chart adds pod logs and cluster metrics without
touching your application at all. It parses JSON pod logs for severity and trace
correlation, and routes common database logs (Postgres, MySQL, Kafka and others).

**What is not included:** host-level telemetry, meaning the machine's CPU, memory, disk,
and system logs, unless you run that Kubernetes collector chart. These are your
application's signals, not your infrastructure's.

**If you sample, sample traces only.** Log volume is managed with log levels, not with
sampling.

## What happens once it arrives

Your telemetry lands in one of three tables, `logs`, `spans`, and `metrics`, and is
queryable seconds later. Nothing has to be indexed, registered, or declared first.

- **Your own attributes become fields**, by name, with no schema step. They appear in
  the filter bar's suggestions and in SQL as soon as one record carrying them arrives.
- **Traces stitch themselves together** from the `trace_id` your instrumentation already
  sets, which is what lets any log open the whole request it belongs to.
- **Severity is whatever your logging library calls it.** Fixter does not normalise it
  to a fixed set, so filter on a set of values rather than one. The
  [data model](/reference/data-model/#logs) has the detail and the trap.
- **Retention is two years** for all three signals.

Everything reads that same data afterwards: the app, your agent over MCP, alert rules,
and investigations. There is no second copy to keep in sync.

## Turning it on

- **With Claude Code**, the [plugin](/setup/claude-code-plugin/) detects your stack and
  writes the instrumentation for you. This is the [Onboarding](/getting-started/onboarding/).
- **Without it**, set four environment variables or add one exporter block to your
  Collector: [Send data with OpenTelemetry](/setup/opentelemetry/).

## If something is missing

- **A field you send is not queryable.** Attributes become fields only once a record
  carrying them has arrived. Exercise the code path that emits it, then filter again.
- **A log has no trace view.** That record has no `trace_id`, so the service is
  instrumented for logging but not for tracing.
- **You cannot find the alert measure you wanted.** Check the rule's source. Error rate
  and the burn rates only exist on spans, and metric rules are further limited by the
  kind of metric you sent. See [Alerts](/using-fixter/alerting/#what-you-fill-in).
- **Logs arrive but you cannot filter on their contents.** They are plain text rather
  than JSON.

Still stuck? Email [info@fixter.dev](mailto:info@fixter.dev) and one of our founders will help you fix your problem asap.

## Related

- [Send data with OpenTelemetry](/setup/opentelemetry/): the wiring, per language
- [Data model](/reference/data-model/): the exact fields on a log, span, and metric
- [Debugging](/using-fixter/debugging/): querying all of this once it is arriving