Skip to content

Live check output formats#1157

Merged
jerbly merged 19 commits intoopen-telemetry:mainfrom
jerbly:live-check-output-formats
Feb 2, 2026
Merged

Live check output formats#1157
jerbly merged 19 commits intoopen-telemetry:mainfrom
jerbly:live-check-output-formats

Conversation

@jerbly
Copy link
Contributor

@jerbly jerbly commented Jan 26, 2026

Feature (#1132) - Live-check: builtin output rendering for json, jsonl and yaml.

Added an OutputProcessor to weaver_forge which handles json, jsonl, yaml for builtin formats, templates or mute (no output).

The end goal is to unify all weaver output in a standard way. This first PR tackles this only for Live-check only.

A follow-up PR will fit this for other commands: diff and resolve.

@jerbly jerbly requested a review from a team as a code owner January 26, 2026 02:48
@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 79.06977% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.1%. Comparing base (e6649a0) to head (fe5df1e).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
crates/weaver_forge/src/output_processor.rs 79.0% 18 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##            main   #1157   +/-   ##
=====================================
  Coverage   80.1%   80.1%           
=====================================
  Files        108     109    +1     
  Lines       8440    8526   +86     
=====================================
+ Hits        6761    6831   +70     
- Misses      1679    1695   +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jsuereth
Copy link
Contributor

Is there anything in this PR you'd change based on check and resolve output? It seems like you went 90% of the way there already...

@jerbly
Copy link
Contributor Author

jerbly commented Jan 26, 2026

Is there anything in this PR you'd change based on check and resolve output? It seems like you went 90% of the way there already...

I'm hoping not. I think I've made it general purpose.

I think resolve or another one already has some code for serde output directly so I wanted to do that refactoring in a follow up to avoid huge PRs.

Copy link
Contributor

@jsuereth jsuereth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great addition!!!

mostly just nits or thoughts on improvement.

Looking forward to using this one.

fn serialize<T: Serialize>(&self, data: &T) -> Result<String, Error> {
match self {
BuiltinFormat::Json => {
serde_json::to_string_pretty(data).map_err(|e| Error::SerializationError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Should we have a json_pretty and json as separate options?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think jsonl is the json_ugly. The way I've written jsonl handling leaves it to the caller to determine where they want the newlines in the output. You could just have one for the entire output.


/// Output processor - handles output generation with builtin formats or templates
#[allow(clippy::large_enum_variant)]
pub enum OutputProcessor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

This may be better as a trait. If we need a container to wrap it, we could do something like:

struct OutputProcessorOpt(dyn OutputProcessor);

impl <T: OutputProcessor> From<T> : OutputProcessorImpl {
   fn from(p: T) -> OutputProcessorImpl { OutputProcessorImpl(p) }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look at a refactoring... the clippy thing is not a big deal IMO since you make one of these not thousands.

}

/// Open/create the output file if not already open.
fn open(&mut self) -> Result<(), Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These kinds of "implementation deferred" functions are why I think it may be easier to use a trait instead of enum here.

It's a bit hard to follow the stateful behavior across the enum when all the methods are in the same impl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above... I'll look at that refactoring.

}

/// Create/truncate a file and return the handle
fn create_file(path: &std::path::Path, filename: &str) -> Result<File, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - this could be a free-standing helper function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and open are supposed to be the hidden internals. I guess this might be useful elsewhere??

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make it free-standing and hidden to the file, and if we want to publicize it later we can easily do so without moving it around.

})
}
OutputDirective::Stderr => {
unreachable!("OutputProcessor does not support Stderr directive")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why's that? Should we support stderr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revisit this when I integrate OutputProcessor with the other weaver commands. StdErr should be reserved for the logger output so we don't mix artifacts with runtime information. I think this may change for diagnostic output, but I need to get to that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - I'm relying on diagnostic output for the weaver-package tests, hence why I'm really curious how we do this going forward :)


/// Returns the builtin format if this is a builtin processor
#[must_use]
pub fn builtin_format(&self) -> Option<BuiltinFormat> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIt: Is this needed publicly? Anyway we could hide this?

samples: Vec<Sample>,
stats: LiveCheckStatistics,
) -> Result<(), weaver_forge::error::Error> {
// Special JSONL handling: one line per sample, stats at end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we create something like - "needs_multiple_events()" or "is_streaming()" on the OutputProcessor instead?

If we were to use jsonl other places or if we add other new built-in formats, It'd be nice if we didn't need to alter this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went back and forth on this a few times and landed on this API. This handling is specifically for jsonl in live-check's report mode. Most things, I think, will have a single object (albeit large) output. The API here allows you to call generate multiple times and it will append to the file. The first call to generate opens the file.

Again, if this is a bad fit for the other commands I'll refactor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, I think you're talking about the call below what you've highlighted. Instead finding out if it's JSONL find out if the format requires multiple calls to generate. Got it.

LiveCheckStatistics::Cumulative(_) => output.generate(&stats),
LiveCheckStatistics::Disabled(_) => Ok(()),
}
} else if matches!(output, OutputProcessor::Mute) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: If Mute processor does nothing, why not just call it?

OutputDirective::Stdout
};

let mut output = OutputProcessor::new(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this cleanup!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fix this somehow.

@jerbly
Copy link
Contributor Author

jerbly commented Jan 29, 2026

@jsuereth -
I explored the trait approach but backed out because the generic method fn generate<T: Serialize> isn't object-safe. This starts getting messy with solutions involving double serializing with serde_json::Value or introducing erased-serde crate (but that needed ?Sized bounds leaking into TemplateEngine's public API)

Instead I improved the public API by wrapping the enum in a public struct with private internals.

I think I cleaned up all your other nits.

@jerbly jerbly requested a review from jsuereth January 31, 2026 11:48
Copy link
Contributor

@jsuereth jsuereth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanups!! This looks really good now

@jerbly jerbly enabled auto-merge (squash) February 2, 2026 16:37
@jerbly jerbly merged commit a79eda2 into open-telemetry:main Feb 2, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants