OpenTelemetry
Export mineru-runpod logs, traces, and metrics over OTLP/HTTP to Axiom, Honeycomb, Grafana, Datadog, Jaeger, or your own collector.
The worker already ships with optional OpenTelemetry export for logs,
traces, and metrics. It is off by default. Until you set
OTEL_EXPORTER_OTLP_ENDPOINT, the OTel SDK is not loaded and the worker
continues to write JSON logs only to RunPod's dashboard.
When you turn it on, the OpenTelemetry SDK is configured at worker boot to ship to any OTLP/HTTP-compatible backend. The template itself is vendor-neutral; the only Axiom-specific recipe lives in the Axiom blog post.
Enable OpenTelemetry
Set OTEL_EXPORTER_OTLP_ENDPOINT on your RunPod endpoint to the base URL
of an OTLP/HTTP collector. That one environment variable enables all three
signals. The published worker sends OTLP/HTTP with protobuf and appends
/v1/traces, /v1/logs, and /v1/metrics automatically.
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector.example.com
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer your-token
OTEL_SERVICE_NAME=mineru-runpod
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,team=documentsUse the collector's base URL, not a per-signal path. If you include
/v1/traces, for example, the exporter appends the path again and the
collector returns 404.
| Env var | Purpose |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | Base URL of your OTLP/HTTP backend. Some vendors use a different hostname for telemetry ingest than for their management API, so check the vendor's OpenTelemetry documentation. |
OTEL_EXPORTER_OTLP_HEADERS | Comma-separated key=value headers sent with each export, usually for authentication and dataset selection. |
OTEL_SERVICE_NAME | Service name attached to every signal. Override the default mineru-runpod when several endpoints share one backend. |
OTEL_RESOURCE_ATTRIBUTES | Extra comma-separated attributes such as deployment.environment=production,team=documents. These are merged with the built-in attributes. |
The worker also reads the standard per-signal endpoint and header variables:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT/OTEL_EXPORTER_OTLP_TRACES_HEADERSOTEL_EXPORTER_OTLP_LOGS_ENDPOINT/OTEL_EXPORTER_OTLP_LOGS_HEADERSOTEL_EXPORTER_OTLP_METRICS_ENDPOINT/OTEL_EXPORTER_OTLP_METRICS_HEADERS
Use these when a backend stores metrics separately from traces and logs.
Unlike the shared base endpoint, each per-signal endpoint is an exact URL and
is not given a suffix by the Python exporter. It normally includes the matching
/v1/traces, /v1/logs, or /v1/metrics path; follow your backend's setup.
The RunPod Hub form exposes all three header overrides because some backends,
including Axiom, require a separate dataset for each signal.
After the next worker boot, submit one parse and confirm that you see a
mineru.job trace, the matching job_id in logs, and
mineru.jobs.total in metrics. The first metric batch can take up to
10 seconds. For an exact Axiom setup and verification checklist, follow
the Axiom walkthrough.
What gets emitted
Traces. One span per job (mineru.job) and one span per phase inside it:
mineru.fetch_input, mineru.parse, and mineru.package. The boot-time
warmup has its own mineru.warmup span. Fetch spans include input size and
source kind; parse spans include input format, backend, and requested page
range; package spans include the selected transport.
Logs. Every line emitted by the worker's structured logger is
mirrored to OTLP, additive to the existing stdout JSON. RunPod's
dashboard remains the source of truth; the mirror lets you query the
same records from your downstream sink. Each record carries job_id
as an attribute and (when a span is active) the OTel trace and span
IDs so logs link back to traces in the UI.
Metrics. The full catalog below, exported via the OTLP/HTTP metric exporter on a 10-second batch interval. All histogram metrics use base-2 exponential bucket aggregation, not the SDK default of explicit (linear) buckets. Latency metrics span ms → minutes and byte-size metrics span KB → hundreds of MB; exponential buckets give uniform resolution across those ranges without per-metric bucket tuning. Any modern OTLP backend (Axiom, Honeycomb, Grafana Mimir, Datadog) accepts exponential histograms.
| Metric | Type | Labels | Question it answers |
|---|---|---|---|
mineru.jobs.total | counter | status, backend, input_format | How many jobs, what outcome? |
mineru.pages.total | counter | backend | Requested pages for bounded page ranges. Full-document jobs with an open-ended range currently add zero. |
mineru.bytes_in.total | counter | source (b64/url/volume) | Ingest bytes by input source. URLs and filesystem paths are reduced to their source kind before export. |
mineru.bytes_out.total | counter | transport (tarball_b64/inline/s3) | Egress volume by transport. Inline payloads are approximated as markdown text + image bytes (ignoring JSON overhead). |
mineru.errors.total | counter | type, phase | Error count by exception type. phase is currently reported as handler. |
mineru.degraded.total | counter | reason, artifact | Artifacts a successful response could not carry. reason is unreadable, unresolvable, outside_output_dir, or unsafe_name; artifact is the format key, or archive for a member of a tarball. A job that loses more than 50 artifacts reports the remainder once with both labels set to unlisted — the response stops itemising at that point, and dropping the overflow would understate the total on exactly the worst jobs, so sum over all label values to get the true count. A non-zero rate here means jobs are returning ok: true with holes in them — see Incomplete responses. Every value is bounded, so the series count is. |
mineru.job.duration | histogram | backend, input_format | End-to-end wall-clock per job. |
mineru.phase.duration | histogram | phase | Where time goes inside a job. |
mineru.pages_per_second | histogram | backend | Throughput for jobs with a bounded requested page count. |
mineru.input.size_bytes | histogram | — | Input-size distribution. |
mineru.output.size_bytes | histogram | transport | Output-size distribution. |
mineru.worker.cold_starts.total | counter | — | Cold-start rate per endpoint. |
mineru.worker.warmup.duration | histogram | backend, status (ok/error) | Boot-time warmup duration. |
mineru.worker.refresh.total | counter | reason (jobs_threshold/pages_threshold/sigterm) | Why workers recycle. |
mineru.worker.jobs_since_boot | gauge | — | Counts toward REFRESH_WORKER_AFTER_JOBS. |
mineru.worker.pages_since_boot | gauge | — | Counts toward REFRESH_WORKER_AFTER_PAGES. |
mineru.gpu.memory_used_bytes | gauge | device | VRAM usage — critical for tuning concurrency. |
mineru.gpu.memory_total_bytes | gauge | device | Constant per pod; enables % math. |
mineru.gpu.utilization_percent | gauge | device | SM utilization. GPU-bound vs CPU-bound. |
The mineru.source fetch-span attribute uses the same bounded source kind.
Full URLs and volume paths remain in the job's results[].source response for
caller-side correlation, but they are not exported as telemetry attributes.
Resource attributes are attached to every signal (logs, spans, metrics):
service.name—mineru-runpodby default; override withOTEL_SERVICE_NAMEmineru.version— the MinerU library version baked into the imagerunpod.endpoint_id,runpod.pod_id,runpod.gpu_type,runpod.gpu_count— read from theRUNPOD_*env vars RunPod sets on every worker
Compatible backends
Any OTLP/HTTP collector. Tested or known-compatible:
- Axiom — OTLP-native, no agent. See the dedicated walkthrough for endpoint URLs, header layout, and the metrics-dataset gotcha.
- Honeycomb — set
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.ioandOTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=<key>. - Grafana Tempo / Loki / Mimir — point at your Grafana Cloud OTLP gateway or self-hosted collector.
- Datadog — via their OTLP intake (requires the dedicated endpoint URL).
- Jaeger — via Jaeger's OTLP receiver.
- Any OTel Collector — point at its OTLP/HTTP receiver and let the collector fan out to whatever downstreams you have configured.
If you write up a recipe for a backend not listed here, send a PR adding it as a blog post (one post per vendor, please — this guide stays vendor-neutral).
Performance notes
On one RTX 4090 deployment, enabling OTel added roughly 200-500 ms to a fresh boot for SDK initialization and the first export. That work did not visibly repeat on the measured fast FlashBoot path, but RunPod does not document enough snapshot detail to make that a platform guarantee.
The batch span / log / metric processors flush every 500 ms (spans, logs) or every 10 s (metrics) with a small in-memory queue. Network export happens off the request path; latency-sensitive deployments should still benchmark with their own collector. If the collector is unreachable, the SDK retries with exponential backoff and drops batches that age out; the worker continues serving requests either way.
Failure modes
Bad endpoint URL or auth. The SDK logs Transient error HTTPConnectionPool(...) to stderr (visible in RunPod's logs) and
retries. The worker stays healthy and continues serving — the
stdout-JSON log channel is unaffected.
Collector goes down mid-job. Same behavior as above. In-flight batches retry until they age out of the queue; new batches keep flowing. Logs in RunPod's dashboard are unaffected (additive mirror, not replacement).
OTel SDK init crashes. The worker logs
[mineru-telemetry] init failed, continuing without OTel: ... to
stdout and proceeds to serve traffic without telemetry. This is
deliberate — a misconfigured exporter must never block worker boot.
Missing dependencies. The container always ships with the OTel packages installed; forks that strip them will fall into the "init crashes" path above (telemetry simply stays disabled). The worker still serves requests.
Reading worker logs
Independent of OpenTelemetry, the worker emits structured JSON to stdout on every log line — see Troubleshooting → Reading worker logs for the schema and field reference. The OTel logs export above mirrors those records to your OTLP backend; the stdout JSON is always the primary channel and remains queryable through RunPod's log viewer regardless of whether OTel is enabled.
Set LOG_FORMAT=text on the endpoint for a human-readable single-line
format instead of JSON — useful for local development, less useful
once you're shipping to an indexed sink.
Last updated on
Scaling and tuning
Worker-level knobs for concurrency, memory hygiene, and MinerU's own performance tuning. All are env vars on your RunPod endpoint, not per-request fields.
Network volumes
Use RunPod network volumes for large input corpora or shared pipeline files, and avoid duplicating model weights already baked into the image.