Skip to content

feat: Add OpenTelemetry environment variable and options configuration helpers.#17524

Draft
chalmerlowe wants to merge 1 commit into
mainfrom
feat/otel-prototype
Draft

feat: Add OpenTelemetry environment variable and options configuration helpers.#17524
chalmerlowe wants to merge 1 commit into
mainfrom
feat/otel-prototype

Conversation

@chalmerlowe

Copy link
Copy Markdown
Contributor

This PR introduces a foundational configuration layer for integrating OpenTelemetry (OTel) observability into Google Cloud Python client libraries. It establishes the google.api_core.observability module, which is responsible for resolving telemetry configuration settings in a standardized, hierarchical manner.

Intent & Motivation

To provide a consistent observability experience across all Google Cloud clients and languages, we are standardizing how telemetry signals (traces, metrics, logs) are enabled or disabled. This module parses and resolves configuration from multiple sources, falling back gracefully, so that users can control telemetry globally or on a per-service basis.

Changes Included

  • Configuration Resolution Logic: Added the is_signal_enabled function in the new observability package to resolve telemetry enablement settings based on a strict precedence hierarchy:
    1. Programmatic client_options configuration.
    2. Language and Service-specific environment variables (e.g., GOOGLE_CLOUD_PYTHON_TRANSLATE_TRACES_ENABLED).
    3. Language-wide global environment variables.
    4. Cross-language service-specific environment variables.
    5. Cross-language global environment variables.
  • Experimental Fallbacks: Implemented fallback logic that automatically checks for EXPERIMENTAL prefixes within the environment variable definitions to support early-stage adoption and rollout phases safely.
  • Legacy Variable Support: Added backwards-compatibility warnings for legacy environment variables to facilitate smooth migrations to the standardized cross-language schema.
  • Test Coverage: Provided comprehensive data-driven test coverage using pytest for all precedence rules and environmental scenarios.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces observability environment variable and client options resolution helpers under a new observability module, including the is_signal_enabled function and corresponding unit tests. Feedback is provided to improve the resolution of client options by avoiding direct __dict__ access (which fails for classes using __slots__ or properties) in favor of getattr, and replacing rstrip('s') with a safer suffix removal method to prevent unintended character stripping.

Comment on lines +58 to +69
options_dict = (
client_options
if isinstance(client_options, dict)
else getattr(client_options, "__dict__", {})
)
option_key = f"enable_{signal_type.lower()}"
provider_key = f"{signal_type.rstrip('s').lower()}_provider"

if options_dict.get(option_key) is not None:
return bool(options_dict.get(option_key))
if options_dict.get(provider_key) is not None:
return True

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.

medium

There are two areas of improvement in how client options and signal types are resolved:

  1. Direct Attribute Access via getattr: Accessing __dict__ directly is an implementation detail that bypasses properties (defined with @property) and fails for classes using __slots__. Using getattr(client_options, ...) is the standard, robust way to retrieve attributes from arbitrary objects in Python.
  2. Avoid rstrip for Suffix Removal: rstrip('s') is a common Python gotcha. It removes all trailing occurrences of the character 's' (e.g., 'address' becomes 'addre'). Using slicing or removesuffix is safer.
        option_key = f"enable_{signal_type.lower()}"
        sig_lower = signal_type.lower()
        sig_singular = sig_lower[:-1] if sig_lower.endswith("s") else sig_lower
        provider_key = f"{sig_singular}_provider"

        if isinstance(client_options, dict):
            val = client_options.get(option_key)
            provider = client_options.get(provider_key)
        else:
            val = getattr(client_options, option_key, None)
            provider = getattr(client_options, provider_key, None)

        if val is not None:
            return bool(val)
        if provider is not None:
            return True

if isinstance(client_options, dict)
else getattr(client_options, "__dict__", {})
)
option_key = f"enable_{signal_type.lower()}"

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 TO SELF: remove references to option_key throughout.

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.

1 participant