Skip to content

Update OtlpGrpcClientOptions to populate options for gRPC client sharing - #4249

Open
ML-dev-crypto wants to merge 8 commits into
open-telemetry:mainfrom
ML-dev-crypto:fix-4239-otlp-grpc-client-options
Open

Update OtlpGrpcClientOptions to populate options for gRPC client sharing#4249
ML-dev-crypto wants to merge 8 commits into
open-telemetry:mainfrom
ML-dev-crypto:fix-4239-otlp-grpc-client-options

Conversation

@ML-dev-crypto

Copy link
Copy Markdown

Fixes #4239

OtlpGrpcClientOptions previously only had a = default constructor,
leaving every option at null/zero regardless of environment variables
or otel-spec defaults. This made it impossible to build a standalone
OtlpGrpcClient with spec-compliant defaults for sharing across the
trace/metric/log exporters, as described in the issue.

Changes:

  • Added signal-independent (no TRACES_/METRICS_/LOGS_ prefix) env-var
    accessors to otlp_environment.h, e.g. GetOtlpDefaultGrpcClientEndpoint().
    These are inline, defined in the header rather than
    otlp_environment.cc, so that otlp_grpc_client (a lean transport-only
    target) doesn't need to depend on otlp_recordable and its
    sdk/logs, sdk/metrics, sdk/trace dependencies just to read an env var.
  • OtlpGrpcClientOptions now has a real default constructor (in a new
    otlp_grpc_client_options.cc) that populates from those accessors,
    and a void* constructor that skips populating, for parity with the
    existing per-signal options classes.
  • OtlpGrpcExporterOptions / OtlpGrpcMetricExporterOptions /
    OtlpGrpcLogRecordExporterOptions each gained a constructor taking a
    const OtlpGrpcClientOptions&, copying the client-fixed fields
    (endpoint, SSL/mTLS fields, credentials, user_agent, channel_arguments,
    max_threads, compression, retry policy) and overriding only the fields
    that may still differ per signal (timeout, metadata,
    max_concurrent_requests) — per the table in the issue.
  • Build files updated for the new source file and one new Bazel dep
    (//sdk/src/common:env_variables); no new library targets, no change
    to the existing dependency graph shape.
  • New unit test otlp_grpc_client_options_test.cc covering: default
    env-var population, the void* no-defaults path (including the
    derived classes' void* constructors), fixed-field copying from a
    shared client, and signal-specific env-var precedence over the
    shared client's generic value.

Tested: compiled and unit-tested with both GCC/libstdc++ and
MSVC/STL; clang-format applied.

@ML-dev-crypto
ML-dev-crypto requested a review from a team as a code owner July 15, 2026 09:51
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 15, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: ML-dev-crypto / name: Ansh Rai (4998b72)

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.36620% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.73%. Comparing base (5a5fbfd) to head (adf559b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
exporters/otlp/src/otlp_environment.cc 93.89% 14 Missing ⚠️
.../otlp/src/otlp_grpc_log_record_exporter_options.cc 88.89% 1 Missing ⚠️
...ters/otlp/src/otlp_grpc_metric_exporter_options.cc 88.89% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4249      +/-   ##
==========================================
+ Coverage   81.62%   81.73%   +0.11%     
==========================================
  Files         493      494       +1     
  Lines       19454    19533      +79     
==========================================
+ Hits        15878    15963      +85     
+ Misses       3576     3570       -6     
Files with missing lines Coverage Δ
...de/opentelemetry/exporters/otlp/otlp_environment.h 100.00% <ø> (ø)
...elemetry/exporters/otlp/otlp_grpc_client_options.h 100.00% <ø> (ø)
...emetry/exporters/otlp/otlp_grpc_exporter_options.h 100.00% <ø> (ø)
...rters/otlp/otlp_grpc_log_record_exporter_options.h 100.00% <ø> (ø)
...exporters/otlp/otlp_grpc_metric_exporter_options.h 100.00% <ø> (ø)
exporters/otlp/src/otlp_grpc_client_options.cc 100.00% <100.00%> (ø)
exporters/otlp/src/otlp_grpc_exporter_options.cc 100.00% <100.00%> (+19.24%) ⬆️
.../otlp/src/otlp_grpc_log_record_exporter_options.cc 97.15% <88.89%> (+16.38%) ⬆️
...ters/otlp/src/otlp_grpc_metric_exporter_options.cc 97.15% <88.89%> (+16.38%) ⬆️
exporters/otlp/src/otlp_environment.cc 90.24% <93.89%> (-0.82%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread exporters/otlp/src/otlp_grpc_client_options.cc
Comment thread exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h Outdated
Comment thread exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h Outdated
Comment thread exporters/otlp/include/opentelemetry/exporters/otlp/otlp_environment.h Outdated
Comment thread exporters/otlp/src/otlp_grpc_exporter_options.cc Outdated
Comment thread exporters/otlp/src/otlp_grpc_exporter_options.cc Outdated
Comment thread exporters/otlp/src/otlp_grpc_log_record_exporter_options.cc Outdated
@ML-dev-crypto

ML-dev-crypto commented Jul 16, 2026

Copy link
Copy Markdown
Author

Thanks for the thorough review, @owent! I'll address all of these comments in a follow-up commit, including the export annotation, constructor initialization cleanup, shared environment variable definitions, removal of the duplicated header parsing logic, moving the new environment helpers out of the header, reusing the base constructor for shared client initialization, and preserving the shared client's max_concurrent_requests value. I'll push everything together.

Comment thread exporters/otlp/src/otlp_environment.cc Outdated
Comment thread exporters/otlp/src/otlp_environment.cc Outdated
{
std::string endpoint = GetOtlpDefaultGrpcClientEndpoint();

if (endpoint.substr(0, 6) == "https:")

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.

Just a suggestion, use 'nostd::string_view' here can slightly improve performance.

@owent owent left a comment

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.

Just a few minor non-blocking issues left — otherwise LGTM. Could you please resolve the conflicts?

Fixes open-telemetry#4239 OtlpGrpcClientOptions previously only had a = default constructor, leaving every option at null/zero. Add a real default constructor that populates spec-compliant defaults from generic (signal-independent) OTEL_EXPORTER_OTLP_* environment variables, a void* constructor that skips defaults, and a constructor on each of OtlpGrpcExporterOptions, OtlpGrpcMetricExporterOptions, and OtlpGrpcLogRecordExporterOptions that builds from a shared OtlpGrpcClientOptions, copying client-fixed fields and overriding only timeout, metadata, and max_concurrent_requests per signal.

Signed-off-by: Ansh Rai <anshrai331@gmail.com>
Signed-off-by: Ansh Rai <anshrai331@gmail.com>
Signed-off-by: Ansh Rai <anshrai331@gmail.com>
Signed-off-by: Ansh Rai <anshrai331@gmail.com>
@ML-dev-crypto
ML-dev-crypto force-pushed the fix-4239-otlp-grpc-client-options branch from 3afcbb1 to 2ca8d8e Compare July 19, 2026 18:46
@ML-dev-crypto

Copy link
Copy Markdown
Author

@owent Thanks! Addressed the remaining comments, rebased onto the latest main, resolved the merge conflict, and pushed the updates.

@ML-dev-crypto

Copy link
Copy Markdown
Author

@owent
After rebasing, the CI is failing with linker errors because the generic GetOtlpDefaultGrpcClient*() helpers are currently defined in otlp_environment.cc, which isn't linked into the otlp_grpc_client target. I looked into a few possible approaches (moving the helpers, introducing a shared utility, etc.), but each would require changes to the target structure. Could you please take a look and let me know which direction you would recommend?

@owent

owent commented Jul 20, 2026

Copy link
Copy Markdown
Member

@owent After rebasing, the CI is failing with linker errors because the generic GetOtlpDefaultGrpcClient*() helpers are currently defined in otlp_environment.cc, which isn't linked into the otlp_grpc_client target. I looked into a few possible approaches (moving the helpers, introducing a shared utility, etc.), but each would require changes to the target structure. Could you please take a look and let me know which direction you would recommend?

Could you please try to add OPENTELEMETRY_EXPORT for these function both in .h and .cc files?

Signed-off-by: Ansh Rai <anshrai331@gmail.com>
@ML-dev-crypto

Copy link
Copy Markdown
Author

Thanks for the suggestion! I've updated the PR by adding OPENTELEMETRY_EXPORT to the relevant declarations and definitions in both the header and source files, and pushed the changes.

metadata = GetOtlpDefaultTracesHeaders();

#ifdef ENABLE_ASYNC_EXPORT
max_concurrent_requests = client_options.max_concurrent_requests;

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.

One shared-client edge case: the gRPC client only uses this when its async data is first created. With one client shared by trace/metric/log exporters, the first exporter wins and later per-signal values are ignored.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I agree this is a limitation of the shared async client rather than the option-copying logic in this PR. I'd prefer to keep this PR focused on the current fixes, but I'm happy to address it in a follow-up PR .

OtlpGrpcExporterOptions::OtlpGrpcExporterOptions(const OtlpGrpcClientOptions &client_options)
: OtlpGrpcClientOptions(client_options)
{
timeout = GetOtlpDefaultTracesTimeout();

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.

I think this re-reads env vars instead of falling back to client_options.timeout. If the shared client timeout was set programmatically and no trace-specific env var exists, this drops that value and goes back to the default.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for catching this! I've updated the implementation to preserve client_options.timeout unless a signal-specific timeout override is present, and added a regression test covering the programmatic shared-client timeout case.

Comment thread exporters/otlp/src/otlp_grpc_client_options.cc
Signed-off-by: Ansh Rai <anshrai331@gmail.com>
{
timeout = signal_timeout;
}
metadata = GetOtlpDefaultTracesHeaders();

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.

The base-class copy on line 52 already preserves client_options.metadata, but line 59 immediately discards it and rebuilds the map only from OTLP header environment variables.

With no header environment variables:

OtlpGrpcClientOptions client(nullptr);
client.metadata.emplace("authorization", "credential");
OtlpGrpcExporterOptions trace(client);

trace.metadata becomes empty, so programmatic authentication is silently lost.

Could we preserve the copied map and overlay only OTEL_EXPORTER_OTLP_TRACES_HEADERS onto it, with signal-specific values replacing same-name client values? That matches the existing generic-then-signal header precedence. Please apply the same fix to metrics and logs, and add regression tests for preservation without a signal variable plus merge/override behavior when one is present.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for pointing this out. I’ll preserve the metadata copied from client_options and overlay the signal-specific headers on top, with signal-specific values taking precedence for the same key. I’ll apply the same fix to traces, metrics, and logs and add the regression tests.

@owent

owent commented Jul 30, 2026

Copy link
Copy Markdown
Member

About the undefined reference symbols. The symbols should be declared as visibility=defaut on Unix like system and __declspec(dllexport)/__attribute__((__dllexport__)) for the target which implement it and declare as __declspec(dllimport)/__attribute__((__dllimport__)) for other targets on Windows.
Here is a solution:

  • Using project_build_tools_set_shared_library_declaration() and project_build_tools_set_static_library_declaration() to declare symbol rule in cmake.
if ("${OPENTELEMETRY_OTLP_TARGETS_LIB_TYPE}" STREQUAL "STATIC" OR NOT BUILD_SHARED_LIBS)
  project_build_tools_set_shared_library_declaration(OPENTELEMETRY_OTLP_RECORDABLE_API
                                                     opentelemetry_otlp_recordable)
else()
  project_build_tools_set_static_library_declaration(OPENTELEMETRY_OTLP_RECORDABLE_API
                                                     opentelemetry_otlp_recordable)
endif()

if ("${OPENTELEMETRY_OTLP_GRPC_CLIENT_LIB_TYPE}" STREQUAL "STATIC" OR NOT BUILD_SHARED_LIBS)
  project_build_tools_set_shared_library_declaration(OPENTELEMETRY_OTLP_GRPC_CLIENT_API
                                                     opentelemetry_exporter_otlp_grpc_client)
else()
  project_build_tools_set_static_library_declaration(OPENTELEMETRY_OTLP_GRPC_CLIENT_API
                                                     opentelemetry_exporter_otlp_grpc_client)
endif()
  • Declared macro in .h files for bazel(which will always be built as static library)

In otlp_environment.h

#ifndef OPENTELEMETRY_OTLP_RECORDABLE_API
#  if defined(__clang__)
#    define OPENTELEMETRY_OTLP_RECORDABLE_API __attribute__((visibility("default")))
#  elif defined(__GNUC__)
#    define OPENTELEMETRY_OTLP_RECORDABLE_API __attribute__((visibility("default")))
#  else
#    define OPENTELEMETRY_OTLP_RECORDABLE_API
#  endif
#endif

And in otlp_grpc_client_options.h

#ifndef OPENTELEMETRY_OTLP_GRPC_CLIENT_API
#  if defined(__clang__)
#    define OPENTELEMETRY_OTLP_GRPC_CLIENT_API __attribute__((visibility("default")))
#  elif defined(__GNUC__)
#    define OPENTELEMETRY_OTLP_GRPC_CLIENT_API __attribute__((visibility("default")))
#  else
#    define OPENTELEMETRY_OTLP_GRPC_CLIENT_API
#  endif
#endif
  • At last, do not use OPENTELEMETRY_EXPORT any more, use OPENTELEMETRY_OTLP_RECORDABLE_API to declare public functions in opentelemetry_otlp_recordable and use use OPENTELEMETRY_OTLP_GRPC_CLIENT_API to declare public functions in opentelemetry_exporter_otlp_grpc_client.

@dbarker
dbarker requested a review from owent August 5, 2026 13:24
@ML-dev-crypto

Copy link
Copy Markdown
Author

@owent Should the project_build_tools_set_*_library_declaration() calls target opentelemetry_otlp_common instead? And should I keep OPENTELEMETRY_OTLP_RECORDABLE_API as the macro name, or rename it to OPENTELEMETRY_OTLP_COMMON_API to match the target?

@owent

owent commented Aug 9, 2026

Copy link
Copy Markdown
Member

@owent Should the project_build_tools_set_*_library_declaration() calls target opentelemetry_otlp_common instead? And should I keep OPENTELEMETRY_OTLP_RECORDABLE_API as the macro name, or rename it to OPENTELEMETRY_OTLP_COMMON_API to match the target?

The new target (opentelemetry_otlp_common) is used and I think the macro name should be OPENTELEMETRY_OTLP_COMMON_API now.

BTW: I raise a PR in otel-cpp-contrib repo to use the similar way to export symbols. Which may be helpful: open-telemetry/opentelemetry-cpp-contrib#646

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.

Update OtlpGrpcClientOptions to populate options for gRPC client sharing

5 participants