Send Telemetry to a Backend
NeMo Lens does not provide or recommend an observability solution. It emits OTLP. Where that OTLP goes, how it is stored, how it is queried, and how it is visualized are your decisions, which are shaped by your organization’s existing observability investments and the scale of your workloads.
This page shows how to configure NeMo Lens for common destinations. NeMo Lens exports through standard OTLP, so any OTLP-compatible backend works without code changes. Four destinations are covered in depth:
- File: local trace and metric capture for offline analysis or archival
- W&B Weave: Weights & Biases’ trace UI, co-located with training run metadata
- Honeycomb: hosted APM that accepts all three signals on one OTLP endpoint
- OTel Collector: a routing and aggregation layer in front of other backends
This guide also provides a quick reference for other hosted backends.
Export Telemetry to a File
Writing traces to a local file is useful for offline analysis, CI captures, and archival. Choose one of the following four approaches depending on your requirements.
Use Console Exporter with Shell Redirect (Simplest)
Setting NEMO_LENS_EXPORTER=console installs the ConsoleSpanExporter, which writes one JSON line per span to stdout. Redirect stdout to a file, and you have a span log.
Drawbacks:
- Mixes application stdout with span data; separate them with selective logging to stderr.
- Does not capture metrics (the metric exporter writes a different format).
Point Custom ConsoleSpanExporter to a File Handle
The ConsoleSpanExporter accepts any file-like object using out=. This separates trace data from application stdout without a shell redirect.
Caveats:
- Line-buffer the output using
buffering=1so that lines are not lost if the process crashes. - Close the file after calling
handle.shutdown(). - Each line is a Python
reprrepresentation of the span, not strict JSON. For strict JSON, write a custom exporter as described in the next approach.
Implement Custom SpanExporter for Full Control
For structured JSON, compression, rotation, or any custom format:
This approach provides strict JSONL that is trivial to query using jq. Extend this implementation with gzip compression, rotation, or remote write as needed.
Use OTel Collector File Exporter
If you are already running an OTel Collector, add a file exporter to its pipeline:
The application still exports OTLP as normal, and the Collector handles file writes, rotation, and retention.
Use this approach when you want a single file with spans from multiple ranks or multiple services.
Export Metrics to a File
For metrics, use a PeriodicExportingMetricReader with a ConsoleMetricExporter:
Alternatively, use the file exporter of the Collector in the metrics pipeline using the same pattern as traces.
Integrate with W&B Weave
Weave is the Weights & Biases trace visualization tool. It ingests OTLP spans and renders them in the same UI as your training runs, so that traces and training metrics live together.
Configure the Integration
Configure the integration by choosing one of the two patterns below depending on whether you are using a collector.
Configure Pattern A for Direct Export from the Application
Use this pattern when you want to export telemetry directly to Weights & Biases without running a local OTel Collector.
A ready-to-run compose file for this pattern is at docker-compose.weave.yml, which brings up only the Megatron container and points traces straight to Weave. Use this file when you do not want to run the local stack.
Configure Pattern B for Export Through an OTel Collector
Use this pattern when you want batching, filtering, or multi-backend fan-out. See observability/otel-collector-weave.yaml in the repository for a ready-to-run example, toggled through the --config=/etc/otel/collector-weave.yaml mode of the docker-compose.otel.yml file.
Notes on the Direct Path
- W&B Weave currently ingests traces only (as of early 2026). Metrics still require a separate sink, such as Prometheus, an OTel Collector, or native
wandb.log(). - Use the
OTEL_EXPORTER_OTLP_TRACES_*variants instead of the signal-agnosticOTEL_EXPORTER_OTLP_*. The Weave URL is a full path ending in/v1/traces. Signal-specific environment variables are treated as full URLs, whereas the generic variant appends/v1/tracesautomatically; setting both would produce/v1/traces/v1/tracesand a 404 error. - NeMo Lens honors
OTEL_EXPORTER_OTLP_PROTOCOLand signal-specific variants, sohttp/protobufroutes to the HTTP exporter class because Weave is HTTP-only. NemoLensConfig.from_env()readsWANDB_ENTITYandWANDB_PROJECTdirectly and sets them aswandb.entityandwandb.projectresource attributes on every span, which is required for Weave to route correctly.
Run the Application
Traces appear in the Weave tab of your W&B run within a few seconds. The trace tree mirrors Jaeger’s structure: a megatron.train_step root span with child spans for forward_backward, optimizer, and other tasks.
Link Traces to Runs
Because WANDB_ENTITY and WANDB_PROJECT are set as span attributes, Weave automatically associates traces with the right W&B run. The nemo.run.id resource attribute (auto-generated or from SLURM_JOB_ID) serves as a unique run identifier you can filter on in the Weave UI.
Configure Sampling for Cost Management
W&B bills by ingested trace volume. For long per_step runs, sample aggressively:
See the sampling documentation for how this composes with NeMo Lens’s export strategies.
Understand Exported Data
Everything the SDK exports, including span names, attributes, events, links, and status. Weave renders:
- Attribute key-value pairs
- Error events through
span.record_exception - OTel links as clickable references, which are useful for pipeline-parallel correlation
Send Telemetry to Honeycomb
Honeycomb is a hosted APM that ingests OpenTelemetry data natively. Unlike W&B Weave, it accepts all three signals (traces, metrics, and logs) on a single OTLP endpoint. This is a good fit if you want one hosted destination for everything and already have (or are happy to adopt) Honeycomb’s query model.
Configure Honeycomb
Configure the integration with Honeycomb by choosing one of the following two patterns.
Configure Pattern A for Direct Export from the Application
x-honeycomb-team: Your ingest API key. In the Honeycomb UI, select Environment Settings, and then select API Keys to find this key.x-honeycomb-dataset: The dataset name. This name is required for metrics, and it is optional but recommended for traces and logs. Choose any meaningful name; Honeycomb automatically creates the dataset on the first write.OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf: Honeycomb supports both gRPC and HTTP, but HTTP is more forgiving behind load balancers. Default to HTTP unless you have a reason otherwise.
NeMo Lens honors OTEL_EXPORTER_OTLP_PROTOCOL (and the signal-specific variants OTEL_EXPORTER_OTLP_TRACES_PROTOCOL and OTEL_EXPORTER_OTLP_METRICS_PROTOCOL) when picking between gRPC and HTTP exporters, so this works without code changes.
For EU instance, substitute https://api.eu1.honeycomb.io:443.
A ready-to-run compose file for this pattern is at docker-compose.honeycomb.yml, which brings up only the Megatron container and points it straight to Honeycomb. Use this file when you do not want to run the local stack.
Configure Pattern B for Export Through an OTel Collector
Use this pattern when you want batching, filtering, or multi-backend fan-out between the application and Honeycomb. See collector-honeycomb.yaml in the repository for a ready-to-run example:
The application then points at your collector (OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317), and the collector handles Honeycomb auth and routing.
The repo’s docker-compose.otel.yml has a one-line toggle for this: uncomment --config=/etc/otel/collector-honeycomb.yaml and set HONEYCOMB_API_KEY and HONEYCOMB_DATASET in .env.
Compare Classic and Current Honeycomb Accounts
Honeycomb migrated from dataset-per-service (Classic) to environment-based organization. If you are on a Classic account, the x-honeycomb-dataset header is required for every signal, and the dataset field has specific semantics. For current Honeycomb it is still required for metrics and optional but recommended for traces and logs. If you are unsure which account type you have, your account page displays this information.
Manage Data Volume through Sampling
Honeycomb bills on event volume. A per_step Megatron run on many ranks will ship a lot of events. Layer your sampling:
- NeMo Lens
export_strategyat the rank level (start withsingle_rank). - OTel SDK
OTEL_TRACES_SAMPLER=parentbased_traceidratioat the trace level. - Honeycomb Refinery tail sampling, which provides access to the full trace before making a decision. This is recommended for production; see Honeycomb’s Refinery docs.
Understand Exported Honeycomb Data
Every attribute, event, and link the SDK exports. Honeycomb’s UI is especially good at high-cardinality attribute queries (BubbleUp, HEATMAP, etc.), so set span attributes liberally; attribute cardinality is what Honeycomb is best at.
Configure OTel Collector
The OpenTelemetry Collector is a common intermediary between your application and your observability backends. Running a Collector instead of exporting directly from the SDK can provide several benefits:
- Fan-out capabilities. Send the same telemetry to multiple backends, such as Jaeger, Prometheus, and an S3 archive.
- Sampling and filtering. Drop spans at the Collector instead of within each SDK instance.
- Batching and resilience. Buffer during network outages without losing data.
- Transforms. Rename attributes, redact PII, or enrich with external metadata.
- Centralized configuration. Change backends without restarting training jobs.
Set Minimum Configuration
Run the Collector
Docker (simplest):
This is a generic standalone example. The repository’s bundled configurations live under observability/ and are mounted at /etc/otel/collector*.yaml by docker-compose.otel.yml (selected using the --config line); adjust the path if you copy from there.
On a cluster, deploy as a sidecar, DaemonSet, or shared service. Typical patterns:
- Sidecar deployment. Deploy one Collector per application pod. This provides low latency and an isolated failure domain.
- DaemonSet deployment. Deploy one Collector per host, where every local application exports to it. This is a good fit for Kubernetes.
- Shared service deployment. Deploy one fleet of Collectors behind a load balancer. This is the most cost-effective option but adds a network hop.
Configure the Application
That’s it. Lens discovers the endpoint from the standard env var; no code changes.
Apply Useful Processors
Beyond batch, consider:
Attach to a pipeline:
Configure Multi-Backend Routing
Send traces to two places simultaneously, such as Jaeger for interactive debugging and W&B Weave for run history:
The application exports to one endpoint (the Collector), and the Collector fans out the telemetry.
Configure Collector-Side Sampling
Instead of sampling at the SDK through OTEL_TRACES_SAMPLER, sample at the Collector. The advantage of this approach is that you can make the decision based on the complete trace (for example, keep all traces containing an error), which the SDK cannot do because it has not seen the whole trace yet.
The tail_sampling processor is the standard tool. See the full tail sampling documentation.
Production Considerations
- Backpressure. If a backend is slow, the Collector buffers. Configure
sending_queuelimits to cap memory. - TLS. Enable TLS between the SDK and the Collector, and between the Collector and backends, in any multi-tenant setup.
- Health checks. Enable the
health_checkextension (as the bundled configurations do) to expose:13133/and monitor it. - Version pinning. The
opentelemetry-collector-contribimage changes, so pin to a version and upgrade deliberately.
Debug the Collector
The Collector’s own telemetry (:8888/metrics) shows incoming span rates, processor queue depth, and exporter success counts; scrape it with Prometheus to monitor the monitoring.
Send Telemetry to Other Hosted Backends
Configure other popular hosted backends by using standard OpenTelemetry environment variables.
Configure Grafana Cloud
This routes traces to Tempo, metrics to Mimir, and logs to Loki, all queryable from a unified Grafana UI.
Configure Datadog
Datadog also ships their own Collector preset; see their documentation for advanced configuration.
Configure New Relic
Configure Self-Hosted Jaeger or Tempo
Both accept OTLP natively:
Select a Backend
All destinations work the same from NeMo Lens’s perspective; the choice is about cost, operational burden, and integration with your existing stack.
Partition Telemetry by Run
Regardless of the backend, filter by nemo.run.id (auto-set by NeMo Lens) to isolate a specific training run’s data:
- Jaeger: Use the tag filter
nemo.run.id=<value>. - Grafana: Use the dashboard variable
nemo_run_id. - Honeycomb: Use the filter
nemo.run.id. - Datadog: Use the facet
@nemo.run.id. - Weave: Use the run-level association through
WANDB_ENTITYandWANDB_PROJECT.
Multiple runs land in the same index or project; the attribute is the partition key.
gRPC vs HTTP
OTLP has two transport variants:
The NeMo Lens providers.py tries gRPC first, and falls back to HTTP if the gRPC exporter is not installed. If you only installed opentelemetry-exporter-otlp-proto-http, set the protocol explicitly.
Limit Ingested Spans
A per_step run on 1,000 ranks can produce over 100,000 spans per second. Most backends cannot (or will not affordably) ingest that volume. Layer your sampling:
- NeMo Lens
export_strategyat the rank level, wheresingle_ranksends one rank’s data. This is usually the correct starting point. - The OTel SDK sampler at the trace level, where setting
OTEL_TRACES_SAMPLER=parentbased_traceidratiowithOTEL_TRACES_SAMPLER_ARG=0.1keeps 10% of traces. - Collector tail sampling for intelligent decisions, which keeps all errors and samples 1% of successes.
Combine aggressively. It is easier to re-enable telemetry when you are debugging than to pay for ingestion nobody looks at.