I added OpenTelemetry to my Moodle 5.2, and the traces go to a Jaeger at home

I added OpenTelemetry to my Moodle 5.2, and the traces go to a Jaeger at home
ContenidoContents

Moodle 5.2 shipped a feature that went fairly unnoticed and that I was quietly excited about: built-in support for OpenTelemetry. Translated: Moodle can emit traces of what it does on each request —routing, the controller, every cron task, every webservice call— so you can see, down to the millisecond, where the time goes.

I’d had a nagging itch about not having good observability for my Moodle. I have cookieless visit analytics and a monitoring dashboard that tells me if something is down, but nothing that told me why a given page is slow. So I took advantage of the new support. And, true to the house style, with the data staying at home: no shipping telemetry off to a SaaS.

Here’s what it is, how I set it up, and —above all— the scares, which is where the learning happens.

What OpenTelemetry actually is (plain words)

OpenTelemetry (OTel) is an open observability standard. The piece I care about here are traces: each web request generates a root span, and child spans hang off it for every relevant operation (routing, controller/action, task processing, webservices…). The result is a waterfall where you see what called what and how long each thing took.

This isn’t error monitoring or “up / down”. It’s the next question: when something is slow, which specific part is eating the time?

The decision: Jaeger locally, not a SaaS

OTel only generates the traces; you need a backend to collect and view them (Jaeger, Grafana Tempo, SigNoz, Honeycomb, Datadog…). The last two are paid cloud services. I ruled them out for the usual reason: I don’t want to ship my site’s performance data off-site. I went with Jaeger, which spins up in a container and keeps everything local. Consistent with the rest of my self-hosted setup.

How the setup works

Three pieces, and none of them in Moodle’s UI (it’s all console and configuration):

  1. The PHP extension. OTel needs a C extension installed via PECL:
    1pecl install opentelemetry
    
  2. The Composer packages, in the root of the Moodle install:
    1composer require moodlehq/moodle-package-otel open-telemetry/exporter-otlp
    
    The first is Moodle HQ’s official integration (it registers the auto-instrumentation hooks); the second is the exporter that speaks the OTLP protocol.
  3. The configuration, which in OTel is done only via environment variables or php.ini —there’s no settings screen—. The essentials: enable autoload, name the service, say “export via OTLP to this address” and set sampling (in production you don’t want to trace 100% of requests):
    1OTEL_PHP_AUTOLOAD_ENABLED=true
    2OTEL_TRACES_EXPORTER=otlp
    3OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
    4OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
    5OTEL_TRACES_SAMPLER=parentbased_traceidratio
    6OTEL_TRACES_SAMPLER_ARG=0.2
    

And Jaeger, in a container, listening for OTLP and serving its web UI. With that, every Moodle request that gets sampled shows up in Jaeger within seconds.

As a finishing touch, I didn’t want yet another URL to remember: I embedded the Jaeger UI as a tab inside my monitoring dashboard, next to visits and alerts. An iframe and an Apache reverse proxy, and now it’s all in one place.

The scares that teach you

This is where “install four things” turns into a debugging afternoon.

Scare 1: I reloaded Apache, but PHP didn’t notice. I installed the extension, reloaded Apache, generated traffic… and the log filled with warnings saying “the opentelemetry extension must be loaded”. On top of that, traces showed up intermittently. The reason: my PHP runs under php-fpm, which is a separate service from Apache. Reloading Apache doesn’t reload php-fpm; its workers were still running the old build, without the extension. The intermittent traces were from the few workers that had recycled themselves. The fix: actually reload php-fpm (a kill -USR2 to the master process does a graceful reload). Moral: when you install a PHP extension, remember php-fpm is its own service.

Scare 2: I didn’t want to instrument ALL my sites. On my server, several sites share the same php-fpm pool. If I enabled OTel in php.ini, it would swallow all the PHP on the server, not just Moodle. I wanted it scoped to a single install. The elegant way out: define the environment variables per virtual host in Apache (SetEnv), not globally. The nice bit is why it works: those variables travel to PHP as FastCGI parameters and land in $_SERVER, and the OpenTelemetry SDK reads its configuration from $_SERVER too, not only from getenv(). So even with a shared pool, the variables only exist on that vhost’s requests. Surgical instrumentation, without touching the neighbours.

Scare 3: the observability UI has no password. Jaeger, out of the box, ships with no login. And its UI shows your traces: internal URLs, timings, task names… information you don’t want to hand out. Exposing a tool like that to the internet as-is is a textbook mistake. I put it behind a password at the web-server level, with the twist that from my private network it doesn’t ask. The general rule: an observability UI with no authentication is never published without putting something in front of it.

What it’s for, in practice

When a Moodle page is slow, I no longer guess. I find its trace in Jaeger, open the span waterfall and see, bar by bar, which operation eats the time: a heavy controller, task processing, an external call that drags. Jaeger also lets you compare two traces side by side, so I can put a fast request next to a slow one and see exactly how they differ.

I don’t need it every day. But the day something is off, the difference between reading a span waterfall and staring at the ceiling is enormous.

Looking ahead: what if I add Grafana?

Jaeger answers a very specific question: “why was this request slow?”. But it stops there, one trace at a time. The natural evolution —and the next thing I want to explore— is to add Grafana.

It helps to understand that Jaeger and Grafana don’t compete; they play different positions. Jaeger stores and shows traces: the fine-grained detail of a request. Grafana is a visualization dashboard that brings several sources together in one place; it doesn’t store data itself, it connects to other backends (and one of them can be Jaeger). In one sentence: Jaeger shows me a tree; Grafana would show me the forest.

What would I gain with Grafana on top?

  1. Metrics over time. OTel doesn’t only emit traces, it also emits metrics (which I currently have disabled). With a metrics store —Prometheus— and Grafana in front, I’d see trends: average latency and its percentiles (p95, p99), requests per minute, error rate… day by day. A trace is a snapshot; this would be the film.
  2. Correlation across the three pillars. The observability ideal is jumping from a metric that spikes → to the traces at that moment → to the logs of that request, without switching tools. Grafana is what stitches those three layers together.
  3. Alerting. Grafana can warn me when something degrades (“p95 goes above X”, “error rate climbing”). Jaeger, on its own, doesn’t alert.
  4. My own dashboards. Instead of Jaeger’s trace search, custom panels: the slowest endpoints, cron health, whatever I decide.

The price? More pieces to maintain. Grafana, a metrics store, maybe swapping Jaeger for Grafana Tempo (its traces equivalent, which integrates out of the box) and, if I get into logs, Loki. It’s the well-known “LGTM” stack (Loki, Grafana, Tempo, Mimir). For a personal Moodle like mine, Jaeger alone already does the job; Grafana starts to make sense when you want to go from “putting out a specific fire” to “watching the trend and being warned before it catches.”

So I’m noting it down as the next step: keep Jaeger for the detail, and put Grafana on top for the trends, the alerts, and having the three pillars —traces, metrics and logs— stitched into a single dashboard. When I do it, I’ll write it up.

The takeaway

Three things I keep. First: self-hosted observability is within reach. One container and three packages, and you get professional-grade traces without paying a subscription or shipping your data to anyone. Moodle 5.2 having it built in helps a lot.

Second: the real work isn’t in installing, it’s in deploying well. Reloading the right service, scoping the instrumentation to what you want, adding authentication where the software doesn’t bring any. That’s where the afternoon goes, and that’s where the judgement is.

And third, the ever-present one when you self-host: every new piece is a new surface to look after. A tool that’s “just for looking at graphs” still gets exposed, still gets protected, and still needs maintaining. Worth it, but with eyes open.

CompartirShare