Observability
Health endpoints
GET /healthz: process liveness, returns{"status":"ok"}when the process is aliveGET /readyz: readiness across PostgreSQL, Redis, and buckets; returns{"status":"ok"}on success or a structured JSON failure body withchecks[]GET /metrics: Prometheus metrics for the main server
Any readiness failure increments vylux_readiness_failures_total{check=...}.
When diagnosing a fresh deployment, check /healthz first, then /readyz, and only then look at application behavior. That separates process startup failures from dependency failures quickly.
Worker metrics
When running worker-only mode, Vylux can expose a separate listener on WORKER_METRICS_PORT for worker metrics and basic health checks.
For the exact defaults and validation rules of WORKER_METRICS_PORT and OTEL_EXPORTER_OTLP_ENDPOINT, see Configuration.
Prometheus metric families
The most useful metric families today are:
| Metric | Meaning |
|---|---|
vylux_http_requests_total | HTTP request count by method, route, and status |
vylux_http_request_duration_seconds | HTTP request latency |
vylux_image_cache_events_total | image cache hits and misses by layer |
vylux_image_results_total | top-level image request outcomes |
vylux_image_errors_total | image failures by stage and status |
vylux_worker_tasks_total | worker task attempts by task type and result |
vylux_worker_task_duration_seconds | worker task latency |
vylux_readiness_failures_total | readiness failures by dependency check |
vylux_queue_tasks | queue depth by queue and state |
vylux_queue_metrics_sync_failures_total | failures while refreshing queue-depth metrics |
Tracing
OpenTelemetry tracing is integrated across HTTP requests and queued media tasks. The system propagates trace context into async workflows so job execution is visible as part of the same trace tree.
Relevant headers
traceparenttracestateX-Trace-ID
X-Trace-ID is a convenience header for manual debugging and log correlation. The authoritative context still comes from the W3C trace headers.
Enabling export
Set:
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
or another OTLP HTTP endpoint. If the variable is empty, spans are still created locally but are not exported.
Local Jaeger validation
If you want to inspect end-to-end traces from the HTTP request into worker execution, use a minimal collector plus Jaeger stack. The key point is that the collector must load a real config file and forward OTLP traces to Jaeger; simply exposing 4317 and 4318 is not enough.
docker-compose example
services:
jaeger:
image: jaegertracing/all-in-one:1.76.0
tmpfs:
- /tmp
restart: unless-stopped
environment:
COLLECTOR_OTLP_ENABLED: true
ports:
- 16686:16686
otel-collector:
image: otel/opentelemetry-collector-contrib:0.148.0
volumes:
- ./otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro
command: ["--config=/etc/otelcol-contrib/config.yaml"]
restart: unless-stopped
depends_on:
- jaeger
ports:
- 4317:4317
- 4318:4318
- 13133:13133
The collector must mount a config file into the container. A practical local filename is otel-collector.yml at the repo root.
otel-collector.yml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
debug:
verbosity: normal
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug, otlp/jaeger]
What each block does
receivers.otlpopens OTLP gRPC on4317and OTLP HTTP on4318processors.batchgroups spans before export so the collector does not forward them one-by-oneexporters.debugprints received spans to the collector logs, which is the fastest way to verify that Vylux is actually exportingexporters.otlp/jaegerforwards traces to Jaeger over OTLP gRPC on the internal Docker networkextensions.health_checkexposes the collector health endpoint on13133service.pipelines.traceswires the trace receiver, processor, and exporters together; without this pipeline, Jaeger will stay empty even though the collector container is up
Choosing the right OTLP endpoint
Use the OTLP endpoint that matches where the Vylux process itself runs:
- if Vylux runs inside Docker Compose, use
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 - if Vylux runs on the host while collector runs in Docker, use
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
That is why a typical setup uses:
.envfor container-to-container addresses such ashttp://otel-collector:4318.env.localfor host-to-container addresses such ashttp://localhost:4318
Validation flow
- Start Jaeger and the collector with
otel-collector.ymlmounted into the collector container. - Set
OTEL_EXPORTER_OTLP_ENDPOINTto the correct OTLP HTTP address for both server and worker. - Submit a
POST /api/audio/jobsorPOST /api/video/jobsrequest, or hit another traced HTTP route. - Capture the
X-Trace-IDfrom the HTTP response headers or logs. - Check the collector logs first. The
debugexporter should print spans. - Open
http://localhost:16686and search for servicevyluxor paste the trace ID directly.
If you run the collector through Docker Compose, mount the config file into the container and start it with --config=.... Exposing ports alone is not enough to forward traces to Jaeger.
Example local verification commands
docker compose -f docker-compose.dev.yml up -d
docker compose -f docker-compose.dev.yml logs -f otel-collector
curl -i http://localhost:3100/healthz
If traces still do not appear in Jaeger after a media request:
- confirm Vylux actually loaded a non-empty
OTEL_EXPORTER_OTLP_ENDPOINT - confirm the collector logs show spans through the
debugexporter - confirm the collector config file was mounted to
/etc/otelcol-contrib/config.yaml - confirm the collector is started with
--config=/etc/otelcol-contrib/config.yaml - confirm Vylux is using the correct host name for where it runs:
otel-collectorinside Compose,localhoston the host - confirm you are opening the Jaeger UI on
16686, not trying to send traces there
What to watch
- readiness failures
- queue depth and task latency
- image cache behavior
- media job success and failure trends
Troubleshooting hints
localhost health checks fail from the hostIf curl http://localhost:<PORT>/healthz returns connection refused, the container port is usually not published to the host, or you are testing the wrong host port.
502If cloudflared logs show dial tcp [::1]:3000 or 127.0.0.1:3000, the tunnel origin is pointed at localhost inside the tunnel container. Use http://app:<PORT> instead.
/healthz is green but /readyz is redThis usually means the Vylux process is alive but PostgreSQL, Redis, or bucket reachability is broken.
Confirm that Vylux is actually running in --mode=worker and that WORKER_METRICS_PORT is not 0.
Verify that the exporter points to an OTLP HTTP endpoint, not the Jaeger UI port.