Skip to content

An architecture diagram drawn from your OpenTelemetry traces, not from memory.

A hand-drawn diagram is right on the day it is drawn. The traces your services send are right every minute, because they record the calls that actually happened. Every span names the service that made it and, for a call out, what it called — which is everything a diagram needs.

Updated

What a trace already says about your architecture

A span carries service.name on its resource, so every span says which service did the work. A client span says what it called: an HTTP host, a db.system such as postgresql or redis, a messaging.system such as kafka with its destination. A server span on the far side closes the pair. Collect those pairs and you have the nodes and edges of a dependency graph.

The OpenTelemetry Collector's servicegraph connector does the pairing for you, and its spanmetrics connector counts every request, error and duration along the way, before any sampling. That is why a diagram built this way also knows how busy each line is.

What you need to send

For the map alone: spans with service.name on the resource. That is all. Any OpenTelemetry SDK in any language does it, and so does auto-instrumentation.

Each further attribute buys something specific. deployment.environment.name keeps staging off the production map. http.route on server spans names entry points by route rather than by raw URL. The messaging conventions on producers and consumers let a queue sit between the services that use it.

  • service.name — the node, and the only thing the first map needs
  • deployment.environment.name — one map per environment
  • db.system, messaging.system, server.address — what each outbound call reached
  • ritele.domain, ritele.layer — optional, to lay the map out the way your organisation thinks

Typing the boxes: services, databases, queues, caches and external APIs

A diagram that draws everything as a rectangle is a graph, not an architecture. Ritele types each component from the OpenTelemetry semantic conventions: a db.system of redis, memcached or valkey is a cache, any other is a database, a messaging system is a queue, and a host your services call but which sends nothing itself is an external API.

The order is fixed: an explicit ritele.component.type on the resource wins over a convention, a convention wins over a heuristic, and anything left is shown as unknown rather than guessed. A type you set by hand in the UI wins over all of them. Callers that cannot be named are drawn and counted, so the map you present is the map you have, gaps included.

Keeping it current without editing it

Ship a new dependency and it appears on the map from its first traces. Stop calling one and its line fades, then goes. Nobody updates anything, so nothing goes stale. A time slider shows the map as it was at an earlier moment.

The map can be exported from the UI as PNG or SVG, or as Mermaid or Structurizr DSL text to keep beside your code. SVG and PNG exports are made in your browser.

Where to send the traces

Ritele runs as one container with an OpenTelemetry Collector inside it. Start it, point an SDK at port 4318, or add one OTLP exporter to a Collector you already run, and open port 8080. The map draws itself from the first traces that arrive; there is nothing to configure first.