Skip to content

feat: add user agent option for HTTP and GRPC exporters#5928

Merged
david-luna merged 42 commits intoopen-telemetry:mainfrom
david-luna:exporters-user-agent-option
Oct 8, 2025
Merged

feat: add user agent option for HTTP and GRPC exporters#5928
david-luna merged 42 commits intoopen-telemetry:mainfrom
david-luna:exporters-user-agent-option

Conversation

@david-luna
Copy link
Copy Markdown
Contributor

@david-luna david-luna commented Sep 12, 2025

Which problem is this PR solving?

Closes: #5860
Fixes: #5867

Description of the changes

Summary
At its core the PR is adding a new optional property in the legacy and new new configuration options for HTTP and GRPC. It also unifies how both configurations are resolved regarding the user-agent header/metadata.

Details
Current implementations where using headers and metadata functions to pass the user agent. I preferred to have a specific option for that.

The default user agent for HTTP has been moved to the base exporter packages as a default config option. Then the merge mechanism takes care (concats) if the user has provided another value. This way we can remove the default value of user agent from all http exporters. The resolved user agent will be passed to transport and this will be used for exports, ignoring any possible user agent value in the headers function. This approach is similar to what GRPC is doing and I prefer it since IMHO there should be no differences in exporters.

For GRPC we do the same to resolve the user agent but we use it in the GRPC service constructor. Turns out adding into metadata was not enough and the internals of the library just ignore that entry. This contributes to fix #5867

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Add tests to http configuration methods
  • Add tests to http-export delegate and transport
  • Add tests to grpc configuration methods
  • Add tests to grpc-export transport

Checklist:

  • Followed the style guidelines of this project
  • Unit tests have been added
  • Documentation has been updated

@codecov
Copy link
Copy Markdown

codecov bot commented Sep 12, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.24%. Comparing base (f8450cf) to head (cc21965).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5928   +/-   ##
=======================================
  Coverage   95.24%   95.24%           
=======================================
  Files         315      315           
  Lines        8602     8604    +2     
  Branches     1795     1798    +3     
=======================================
+ Hits         8193     8195    +2     
  Misses        409      409           
Files with missing lines Coverage Δ
...ogs-otlp-http/src/platform/node/OTLPLogExporter.ts 100.00% <ø> (ø)
...gs-otlp-proto/src/platform/node/OTLPLogExporter.ts 100.00% <ø> (ø)
...e-otlp-http/src/platform/node/OTLPTraceExporter.ts 100.00% <ø> (ø)
...-otlp-proto/src/platform/node/OTLPTraceExporter.ts 100.00% <ø> (ø)
...-otlp-http/src/platform/node/OTLPMetricExporter.ts 100.00% <ø> (ø)
...otlp-proto/src/platform/node/OTLPMetricExporter.ts 100.00% <ø> (ø)
.../configuration/convert-legacy-node-http-options.ts 100.00% <ø> (ø)
...ase/src/configuration/legacy-node-configuration.ts 100.00% <ø> (ø)
...-base/src/configuration/otlp-http-configuration.ts 100.00% <ø> (ø)
.../src/configuration/otlp-node-http-configuration.ts 100.00% <ø> (ø)
... and 3 more
🚀 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.

