Repository navigation
feat: add opentelemetry-otlp-file crate implementing the OTLP File Exporter - #3763
garysassano wants to merge 13 commits into
Conversation
…porter SpanExporter, MetricExporter and LogExporter write each export as one line of OTLP JSON (TracesData, MetricsData or LogsData) to stdout, to a file opened for appending, or to any std::io::Write implementation, as described by the OpenTelemetry Protocol File Exporter specification.
Pull request dashboard statusWaiting on the author · refreshed 2026-10-17 01:45 UTC Respond to 4 review items (e.g. link a commit, explain why not, ask a follow-up): Status above doesn't look right?
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3763 +/- ##
=======================================
+ Coverage 84.1% 84.9% +0.8%
=======================================
Files 123 127 +4
Lines 30280 31169 +889
=======================================
+ Hits 25483 26486 +1003
+ Misses 4797 4683 -114 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
opentelemetry-otlp-file crate implementing the OTLP File Exporter
SystemTime has 100 ns resolution on Windows, so a start time ending in ...321 ns came back as ...300 and failed the encoding assertion there.
2166952 to
e430738
Compare
scottgerring
left a comment
There was a problem hiding this comment.
Hey @garysassano thanks for the PR! -i've put some initial feedback inline but it's generally looking good! I reckon the biggest thing to agree on to move forward is where this belongs - contrib, here, or as an experimental feature on the OTLP crate. I'm kind of leaning towards the latter to avoid duplication.
Have tagged the rest of the maintainers to express opinions so we can unblock you
| @@ -0,0 +1,53 @@ | |||
| [package] | |||
There was a problem hiding this comment.
I feel like we should host this in contrib or as an unstable feature in the OTLP crate (which will help with re-use), in particular because the spec is still in development.
Reviewing it inline anyhow as I don't think it should affect the actual contents much, but it would be good to align on this - @open-telemetry/rust-maintainers thoughts?
There was a problem hiding this comment.
Happy to move it wherever you prefer. If it goes into opentelemetry-otlp, I'd suggest a separate file module behind its own feature flag (e.g. opentelemetry_otlp::file::SpanExporter) rather than a third option next to with_tonic()/with_http() on the existing builders, since it shares none of the endpoint, headers, timeout or TLS configuration. That would also let it reuse the env-var helpers you mention in the other comment. The move itself is mostly mechanical, since the encoding already goes through the same opentelemetry-proto with-serde types as http-json, so I'll wait for the decision before changing anything.
There was a problem hiding this comment.
I’d keep this as a separate crate in this repo, unless other maintainers have concerns. The file exporter has a distinct configuration and dependency surface from HTTP/gRPC, and it already reuses the proto transforms. The small amount of duplicated environment-variable handling doesn’t seem sufficient reason to put it in opentelemetry-otlp or move that policy into the SDK. From my side, @garysassano can proceed with the current placement.
There was a problem hiding this comment.
Thanks @lalitb, I'll keep it as a separate crate here then. The Development status is called out at the top of the README and in the crate docs. I'll leave this thread open in case @scottgerring wants to weigh in, since he leaned towards the OTLP crate.
| @@ -0,0 +1,53 @@ | |||
| [package] | |||
There was a problem hiding this comment.
I’d keep this as a separate crate in this repo, unless other maintainers have concerns. The file exporter has a distinct configuration and dependency surface from HTTP/gRPC, and it already reuses the proto transforms. The small amount of duplicated environment-variable handling doesn’t seem sufficient reason to put it in opentelemetry-otlp or move that policy into the SDK. From my side, @garysassano can proceed with the current placement.
This comment has been minimized.
This comment has been minimized.
|
/dashboard route:reviewers |
|
@garysassano, this pull request was routed to reviewers. The handoff remains active across pushes until newer actionable human feedback arrives. Top-level feedback through this request will not return; unresolved review threads remain open. |
| /// exporter. The writer is dropped even if the flush fails. | ||
| pub(crate) fn shutdown(&self) -> OTelSdkResult { | ||
| let mut state = self.lock()?.take().ok_or(OTelSdkError::AlreadyShutdown)?; | ||
| state.flush().map_err(|err| self.io_error(err)) |
There was a problem hiding this comment.
It may be worth also calling fsync when the destination is a file so that the caller can be more certain that the data is actually written durably to disk.
|
|
||
| /// The destination configured on an exporter builder. | ||
| #[derive(Default)] | ||
| pub(crate) enum Output { |
There was a problem hiding this comment.
Can we use a more descriptive name here other than just Output, e.g. OutputStream?
| @@ -0,0 +1,667 @@ | |||
| use std::fmt; | |||
There was a problem hiding this comment.
This might just be me, but I feel like a lot of the logic in this file can be greatly simplified and that some of these helper enums and structs can be consolidated.
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { |
There was a problem hiding this comment.
Can we include some true end-to-end tests that involve writing to a real file (same for the trace/log exporters)?
Closes #2602
Design discussion issue (if applicable) #2602
Changes
Adds a new crate,
opentelemetry-otlp-file, that implements the OTLP File Exporter specification (Development status) for traces, metrics and logs.SpanExporter,MetricExporterandLogExporterwrite each export as one line of compact OTLP JSON: aTracesData,MetricsDataorLogsDataobject followed by\n.with_file(path)appends to a file, creating it if needed, and opens it inbuild()so a bad path fails early.with_writer(w)accepts anystd::io::Write + Send + 'static.exportreturns and does not interleave with other exports orprintln!output. If a write fails partway through a line, the export returns an error and the rest of the line is written before the next one, so later lines stay valid. Empty batches write nothing.AlreadyShutdown.MetricExporterreadsOTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCEwith the same precedence asopentelemetry-otlp(builder, then environment, then cumulative).opentelemetry-protowith-serdetypes and transforms, the same path ashttp-json. No new dependencies are added to the workspace.max_log_sizelimit, blocking writes with the async-runtime processors, and running on AWS Lambda, including the trade-offs between flushing strategies.Consistency with other SDKs
opentelemetry-exporter-otlp-json-file): writes to a path, a stream or stdout, and flushes after every export. This crate does the same.OtlpStdout*Exporter): writes to anyOutputStreamand never closes stdout.with_writerand the stdout handling match this.Why a separate crate
The exporter needs none of the transport configuration in
opentelemetry-otlp(endpoint, headers, timeout, retry, TLS), andopentelemetry-otlpis being stabilized while this spec is still Development, so a separate crate keeps it out of that API. Python and Java also ship it as a separate package. In review, @lalitb agreed with keeping it as a separate crate in this repo.Not covered
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION, becausePushMetricExportercannot choose the default aggregation yet. Once feat(metrics): addOTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATIONsupport #3433 lands,MetricExportercan implement it the same way as the OTLP exporter.OTEL_{TRACES,METRICS,LOGS}_EXPORTER=otlp/stdout, because the Rust SDK does not select exporters from environment variables.Testing
opentelemetry-protodeserialization, empty batches, shutdown, file create/append/open errors and concurrent writes.-D warningsfor each feature on its own and with all features, and the workspace lib tests pass.otlp_json_filereceiver in Collector contrib 0.161.0. Diffing what itsfileexporter wrote back against the input showed no differences for any signal.provided.al2023, arm64) with all three exporters on stdout andforce_flushat the end of each invocation: every line arrived in CloudWatch Logs as a single log event with both the Text and JSON log formats, and the extracted lines passed the same Collector check. Lines longer than the roughly 256 KiB CloudWatch event limit are split across events, which the README points out.max_log_sizeand received in full withmax_log_size: 4MiB, which the README now documents.cargo check-external-types(nightly-2025-05-04) passes with the newallowed-external-types.toml, which I added to the CI matrix.Notes
with-serdeexemplar encoding gap I found while testing ([Bug]: opentelemetry-proto with-serde nests Exemplar values under "value" and writesasIntas a JSON number #3764) was fixed in fix(proto): encodeasIntas a string and flatten exemplar values in OTLP/JSON #3765, which this branch includes. NaN and infinite doubles still becomenull(Follow up: Track remaining OTLP metrics JSON serde issues for NaN and integer-valued floats #3442, feat(otlp): Improved support for OTLP JSON metrics serialization (NaN,Infinity,-Infinity) #3496), which affects this crate andhttp-jsonequally.This was developed with AI assistance (Claude); I reviewed and tested the changes.
Merge requirement checklist
CHANGELOG.mdfiles updated for non-trivial, user-facing changes