diff --git a/packages/google-cloud-storage/google/cloud/storage/_helpers.py b/packages/google-cloud-storage/google/cloud/storage/_helpers.py index ffa9fe177ea3..04039971ef41 100644 --- a/packages/google-cloud-storage/google/cloud/storage/_helpers.py +++ b/packages/google-cloud-storage/google/cloud/storage/_helpers.py @@ -34,6 +34,7 @@ from google.cloud.storage._opentelemetry_tracing import ( create_trace_span as _base_create_trace_span, + _is_bucket_metadata_disabled, ) from google.cloud.storage.constants import _DEFAULT_TIMEOUT from google.cloud.storage.retry import ( @@ -156,6 +157,7 @@ def create_trace_span_helper(client, bucket_name, name, attributes=None, **kwarg and client and hasattr(client, "_bucket_metadata_cache") and client._bucket_metadata_cache + and not _is_bucket_metadata_disabled() ): try: if name in ( diff --git a/packages/google-cloud-storage/google/cloud/storage/_http.py b/packages/google-cloud-storage/google/cloud/storage/_http.py index ca7c90e2061a..bfe2bf3843af 100644 --- a/packages/google-cloud-storage/google/cloud/storage/_http.py +++ b/packages/google-cloud-storage/google/cloud/storage/_http.py @@ -27,6 +27,7 @@ HAS_OPENTELEMETRY, create_trace_span, enable_otel_traces, + _is_bucket_metadata_disabled, ) logger = logging.getLogger(__name__) @@ -88,6 +89,7 @@ def api_request(self, *args, **kwargs): and enable_otel_traces and hasattr(client, "_bucket_metadata_cache") and client._bucket_metadata_cache + and not _is_bucket_metadata_disabled() ): path = kwargs.get("path") or "" match = re.search(r"/b/([^/?#]+)", path) diff --git a/packages/google-cloud-storage/google/cloud/storage/_opentelemetry_tracing.py b/packages/google-cloud-storage/google/cloud/storage/_opentelemetry_tracing.py index 173fa090fcb5..1d9e4b88270b 100644 --- a/packages/google-cloud-storage/google/cloud/storage/_opentelemetry_tracing.py +++ b/packages/google-cloud-storage/google/cloud/storage/_opentelemetry_tracing.py @@ -27,6 +27,7 @@ ENABLE_OTEL_TRACES_ENV_VAR = "ENABLE_GCS_PYTHON_CLIENT_OTEL_TRACES" _DEFAULT_ENABLE_OTEL_TRACES_VALUE = False +DISABLE_BUCKET_MD_ENV_VAR = "DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA" def _parse_bool_env(name: str, default: bool = False) -> bool: @@ -36,11 +37,16 @@ def _parse_bool_env(name: str, default: bool = False) -> bool: return str(val).strip().lower() in {"1", "true", "yes", "on"} +def _is_bucket_metadata_disabled() -> bool: + return _parse_bool_env(DISABLE_BUCKET_MD_ENV_VAR, False) + + enable_otel_traces = _parse_bool_env( ENABLE_OTEL_TRACES_ENV_VAR, _DEFAULT_ENABLE_OTEL_TRACES_VALUE ) logger = logging.getLogger(__name__) + try: from opentelemetry import trace diff --git a/packages/google-cloud-storage/tests/system/test_aco_observability.py b/packages/google-cloud-storage/tests/system/test_aco_observability.py index b1de4ae87025..280a7d05badb 100644 --- a/packages/google-cloud-storage/tests/system/test_aco_observability.py +++ b/packages/google-cloud-storage/tests/system/test_aco_observability.py @@ -549,3 +549,40 @@ def monitored_update(*args, **kwargs): assert attrs["gcp.resource.destination.location"] == "global" finally: storage_client._bucket_metadata_cache.update_cache = original_update + + +@pytest.mark.parametrize("env_value", ["true", "1", "yes", "on"]) +def test_disable_bucket_md_env_flag( + storage_client, exporter, buckets_to_delete, monkeypatch, env_value +): + """Verifies that setting DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA to a truthy value disables GCS + destination annotations, even on cache hits.""" + # Clear cache and OTel exporter logs + storage_client._bucket_metadata_cache.clear() + exporter.clear() + + bucket_name = _helpers.unique_name("aco-disable") + bucket = storage_client.bucket(bucket_name) + storage_client.create_bucket(bucket) + buckets_to_delete.append(bucket) + + blob_name = "test_blob.txt" + blob = bucket.blob(blob_name) + blob.upload_from_string("hello") + + # Warm cache directly via GCS creation warming (client.create_bucket already primes the cache) + assert storage_client._bucket_metadata_cache.get(bucket_name) is not None + + # Enable the DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA environment variable using monkeypatch + monkeypatch.setenv("DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA", env_value) + + # Download (normally would be a cache hit with GCS annotations) + blob.download_as_bytes() + + # Verify that ACO attributes are NOT present in the OTel span! + spans = exporter.get_finished_spans() + dl_spans = [s for s in spans if s.name == "Storage.Blob.downloadAsBytes"] + assert len(dl_spans) == 1 + attrs = dl_spans[0].attributes + assert "gcp.resource.destination.id" not in attrs + assert "gcp.resource.destination.location" not in attrs diff --git a/packages/google-cloud-storage/tests/unit/test__opentelemetry_tracing.py b/packages/google-cloud-storage/tests/unit/test__opentelemetry_tracing.py index 9a17281906e7..70722bb0b0f8 100644 --- a/packages/google-cloud-storage/tests/unit/test__opentelemetry_tracing.py +++ b/packages/google-cloud-storage/tests/unit/test__opentelemetry_tracing.py @@ -321,3 +321,34 @@ def test__parse_bool_env(monkeypatch, env_value, default, expected): result = _opentelemetry_tracing._parse_bool_env(env_var_name, default) assert result is expected + + +@pytest.mark.parametrize( + "env_value, expected", + [ + # Test default (not set) + (None, False), + # Test truthy values + ("true", True), + ("1", True), + ("yes", True), + ("on", True), + ("TRUE", True), + (" Yes ", True), + # Test falsy values + ("false", False), + ("0", False), + ("no", False), + ("off", False), + ("any_other_string", False), + ("", False), + ], +) +def test__is_bucket_metadata_disabled(monkeypatch, env_value, expected): + env_var_name = "DISABLE_GCS_PYTHON_CLIENT_OTEL_BUCKET_METADATA" + if env_value is not None: + monkeypatch.setenv(env_var_name, str(env_value)) + else: + monkeypatch.delenv(env_var_name, raising=False) + + assert _opentelemetry_tracing._is_bucket_metadata_disabled() is expected