@david-luna david-luna changed the title feat: add user agent option for traces http exporter feat: add user agent option for HTTP and GRPC exporters Sep 16, 2025
@david-luna david-luna marked this pull request as ready for review September 17, 2025 11:22
@david-luna david-luna requested a review from a team as a code owner September 17, 2025 11:22
super(
createOtlpHttpExportDelegate(
convertLegacyHttpOptions(config, 'LOGS', 'v1/logs', {
'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note for reviewer: the default value is moved to the exporter-base package. Users can pass now a secondary user agent in the config in the constructor.

new OTLPLogExporter({ userAgent: 'Custom-UA/1.2.3' });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the default value was here, since I was not sure if we would ever unpin the dependency. By using VERSION from the base package, the version might differ from the actual package version.

(should not make a difference realistically, as we'd likely always have it at ^whatever-the-concrete-exporter-version-is and almost all the logic is in the base package.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My reasoning for this change was that the factory functions come from the base package. Hence if there is any change in these factory functions the version bump will happen in there. This package will have a version bump too if updating the dependency from base but the semantics might be different.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sounds good 👍
Just wanted to elaborate on my initial decision to put it here. I'm fine with it being in the base :)

signalResourcePath: string,
requiredHeaders: Record<string, string>
): OtlpNodeHttpConfiguration {
): Required<OtlpNodeHttpConfiguration> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note for reviewer: I guess OtlpNodeHttpConfiguration is going to be the replacement for the legacy options which are used at the moment. So I kept the userAgent optional for that interface which is the one exposed to the user. The property gets set when calling mergeOtlpNodeHttpConfigurationWithDefaults so that's the reason to use the Required utility type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yep, it was a blunder on my part that I did not use the Required type from the get-go.
+1 for that change.


export function createOtlpHttpExportDelegate<Internal, Response>(
options: OtlpNodeHttpConfiguration,
options: Required<OtlpNodeHttpConfiguration>,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note for reviewer: I have a doubt here. Is this function meant to be called by users? If so I should mark this PR as a breaking change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please correct me if I'm wrong. The HttpRequestParameters type could make the added userAgent property optional, right? Then there wouldn't be a need to use the Required<...> utility.

I could understand if the desire is to avoid optional params for (mostly) internal types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

At the time of doing the changes it seemed a good option to resolve the user-agent value when merging option values from different sources (user, environment & default).
Making it optional here it means each transport implementation is responsible to set the default agent and prepend the userAgent option if passed. But now I think is not a big deal.

I'll push a new commit with the required changes,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Commit 657adee sets the property optional and moves the resolution of the final value in the transport implementation.

To be in sync I did the same in GRPC exporter. Commit 581565e

},
new http.Agent(),
Buffer.from([1, 2, 3]),
// TODO: the `onDone` callback is called twice because there are two error handlers
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note for reviewer: this is a question for the code owner. Is the callback meat to be called twice upon error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's not supposed to, no.

Can be tackled separate issue:
We should change it to be promise-based like the transport to simplify it. That would also solve the problem here as only the first resolve/reject counts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've created #5990. We can triage it in the next SIG


export function createOtlpHttpExportDelegate<Internal, Response>(
options: OtlpNodeHttpConfiguration,
options: Required<OtlpNodeHttpConfiguration>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please correct me if I'm wrong. The HttpRequestParameters type could make the added userAgent property optional, right? Then there wouldn't be a need to use the Required<...> utility.

I could understand if the desire is to avoid optional params for (mostly) internal types.

@david-luna david-luna requested a review from trentm September 23, 2025 14:34
sendUserAgent = opts.headers['User-Agent'];
return http.request(opts, cb).destroy();
};
let sendUserAgent: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: perhaps call it sentUserAgent, because IIUC this is about recording the user-agent that was sent in the client request.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done in 2025ed6

super(
createOtlpHttpExportDelegate(
convertLegacyHttpOptions(config, 'LOGS', 'v1/logs', {
'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the default value was here, since I was not sure if we would ever unpin the dependency. By using VERSION from the base package, the version might differ from the actual package version.

(should not make a difference realistically, as we'd likely always have it at ^whatever-the-concrete-exporter-version-is and almost all the logic is in the base package.)

signalResourcePath: string,
requiredHeaders: Record<string, string>
): OtlpNodeHttpConfiguration {
): Required<OtlpNodeHttpConfiguration> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yep, it was a blunder on my part that I did not use the Required type from the get-go.
+1 for that change.

},
new http.Agent(),
Buffer.from([1, 2, 3]),
// TODO: the `onDone` callback is called twice because there are two error handlers
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's not supposed to, no.

Can be tackled separate issue:
We should change it to be promise-based like the transport to simplify it. That would also solve the problem here as only the first resolve/reject counts.

@david-luna david-luna requested a review from pichlermarc October 6, 2025 17:16
Copy link
Copy Markdown
Member

@pichlermarc pichlermarc left a comment

Choose a reason for hiding this comment

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

looks good, just a few nits 🙂

super(
createOtlpHttpExportDelegate(
convertLegacyHttpOptions(config, 'LOGS', 'v1/logs', {
'User-Agent': `OTel-OTLP-Exporter-JavaScript/${VERSION}`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sounds good 👍
Just wanted to elaborate on my initial decision to put it here. I'm fine with it being in the base :)

@david-luna david-luna added this pull request to the merge queue Oct 8, 2025
Merged via the queue into open-telemetry:main with commit f85cee1 Oct 8, 2025
25 checks passed
@david-luna david-luna deleted the exporters-user-agent-option branch October 8, 2025 19:12
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.

[grpc-exporter] user-agent header is not properly set [exporters] config option to extend user agent header in exporters

3 participants