Skip to Content
Tares

How do I read Postgres tables next to OpenTelemetry log records when an AI SRE investigates?

Tares puts Postgres rows and OpenTelemetry log records on one timeline when both sources carry a primary label with the same name and the same value, such as service. One read of that value then returns the rows and the log lines together, so an AI SRE sees the application data next to the logs without joining anything itself. You set this up with an otlp source and a postgres source that both mark service as primary.

How it works

Tares stores everything as events with labels. A label named in both sources is what lines them up.

  • OpenTelemetry side. The OpenTelemetry connector receives OTLP/HTTP logs, traces and metrics. Resource attributes such as service.name become labels, so the service label is the natural key. One otlp source ingests every service.
  • Postgres side. The Postgres connector polls a table incrementally and stores one event per new or changed row. A cursor column that only grows, such as updated_at, tracks progress. A primary label turns a column into the entity key.
  • The join. Mark service primary on the otlp source and mark a label named service primary on the postgres source. The postgres label reads a table column that holds the same service name the logs carry. When a log line and a row both carry checkout, they sit under the same entity key on one timeline.
  • A second key. Keep tenant_id as a plain, non-primary label on the postgres source when you also want to filter rows by customer. It adds a field to read, and it does not change the join.

The match is on the value. If the logs say checkout and the table column says checkout-api, the two do not meet, so the column must hold the name the logs carry.

This is the same idea the AI SRE guide describes: events from different systems correlate when they share a label value.

Set it up

  1. Create the otlp source. There is no required config. Add the service label and mark it primary. In the UI that is Sources, Add source, otlp. You can also ask a connected agent to create an OTLP source keyed by service.
- name: otel connector: otlp config: labels: - { name: service, field: service, primary: true }
  1. Create the postgres source. Enter the connection URL, run Discover to list the tables, and pick one. List the column that holds the service name in columns, and give it a service label marked primary. The dsn is stored as a secret.
- name: orders connector: postgres poll: 10s config: dsn: postgresql://user:pass@host:5432/appdb table: orders cursor_column: updated_at columns: id,service,tenant_id,status,amount,updated_at # empty = every column labels: - { name: service, field: service, primary: true } - { name: tenant_id, field: tenant_id } - { name: status, field: status }
  1. Point your exporter at Tares. Set the OTLP endpoint on the app or the OpenTelemetry Collector.
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8787 # OTLP/HTTP → /v1/{logs,traces,metrics} # or gRPC (otlp-grpc extra): OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

The sender uses a Bearer Authorization header holding an ingest-scoped API key, needed only when Tares has auth on.

  1. Check both sides. On the otlp source, Recent events shows log records as they arrive and Explore lists services under the service key. On the postgres source, Recent events shows rows on the next poll and Fields reports each column’s coverage. Use Fields to confirm the service column is present on nearly every row.

  2. Check the values match. Compare a service name in the otlp Explore list with the values in the postgres service column. They must be identical strings.

What you get

  • One read of a service value returns its log lines and its table rows in time order, so the AI SRE sees an error log and the order rows that changed around it together.
  • The Postgres rows are your application’s source-of-truth data on the same timeline as the telemetry, not a separate lookup.
  • A single otlp source covers every service you instrument, with no per-service setup.
  • Rows are read incrementally. Only new or changed rows become events, tracked by the cursor column.
  • The extra tenant_id label lets you narrow rows to one customer while the service label keeps the join.
  • The postgres dsn stays a secret: it is redacted in the API and omitted from catalog exports.

When something else fits

  • If the data you want next to the logs comes from a JSON API rather than a table, the HTTP API (poll) connector stores one event per item and joins the same way, through a shared primary label value.
  • If your log lines and rows share no value at all, there is nothing to join on. Add a shared identifier to one side first, such as a service column in the table.

Still stuck? Get help

Last updated on