| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 8b6f154 commit d37fb80
16 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,7 @@ | |||
| 20 | 20 | import time | |
| 21 | 21 | import base64 | |
| 22 | 22 | import threading | |
| 23 | + import logging | ||
| 23 | 24 | ||
| 24 | 25 | from google.protobuf.struct_pb2 import ListValue | |
| 25 | 26 | from google.protobuf.struct_pb2 import Value | |
@@ -29,16 +30,27 @@ | |||
| 29 | 30 | from google.api_core import datetime_helpers | |
| 30 | 31 | from google.api_core.exceptions import Aborted | |
| 31 | 32 | from google.cloud._helpers import _date_from_iso8601_date | |
| 32 | - from google.cloud.spanner_v1 import TypeCode | ||
| 33 | - from google.cloud.spanner_v1 import ExecuteSqlRequest | ||
| 34 | - from google.cloud.spanner_v1 import JsonObject, Interval | ||
| 35 | - from google.cloud.spanner_v1 import TransactionOptions | ||
| 33 | + from google.cloud.spanner_v1.types import ExecuteSqlRequest | ||
| 34 | + from google.cloud.spanner_v1.types import TransactionOptions | ||
| 35 | + from google.cloud.spanner_v1.data_types import JsonObject, Interval | ||
| 36 | 36 | from google.cloud.spanner_v1.request_id_header import with_request_id | |
| 37 | + from google.cloud.spanner_v1.types import TypeCode | ||
| 38 | + | ||
| 37 | 39 | from google.rpc.error_details_pb2 import RetryInfo | |
| 38 | 40 | ||
| 39 | 41 | try: | |
| 40 | 42 | from opentelemetry.propagate import inject | |
| 41 | 43 | from opentelemetry.propagators.textmap import Setter | |
| 44 | + from opentelemetry.semconv.resource import ResourceAttributes | ||
| 45 | + from opentelemetry.resourcedetector import gcp_resource_detector | ||
| 46 | + from opentelemetry.resourcedetector.gcp_resource_detector import ( | ||
| 47 | + GoogleCloudResourceDetector, | ||
| 48 | + ) | ||
| 49 | + | ||
| 50 | + # Overwrite the requests timeout for the detector. | ||
| 51 | + # This is necessary as the client will wait the full timeout if the | ||
| 52 | + # code is not run in a GCP environment, with the location endpoints available. | ||
| 53 | + gcp_resource_detector._TIMEOUT_SEC = 0.2 | ||
| 42 | 54 | ||
| 43 | 55 | HAS_OPENTELEMETRY_INSTALLED = True | |
| 44 | 56 | except ImportError: | |
@@ -55,6 +67,12 @@ | |||
| 55 | 67 | + "numeric has a whole component with precision {}" | |
| 56 | 68 | ) | |
| 57 | 69 | ||
| 70 | + GOOGLE_CLOUD_REGION_GLOBAL = "global" | ||
| 71 | + | ||
| 72 | + log = logging.getLogger(__name__) | ||
| 73 | + | ||
| 74 | + _cloud_region: str = None | ||
| 75 | + | ||
| 58 | 76 | ||
| 59 | 77 | if HAS_OPENTELEMETRY_INSTALLED: | |
| 60 | 78 | ||
@@ -79,6 +97,33 @@ def set(self, carrier: List[Tuple[str, str]], key: str, value: str) -> None: | |||
| 79 | 97 | carrier.append((key, value)) | |
| 80 | 98 | ||
| 81 | 99 | ||
| 100 | + def _get_cloud_region() -> str: | ||
| 101 | + """Get the location of the resource, caching the result. | ||
| 102 | + | ||
| 103 | + Returns: | ||
| 104 | + str: The location of the resource. If OpenTelemetry is not installed, returns a global region. | ||
| 105 | + """ | ||
| 106 | + global _cloud_region | ||
| 107 | + if _cloud_region is not None: | ||
| 108 | + return _cloud_region | ||
| 109 | + | ||
| 110 | + try: | ||
| 111 | + detector = GoogleCloudResourceDetector() | ||
| 112 | + resources = detector.detect() | ||
| 113 | + if ResourceAttributes.CLOUD_REGION in resources.attributes: | ||
| 114 | + _cloud_region = resources.attributes[ResourceAttributes.CLOUD_REGION] | ||
| 115 | + else: | ||
| 116 | + _cloud_region = GOOGLE_CLOUD_REGION_GLOBAL | ||
| 117 | + except Exception as e: | ||
| 118 | + log.warning( | ||
| 119 | + "Failed to detect GCP resource location for Spanner metrics, defaulting to 'global'. Error: %s", | ||
| 120 | + e, | ||
| 121 | + ) | ||
| 122 | + _cloud_region = GOOGLE_CLOUD_REGION_GLOBAL | ||
| 123 | + | ||
| 124 | + return _cloud_region | ||
| 125 | + | ||
| 126 | + | ||
| 82 | 127 | def _try_to_coerce_bytes(bytestring): | |
| 83 | 128 | """Try to coerce a byte string into the right thing based on Python | |
| 84 | 129 | version and whether or not it is base64 encoded. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,6 +21,7 @@ | |||
| 21 | 21 | from google.cloud.spanner_v1 import SpannerClient | |
| 22 | 22 | from google.cloud.spanner_v1 import gapic_version | |
| 23 | 23 | from google.cloud.spanner_v1._helpers import ( | |
| 24 | + _get_cloud_region, | ||
| 24 | 25 | _metadata_with_span_context, | |
| 25 | 26 | ) | |
| 26 | 27 | ||
@@ -75,6 +76,7 @@ def trace_call( | |||
| 75 | 76 | enable_end_to_end_tracing = False | |
| 76 | 77 | ||
| 77 | 78 | db_name = "" | |
| 79 | + cloud_region = None | ||
| 78 | 80 | if session and getattr(session, "_database", None): | |
| 79 | 81 | db_name = session._database.name | |
| 80 | 82 | ||
@@ -88,6 +90,7 @@ def trace_call( | |||
| 88 | 90 | ) | |
| 89 | 91 | db_name = observability_options.get("db_name", db_name) | |
| 90 | 92 | ||
| 93 | + cloud_region = _get_cloud_region() | ||
| 91 | 94 | tracer = get_tracer(tracer_provider) | |
| 92 | 95 | ||
| 93 | 96 | # Set base attributes that we know for every trace created | |
@@ -97,6 +100,7 @@ def trace_call( | |||
| 97 | 100 | "db.instance": db_name, | |
| 98 | 101 | "net.host.name": SpannerClient.DEFAULT_ENDPOINT, | |
| 99 | 102 | OTEL_SCOPE_NAME: TRACER_NAME, | |
| 103 | + "cloud.region": cloud_region, | ||
| 100 | 104 | OTEL_SCOPE_VERSION: TRACER_VERSION, | |
| 101 | 105 | # Standard GCP attributes for OTel, attributes are used for internal purpose and are subjected to change | |
| 102 | 106 | "gcp.client.service": "spanner", | |
@@ -107,6 +111,11 @@ def trace_call( | |||
| 107 | 111 | if extra_attributes: | |
| 108 | 112 | attributes.update(extra_attributes) | |
| 109 | 113 | ||
| 114 | + if "request_options" in attributes: | ||
| 115 | + request_options = attributes.pop("request_options") | ||
| 116 | + if request_options and request_options.request_tag: | ||
| 117 | + attributes["request.tag"] = request_options.request_tag | ||
| 118 | + | ||
| 110 | 119 | if extended_tracing_globally_disabled: | |
| 111 | 120 | enable_extended_tracing = False | |
| 112 | 121 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1025,8 +1025,14 @@ def run_in_transaction(self, func, *args, **kw): | |||
| 1025 | 1025 | reraises any non-ABORT exceptions raised by ``func``. | |
| 1026 | 1026 | """ | |
| 1027 | 1027 | observability_options = getattr(self, "observability_options", None) | |
| 1028 | + transaction_tag = kw.get("transaction_tag") | ||
| 1029 | + extra_attributes = {} | ||
| 1030 | + if transaction_tag: | ||
| 1031 | + extra_attributes["transaction.tag"] = transaction_tag | ||
| 1032 | + | ||
| 1028 | 1033 | with trace_call( | |
| 1029 | 1034 | "CloudSpanner.Database.run_in_transaction", | |
| 1035 | + extra_attributes=extra_attributes, | ||
| 1030 | 1036 | observability_options=observability_options, | |
| 1031 | 1037 | ), MetricsCapture(): | |
| 1032 | 1038 | # Sanity check: Is there a transaction already running? | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,20 +18,9 @@ | |||
| 18 | 18 | from .metrics_tracer_factory import MetricsTracerFactory | |
| 19 | 19 | import os | |
| 20 | 20 | import logging | |
| 21 | - from .constants import ( | ||
| 22 | - SPANNER_SERVICE_NAME, | ||
| 23 | - GOOGLE_CLOUD_REGION_KEY, | ||
| 24 | - GOOGLE_CLOUD_REGION_GLOBAL, | ||
| 25 | - ) | ||
| 21 | + from .constants import SPANNER_SERVICE_NAME | ||
| 26 | 22 | ||
| 27 | 23 | try: | |
| 28 | - from opentelemetry.resourcedetector import gcp_resource_detector | ||
| 29 | - | ||
| 30 | - # Overwrite the requests timeout for the detector. | ||
| 31 | - # This is necessary as the client will wait the full timeout if the | ||
| 32 | - # code is not run in a GCP environment, with the location endpoints available. | ||
| 33 | - gcp_resource_detector._TIMEOUT_SEC = 0.2 | ||
| 34 | - | ||
| 35 | 24 | import mmh3 | |
| 36 | 25 | ||
| 37 | 26 | logging.getLogger("opentelemetry.resourcedetector.gcp_resource_detector").setLevel( | |
@@ -44,6 +33,7 @@ | |||
| 44 | 33 | ||
| 45 | 34 | from .metrics_tracer import MetricsTracer | |
| 46 | 35 | from google.cloud.spanner_v1 import __version__ | |
| 36 | + from google.cloud.spanner_v1._helpers import _get_cloud_region | ||
| 47 | 37 | from uuid import uuid4 | |
| 48 | 38 | ||
| 49 | 39 | log = logging.getLogger(__name__) | |
@@ -86,7 +76,7 @@ def __new__( | |||
| 86 | 76 | cls._metrics_tracer_factory.set_client_hash( | |
| 87 | 77 | cls._generate_client_hash(client_uid) | |
| 88 | 78 | ) | |
| 89 | - cls._metrics_tracer_factory.set_location(cls._get_location()) | ||
| 79 | + cls._metrics_tracer_factory.set_location(_get_cloud_region()) | ||
| 90 | 80 | cls._metrics_tracer_factory.gfe_enabled = gfe_enabled | |
| 91 | 81 | ||
| 92 | 82 | if cls._metrics_tracer_factory.enabled != enabled: | |
@@ -153,28 +143,3 @@ def _generate_client_hash(client_uid: str) -> str: | |||
| 153 | 143 | ||
| 154 | 144 | # Return as 6 digit zero padded hex string | |
| 155 | 145 | return f"{sig_figs:06x}" | |
| 156 | - | ||
| 157 | - @staticmethod | ||
| 158 | - def _get_location() -> str: | ||
| 159 | - """Get the location of the resource. | ||
| 160 | - | ||
| 161 | - In case of any error during detection, this method will log a warning | ||
| 162 | - and default to the "global" location. | ||
| 163 | - | ||
| 164 | - Returns: | ||
| 165 | - str: The location of the resource. If OpenTelemetry is not installed, returns a global region. | ||
| 166 | - """ | ||
| 167 | - if not HAS_OPENTELEMETRY_INSTALLED: | ||
| 168 | - return GOOGLE_CLOUD_REGION_GLOBAL | ||
| 169 | - try: | ||
| 170 | - detector = gcp_resource_detector.GoogleCloudResourceDetector() | ||
| 171 | - resources = detector.detect() | ||
| 172 | - | ||
| 173 | - if GOOGLE_CLOUD_REGION_KEY in resources.attributes: | ||
| 174 | - return resources.attributes[GOOGLE_CLOUD_REGION_KEY] | ||
| 175 | - except Exception as e: | ||
| 176 | - log.warning( | ||
| 177 | - "Failed to detect GCP resource location for Spanner metrics, defaulting to 'global'. Error: %s", | ||
| 178 | - e, | ||
| 179 | - ) | ||
| 180 | - return GOOGLE_CLOUD_REGION_GLOBAL | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -532,9 +532,14 @@ def run_in_transaction(self, func, *args, **kw): | |||
| 532 | 532 | database = self._database | |
| 533 | 533 | log_commit_stats = database.log_commit_stats | |
| 534 | 534 | ||
| 535 | + extra_attributes = {} | ||
| 536 | + if transaction_tag: | ||
| 537 | + extra_attributes["transaction.tag"] = transaction_tag | ||
| 538 | + | ||
| 535 | 539 | with trace_call( | |
| 536 | 540 | "CloudSpanner.Session.run_in_transaction", | |
| 537 | 541 | self, | |
| 542 | + extra_attributes=extra_attributes, | ||
| 538 | 543 | observability_options=getattr(database, "observability_options", None), | |
| 539 | 544 | ) as span, MetricsCapture(): | |
| 540 | 545 | attempts: int = 0 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -409,7 +409,11 @@ def read( | |||
| 409 | 409 | method=streaming_read_method, | |
| 410 | 410 | request=read_request, | |
| 411 | 411 | metadata=metadata, | |
| 412 | - trace_attributes={"table_id": table, "columns": columns}, | ||
| 412 | + trace_attributes={ | ||
| 413 | + "table_id": table, | ||
| 414 | + "columns": columns, | ||
| 415 | + "request_options": request_options, | ||
| 416 | + }, | ||
| 413 | 417 | column_info=column_info, | |
| 414 | 418 | lazy_decode=lazy_decode, | |
| 415 | 419 | ) | |
@@ -601,7 +605,7 @@ def execute_sql( | |||
| 601 | 605 | method=execute_streaming_sql_method, | |
| 602 | 606 | request=execute_sql_request, | |
| 603 | 607 | metadata=metadata, | |
| 604 | - trace_attributes={"db.statement": sql}, | ||
| 608 | + trace_attributes={"db.statement": sql, "request_options": request_options}, | ||
| 605 | 609 | column_info=column_info, | |
| 606 | 610 | lazy_decode=lazy_decode, | |
| 607 | 611 | ) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -479,7 +479,10 @@ def execute_update( | |||
| 479 | 479 | request_options = RequestOptions(request_options) | |
| 480 | 480 | request_options.transaction_tag = self.transaction_tag | |
| 481 | 481 | ||
| 482 | - trace_attributes = {"db.statement": dml} | ||
| 482 | + trace_attributes = { | ||
| 483 | + "db.statement": dml, | ||
| 484 | + "request_options": request_options, | ||
| 485 | + } | ||
| 483 | 486 | ||
| 484 | 487 | # If this request begins the transaction, we need to lock | |
| 485 | 488 | # the transaction until the transaction ID is updated. | |
@@ -629,7 +632,8 @@ def batch_update( | |||
| 629 | 632 | ||
| 630 | 633 | trace_attributes = { | |
| 631 | 634 | # Get just the queries from the DML statement batch | |
| 632 | - "db.statement": ";".join([statement.sql for statement in parsed]) | ||
| 635 | + "db.statement": ";".join([statement.sql for statement in parsed]), | ||
| 636 | + "request_options": request_options, | ||
| 633 | 637 | } | |
| 634 | 638 | ||
| 635 | 639 | # If this request begins the transaction, we need to lock | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -30,6 +30,7 @@ | |||
| 30 | 30 | from google.cloud.spanner_admin_database_v1 import DatabaseDialect | |
| 31 | 31 | from google.cloud._helpers import UTC | |
| 32 | 32 | ||
| 33 | + from google.cloud.spanner_v1._helpers import _get_cloud_region | ||
| 33 | 34 | from google.cloud.spanner_v1._helpers import AtomicCounter | |
| 34 | 35 | from google.cloud.spanner_v1.data_types import JsonObject | |
| 35 | 36 | from google.cloud.spanner_v1.database_sessions_manager import TransactionType | |
@@ -356,6 +357,7 @@ def _make_attributes(db_instance, **kwargs): | |||
| 356 | 357 | "db.url": "spanner.googleapis.com", | |
| 357 | 358 | "net.host.name": "spanner.googleapis.com", | |
| 358 | 359 | "db.instance": db_instance, | |
| 360 | + "cloud.region": _get_cloud_region(), | ||
| 359 | 361 | "gcp.client.service": "spanner", | |
| 360 | 362 | "gcp.client.version": ot_helpers.LIB_VERSION, | |
| 361 | 363 | "gcp.client.repo": "googleapis/python-spanner", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,7 +16,11 @@ | |||
| 16 | 16 | import unittest | |
| 17 | 17 | import mock | |
| 18 | 18 | ||
| 19 | - from google.cloud.spanner_v1 import TransactionOptions | ||
| 19 | + from opentelemetry.sdk.resources import Resource | ||
| 20 | + from opentelemetry.semconv.resource import ResourceAttributes | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + from google.cloud.spanner_v1 import TransactionOptions, _helpers | ||
| 20 | 24 | ||
| 21 | 25 | ||
| 22 | 26 | class Test_merge_query_options(unittest.TestCase): | |
@@ -89,6 +93,48 @@ def test_base_object_merge_dict(self): | |||
| 89 | 93 | self.assertEqual(result, expected) | |
| 90 | 94 | ||
| 91 | 95 | ||
| 96 | + class Test_get_cloud_region(unittest.TestCase): | ||
| 97 | + def setUp(self): | ||
| 98 | + _helpers._cloud_region = None | ||
| 99 | + | ||
| 100 | + def _callFUT(self, *args, **kw): | ||
| 101 | + from google.cloud.spanner_v1._helpers import _get_cloud_region | ||
| 102 | + | ||
| 103 | + return _get_cloud_region(*args, **kw) | ||
| 104 | + | ||
| 105 | + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") | ||
| 106 | + def test_get_location_with_region(self, mock_detect): | ||
| 107 | + """Test that _get_cloud_region returns the region when detected.""" | ||
| 108 | + mock_resource = Resource.create( | ||
| 109 | + {ResourceAttributes.CLOUD_REGION: "us-central1"} | ||
| 110 | + ) | ||
| 111 | + mock_detect.return_value = mock_resource | ||
| 112 | + | ||
| 113 | + location = self._callFUT() | ||
| 114 | + self.assertEqual(location, "us-central1") | ||
| 115 | + | ||
| 116 | + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") | ||
| 117 | + def test_get_location_without_region(self, mock_detect): | ||
| 118 | + """Test that _get_cloud_region returns 'global' when no region is detected.""" | ||
| 119 | + mock_resource = Resource.create({}) # No region attribute | ||
| 120 | + mock_detect.return_value = mock_resource | ||
| 121 | + | ||
| 122 | + location = self._callFUT() | ||
| 123 | + self.assertEqual(location, "global") | ||
| 124 | + | ||
| 125 | + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") | ||
| 126 | + def test_get_location_with_exception(self, mock_detect): | ||
| 127 | + """Test that _get_cloud_region returns 'global' and logs a warning on exception.""" | ||
| 128 | + mock_detect.side_effect = Exception("detector failed") | ||
| 129 | + | ||
| 130 | + with self.assertLogs( | ||
| 131 | + "google.cloud.spanner_v1._helpers", level="WARNING" | ||
| 132 | + ) as log: | ||
| 133 | + location = self._callFUT() | ||
| 134 | + self.assertEqual(location, "global") | ||
| 135 | + self.assertIn("Failed to detect GCP resource location", log.output[0]) | ||
| 136 | + | ||
| 137 | + | ||
| 92 | 138 | class Test_make_value_pb(unittest.TestCase): | |
| 93 | 139 | def _callFUT(self, *args, **kw): | |
| 94 | 140 | from google.cloud.spanner_v1._helpers import _make_value_pb | |
| Back | FazBrowse Home | New Git URL |
0 commit comments