Learn about OpenTelemetry on Platon
This tutorial introduces OpenTelemetry (OTel) on Platon for teams that already use logs and want to add metrics and traces.
Why OpenTelemetry exists
Logs alone answer "what happened?", but most services also need to answer two questions that logs do not:
- Which one of these 100 requests was the slow one, and why?
- How is this endpoint behaving this week compared to last week?
Traces are for the first question and metrics for the second. Together with logs, they make up the "three signals" of modern observability.
OpenTelemetry is a vendor-neutral standard for producing those signals. It is a CNCF graduated project, and most major languages, frameworks and backends support it natively. Adopting OTel on Platon means your application emits telemetry in the standard shape, and Platon's backends (Loki, Mimir, Tempo) receive it without translation.
The three signals
- Logs are timestamped events, such as "user 42 clicked enroll" or "database query failed with timeout", and they tell you what happened on a specific request.
- Metrics are numerical measurements aggregated over time, like "the enrollment endpoint saw 430 requests in the last minute at a p95 latency of 120 ms". They answer how often, how fast or how big something is, and they are what you graph and alert on.
- A trace is the timeline of a single request as it crosses services, made up of spans, one per operation. Traces answer why a specific request was slow and how a request moved through the system. When the
enrollment-servicecalls theeligibility-checker, which calls thecourse-catalog, one trace records how long each service took and what went wrong.
A trace crossing three services looks like this:
enrollment-service ├──────────────────────────────────────────── 320ms ─┤
eligibility-checker ├──────── 80ms ─┤
course-catalog ├── 40ms ──┤
database ├─ 30ms ─┤
That is four spans, parent-to-child, in one trace. If this request were slow, you could see in the trace which of the four spans took the time, without searching logs.
What OpenTelemetry actually is
As a standard, OTel defines the shape of a span, the structure of a metric and the correlation between a log and the trace it belongs to. Any compliant backend receives telemetry in that shape, so you can switch observability vendors without rewriting your instrumentation.
OTel is also a set of libraries that do most of the work for you. Besides the SDK, it provides auto-instrumentation for common libraries such as your HTTP server, your HTTP client, your database drivers and your logging framework. After you have set up the SDK, which you do once for the whole application, auto-instrumentation usually takes a single line per library. It gives you useful traces, metrics and log correlation without touching any business logic.
On top of the auto-generated signals, you can add custom instrumentation, such as a span around a specific business operation or a counter for a domain-specific event. The example apps in the otel-examples repo each show both what auto-instrumentation gives them and what they have added on top.
How telemetry flows on Platon

- Your application. The OTel SDK serializes telemetry in the OTLP format and sends it over HTTP on port 4318. gRPC also works, but plain HTTP is sufficient. You configure this with two environment variables (
OTEL_EXPORTER_OTLP_ENDPOINTandOTEL_EXPORTER_OTLP_PROTOCOL). See PaaS Full Observability for details. - Alloy. It is an in-cluster collector that receives the telemetry and enriches it with Kubernetes metadata (pod name, namespace). It also assigns the telemetry to the correct tenant based on the namespace your workload runs in, so you do not configure the tenant in your application.
- Loki, Mimir and Tempo. Loki stores the logs, Mimir the metrics and Tempo the traces. All three backends are pre-wired as data sources in your team's Grafana organization.
- Grafana. You read everything at grafana.platon.sikt.no.
If you run on Platon PaaS, you already get Kubernetes metrics (CPU, memory, pod restarts) in Mimir regardless of whether you ship OTel. See Metrics — What you get for free on Platon PaaS.
Minimal instrumentation example
The following is a minimal OTel setup in Python with FastAPI, taken from the course-catalog example app. ... marks lines that are left out. The SDK setup and the auto-instrumentation are in instrumentation.py:
...
from opentelemetry import metrics, trace
...
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.logging import LoggingInstrumentor
...
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
...
def configure_telemetry(app) -> Callable[[], None]:
...
# OpenTelemetry SDK setup — this block enables observability for the process.
# Nothing below would ship telemetry without this.
# Resource attributes (service.name, service.version) are read from
# OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES env vars.
resource = Resource.create({})
# Traces
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(tracer_provider)
# Metrics
meter_provider = MeterProvider(
resource=resource,
metric_readers=[PeriodicExportingMetricReader(OTLPMetricExporter())],
)
metrics.set_meter_provider(meter_provider)
...
# Auto-instrumentation: wires OTel into FastAPI and the stdlib logger.
# Every HTTP request from here on becomes a span — no per-handler code needed.
...
FastAPIInstrumentor.instrument_app(app)
LoggingInstrumentor().instrument()
...
main.py calls configure_telemetry(app) once at start-up and adds the custom instrumentation:
...
from opentelemetry import metrics, trace
...
app = FastAPI(title="Course Catalog", lifespan=lifespan)
shutdown_telemetry = configure_telemetry(app)
...
tracer = trace.get_tracer(__name__)
meter = metrics.get_meter(__name__)
# Custom counter — exposed to Mimir. The course.id attribute creates one
# time-series per course; keep cardinality modest.
course_view_counter = meter.create_counter(
name="course_catalog.course_views",
description="Number of times each course has been viewed",
)
...
@app.get("/courses/{course_id}")
def get_course(course_id: str):
# Custom span — wraps the single-course lookup so DB time shows separately
# in Tempo from FastAPI's auto-span for the request.
with tracer.start_as_current_span("catalog.lookup") as span:
...
span.set_attribute("course.id", course_id)
try:
course = _catalog.get_course(course_id)
except CourseNotFoundError:
...
else:
...
course_view_counter.add(1, {"course.id": course_id})
...
...
return course
...
Everything above the handler is setup written once. The handler itself is ordinary business code with a handful of lines of custom instrumentation. The excerpt shows three of them: a with tracer.start_as_current_span("catalog.lookup") as span wrapping the catalog lookup, a span.set_attribute("course.id", course_id) on that span and a course_view_counter.add(1, {"course.id": course_id}) after a successful lookup. In the repo, the handler also sets a course_catalog.found span attribute and writes a log line, which the excerpt leaves out.
Every example app in the otel-examples repo follows the same shape in its language:
- course-catalog — Python / FastAPI (the code above)
- eligibility-checker — Java / Spring Boot
- enrollment-service — Node.js / Express
- notification-service — Go / net/http
Next steps
- PaaS Full Observability — setting up OTel on PaaS, including the environment variables and the Alloy endpoint.
- Example apps — pick one in your language, read the README, and follow "Reading the code — start here".
- Choosing your signals — when to use a metric, a log, or a span, with common anti-patterns.
For deeper background, the OpenTelemetry documentation is the authoritative reference, and the CNCF OpenTelemetry project page tracks the wider ecosystem.