Skip to content

Conversation

lalitb
Copy link
Member

@lalitb lalitb commented Jul 30, 2025

Changes

The User-Agent HTTP header should ideally identify the application making the request, not just the library.
This PR adds configurable User-Agent header support to the Geneva Uploader, allowing applications to identify themselves in HTTP requests to Geneva services.

So the approach with the PR is:

Configuration Resulting User-Agent
None RustGenevaClient/0.1
Some("MyApp/2.1.0") MyApp/2.1.0 (RustGenevaClient/0.1)

Also added support to provide the user-agent header in the Ingestion Service (along with Config Service)

Merge requirement checklist

  • CONTRIBUTING guidelines followed
  • Unit tests added/updated (if applicable)
  • Appropriate CHANGELOG.md files updated for non-trivial, user-facing changes
  • Changes in public API reviewed (if applicable)

@lalitb lalitb requested a review from a team as a code owner July 30, 2025 23:01
@lalitb lalitb changed the title feat: [Geneva Exporter] Add configurable User-Agent Header Support feat: [Geneva Exporter] Add configurable User-Agent Header Jul 30, 2025
@utpilla
Copy link
Contributor

utpilla commented Aug 1, 2025

Changes

The User-Agent HTTP header should ideally identify the application making the request, not just the library.

If this is the only intention, then should we choose the User Agent ourselves instead of letting the user override it and potentially misuse or erroneously use it. We should be able to get hold of the executable name through std::env::args() or std::env::current_exe().

Copy link

codecov bot commented Aug 1, 2025

Codecov Report

❌ Patch coverage is 86.12717% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.7%. Comparing base (179f777) to head (6fb82cb).

Files with missing lines Patch % Lines
...etry-exporter-geneva/geneva-uploader/src/client.rs 0.0% 8 Missing ⚠️
...etry-exporter-geneva/geneva-uploader/src/common.rs 96.3% 5 Missing ⚠️
...eneva/geneva-uploader/src/ingestion_service/mod.rs 0.0% 4 Missing ⚠️
...r-geneva/geneva-uploader/src/config_service/mod.rs 83.3% 3 Missing ⚠️
stress/src/geneva_exporter.rs 0.0% 3 Missing ⚠️
.../geneva-uploader/src/ingestion_service/uploader.rs 0.0% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            main    #400     +/-   ##
=======================================
+ Coverage   48.1%   48.7%   +0.6%     
=======================================
  Files         69      70      +1     
  Lines       9811    9969    +158     
=======================================
+ Hits        4720    4859    +139     
- Misses      5091    5110     +19     

☔ 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.

@lalitb
Copy link
Member Author

lalitb commented Aug 1, 2025

If this is the only intention, then should we choose the User Agent ourselves instead of letting the user override it and potentially misuse or erroneously use it. We should be able to get hold of the executable name through std::env::args() or std::env::current_exe().

Intention is to provide the applications to provide the customization for the User-Agent- according to the format they want (which could be say MyApp/1.0

format!("{prefix} (GenevaUploader/0.1)")
};

HeaderValue::from_str(&user_agent).map_err(|e| {
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like this method does the ASCII characters validation check anyway so we could get avoid checking that ourselves.

for (i, ch) in prefix.char_indices() {
match ch {
// Control characters that would break HTTP headers
'\r' | '\n' | '\0' => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think had initially suggested checking for control characters in a comment, but I realized they would anyway be outside of the ASCII range so we would never allow control characters as long as we only accept ASCII.

And the ASCII check is already done by HeaderValue::from_str() method, so we can probably get rid of this whole method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

But I am good to remove the validation altogether, and let the reqwest crate handle it.

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems HeaderValue::from_str() allows non-ascii chars https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=1e264800416082c69cbb17c474caab02

This led to some new learnings. The documentation for HeaderValue::from_str() method says that it doesn't allow non-ASCII characters so I would have expected the test cases you shared to fail. I then found this is in the documentation for HeaderValue struct:

In practice, HTTP header field values are usually valid ASCII. However, the HTTP spec allows for a header value to contain opaque bytes as well. In this case, the header field value is not able to be represented as a string.

To handle this, the HeaderValue is useable as a type and can be compared with strings and implements Debug. A to_str fn is provided that returns an Err if the header value contains non visible ascii characters.

This means that we need to check if HeaderValue::from_str(str).is_ok() && HeaderValue::from_str(str).unwrap().to_str().is_ok() to rule out the non-ASCII inputs.

Check this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=9802b30e262c93e92e71fd92fcb543dd

pub async fn new(cfg: GenevaClientConfig) -> Result<Self, String> {
// Build config client config
// Validate user agent prefix once and build headers once for both services
// This avoids duplicate validation and header building in config and ingestion services
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like a duplicate validation since build_geneva_headers method also calls validate_user_agent_prefix method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants