|
| 1 | +# Copyright 2026 Google LLC |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""Manages OpenTelemetry metrics instruments and gating for GCS client.""" |
| 16 | + |
| 17 | +import logging |
| 18 | +import os |
| 19 | +from typing import Any, Dict, Optional |
| 20 | + |
| 21 | +from google.cloud.storage.version import __version__ |
| 22 | + |
| 23 | +logger = logging.getLogger(__name__) |
| 24 | + |
| 25 | +# --------------------------------------------------------------------------- |
| 26 | +# 1. Hidden Development Gate |
| 27 | +# --------------------------------------------------------------------------- |
| 28 | +# Must remain False in production branches. |
| 29 | +# Only enabled during active development and test runs. |
| 30 | +_ENABLE_METRICS_DEV_GATE = False |
| 31 | + |
| 32 | +# --------------------------------------------------------------------------- |
| 33 | +# 2. Standardized Configuration and Environment Variable Names |
| 34 | +# --------------------------------------------------------------------------- |
| 35 | +ENABLE_OTEL_METRICS_ENV_VAR = "GCP_STORAGE_PYTHON_ENABLE_OTEL_METRICS" |
| 36 | +ENABLE_OTEL_DEBUG_METRICS_ENV_VAR = "GCP_STORAGE_PYTHON_ENABLE_OTEL_DEBUG_METRICS" |
| 37 | + |
| 38 | +_DEFAULT_ENABLE_METRICS = False |
| 39 | +_DEFAULT_ENABLE_DEBUG_METRICS = False |
| 40 | + |
| 41 | +# --------------------------------------------------------------------------- |
| 42 | +# 3. Optional OpenTelemetry Dependency Check |
| 43 | +# --------------------------------------------------------------------------- |
| 44 | +try: |
| 45 | + from opentelemetry import metrics |
| 46 | + |
| 47 | + HAS_OPENTELEMETRY_METRICS = True |
| 48 | +except ImportError: |
| 49 | + HAS_OPENTELEMETRY_METRICS = False |
| 50 | + logger.debug( |
| 51 | + "OpenTelemetry metrics package (opentelemetry-api >= 1.12.0) is not " |
| 52 | + "installed. GCS client metrics are disabled." |
| 53 | + ) |
| 54 | + |
| 55 | + |
| 56 | +_TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"} |
| 57 | +_FALSY_ENV_VALUES = {"0", "false", "no", "off"} |
| 58 | + |
| 59 | + |
| 60 | +def _parse_bool_env(name: str, default: bool = False) -> bool: |
| 61 | + """Parses a boolean from an environment variable.""" |
| 62 | + val = os.environ.get(name) |
| 63 | + if val is None: |
| 64 | + return default |
| 65 | + normalized = val.strip().lower() |
| 66 | + if not normalized: |
| 67 | + return default |
| 68 | + if normalized in _TRUTHY_ENV_VALUES: |
| 69 | + return True |
| 70 | + if normalized in _FALSY_ENV_VALUES: |
| 71 | + return False |
| 72 | + logger.warning( |
| 73 | + "Unrecognized boolean value %r for environment variable %s; using default %s.", |
| 74 | + val, |
| 75 | + name, |
| 76 | + default, |
| 77 | + ) |
| 78 | + return default |
| 79 | + |
| 80 | + |
| 81 | +def is_metrics_enabled(enable_metrics: Optional[bool] = None) -> bool: |
| 82 | + """Evaluates whether standard GCS metrics should be recorded. |
| 83 | +
|
| 84 | + Args: |
| 85 | + enable_metrics: Optional boolean configured on the client instance. |
| 86 | + Takes precedence over the environment variable if specified. |
| 87 | +
|
| 88 | + Returns: |
| 89 | + bool: True if metrics recording is enabled, False otherwise. |
| 90 | + """ |
| 91 | + if enable_metrics is not None and not isinstance(enable_metrics, bool): |
| 92 | + raise TypeError("enable_metrics must be a boolean or None.") |
| 93 | + |
| 94 | + if not HAS_OPENTELEMETRY_METRICS: |
| 95 | + return False |
| 96 | + |
| 97 | + if not _ENABLE_METRICS_DEV_GATE: |
| 98 | + return False |
| 99 | + |
| 100 | + if enable_metrics is not None: |
| 101 | + return enable_metrics |
| 102 | + |
| 103 | + return _parse_bool_env(ENABLE_OTEL_METRICS_ENV_VAR, _DEFAULT_ENABLE_METRICS) |
| 104 | + |
| 105 | + |
| 106 | +def is_debug_metrics_enabled( |
| 107 | + enable_debug_metrics: Optional[bool] = None, |
| 108 | +) -> bool: |
| 109 | + """Evaluates whether high-frequency debug metrics should be recorded. |
| 110 | +
|
| 111 | + Args: |
| 112 | + enable_debug_metrics: Optional boolean configured on the client |
| 113 | + instance. Takes precedence over the environment variable if |
| 114 | + specified. |
| 115 | +
|
| 116 | + Returns: |
| 117 | + bool: True if debug metrics recording is enabled, False otherwise. |
| 118 | + """ |
| 119 | + if enable_debug_metrics is not None and not isinstance(enable_debug_metrics, bool): |
| 120 | + raise TypeError("enable_debug_metrics must be a boolean or None.") |
| 121 | + |
| 122 | + if not HAS_OPENTELEMETRY_METRICS: |
| 123 | + return False |
| 124 | + |
| 125 | + if not _ENABLE_METRICS_DEV_GATE: |
| 126 | + return False |
| 127 | + |
| 128 | + if enable_debug_metrics is not None: |
| 129 | + return enable_debug_metrics |
| 130 | + |
| 131 | + return _parse_bool_env( |
| 132 | + ENABLE_OTEL_DEBUG_METRICS_ENV_VAR, _DEFAULT_ENABLE_DEBUG_METRICS |
| 133 | + ) |
| 134 | + |
| 135 | + |
| 136 | +# --------------------------------------------------------------------------- |
| 137 | +# 4. Standard Common Attributes & Meter Provider |
| 138 | +# --------------------------------------------------------------------------- |
| 139 | +_COMMON_ATTRIBUTES: Dict[str, Any] = { |
| 140 | + "gcp.client.service": "storage", |
| 141 | + "gcp.client.version": __version__, |
| 142 | + "gcp.client.repo": "googleapis/google-cloud-python", |
| 143 | + "gcp.client.artifact": "google-cloud-storage", |
| 144 | +} |
| 145 | + |
| 146 | + |
| 147 | +def get_common_attributes() -> Dict[str, Any]: |
| 148 | + """Returns a copy of standard GCS client attributes for metrics.""" |
| 149 | + return _COMMON_ATTRIBUTES.copy() |
| 150 | + |
| 151 | + |
| 152 | +def get_meter( |
| 153 | + enable_metrics: Optional[bool] = None, |
| 154 | + enable_debug_metrics: Optional[bool] = None, |
| 155 | + meter_provider: Optional[Any] = None, |
| 156 | +) -> Optional[Any]: |
| 157 | + """Returns the OpenTelemetry Meter for Google Cloud Storage. |
| 158 | +
|
| 159 | + Args: |
| 160 | + enable_metrics: Optional boolean configured on the client instance for |
| 161 | + standard metrics. |
| 162 | + enable_debug_metrics: Optional boolean configured on the client |
| 163 | + instance for debug metrics. |
| 164 | + meter_provider: Optional custom OpenTelemetry MeterProvider instance. |
| 165 | + Defaults to the global MeterProvider if None. |
| 166 | +
|
| 167 | + Returns: |
| 168 | + Optional[Any]: The OpenTelemetry Meter instance if standard or debug |
| 169 | + metrics are enabled, or None otherwise. |
| 170 | + """ |
| 171 | + if not ( |
| 172 | + is_metrics_enabled(enable_metrics) |
| 173 | + or is_debug_metrics_enabled(enable_debug_metrics) |
| 174 | + ): |
| 175 | + return None |
| 176 | + return metrics.get_meter( |
| 177 | + "google.cloud.storage", |
| 178 | + __version__, |
| 179 | + meter_provider=meter_provider, |
| 180 | + ) |
0 commit comments