Skip to content

Commit 64aebe7

Browse files
authored
feat(spanner): make built-in metrics enabled by default (#1459)
Make built-in metrics enabled by default This change inverts the logic for enabling built-in OpenTelemetry metrics. Previously, metrics were disabled by default and could be enabled by setting `ENABLE_SPANNER_METRICS_ENV_VAR=true`. With this update, metrics are now enabled by default to provide better out-of-the-box observability for users. To disable metrics, users must now set the new environment variable: `SPANNER_DISABLE_BUILTIN_METRICS=true` The old `ENABLE_SPANNER_METRICS_ENV_VAR` is no longer used. Unit tests have been updated to reflect this new opt-out behavior. **BREAKING CHANGE**: Built-in metrics are now enabled by default. Users who previously did not set any environment variables will have metrics collection and export turned on automatically after upgrading. To restore the previous behavior and disable metrics, thry have to set the `SPANNER_DISABLE_BUILTIN_METRICS` environment variable to `true`.
1 parent 4bb4622 commit 64aebe7

File tree

4 files changed

+97
-7
lines changed

4 files changed

+97
-7
lines changed

google/cloud/spanner_v1/client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
5353
from google.cloud.spanner_v1.instance import Instance
5454
from google.cloud.spanner_v1.metrics.constants import (
55-
ENABLE_SPANNER_METRICS_ENV_VAR,
5655
METRIC_EXPORT_INTERVAL_MS,
5756
)
5857
from google.cloud.spanner_v1.metrics.spanner_metrics_tracer_factory import (
@@ -75,7 +74,7 @@
7574

7675
_CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__)
7776
EMULATOR_ENV_VAR = "SPANNER_EMULATOR_HOST"
78-
ENABLE_BUILTIN_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"
77+
SPANNER_DISABLE_BUILTIN_METRICS_ENV_VAR = "SPANNER_DISABLE_BUILTIN_METRICS"
7978
_EMULATOR_HOST_HTTP_SCHEME = (
8079
"%s contains a http scheme. When used with a scheme it may cause gRPC's "
8180
"DNS resolver to endlessly attempt to resolve. %s is intended to be used "
@@ -102,7 +101,7 @@ def _get_spanner_optimizer_statistics_package():
102101

103102

104103
def _get_spanner_enable_builtin_metrics():
105-
return os.getenv(ENABLE_SPANNER_METRICS_ENV_VAR) == "true"
104+
return os.getenv(SPANNER_DISABLE_BUILTIN_METRICS_ENV_VAR) != "true"
106105

107106

108107
class Client(ClientWithProject):

google/cloud/spanner_v1/metrics/constants.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
GOOGLE_CLOUD_REGION_KEY = "cloud.region"
2121
GOOGLE_CLOUD_REGION_GLOBAL = "global"
2222
SPANNER_METHOD_PREFIX = "/google.spanner.v1."
23-
ENABLE_SPANNER_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"
2423

2524
# Monitored resource labels
2625
MONITORED_RES_LABEL_KEY_PROJECT = "project_id"

tests/system/test_metrics.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright 2025 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+
import os
16+
import mock
17+
import pytest
18+
19+
from opentelemetry.sdk.metrics import MeterProvider
20+
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
21+
22+
from google.cloud.spanner_v1 import Client
23+
24+
# System tests are skipped if the environment variables are not set.
25+
PROJECT = os.environ.get("GOOGLE_CLOUD_PROJECT")
26+
INSTANCE_ID = os.environ.get("SPANNER_TEST_INSTANCE")
27+
DATABASE_ID = "test_metrics_db_system"
28+
29+
30+
pytestmark = pytest.mark.skipif(
31+
not all([PROJECT, INSTANCE_ID]), reason="System test environment variables not set."
32+
)
33+
34+
35+
@pytest.fixture(scope="module")
36+
def metrics_database():
37+
"""Create a database for the test."""
38+
client = Client(project=PROJECT)
39+
instance = client.instance(INSTANCE_ID)
40+
database = instance.database(DATABASE_ID)
41+
if database.exists(): # Clean up from previous failed run
42+
database.drop()
43+
op = database.create()
44+
op.result(timeout=300) # Wait for creation to complete
45+
yield database
46+
if database.exists():
47+
database.drop()
48+
49+
50+
def test_builtin_metrics_with_default_otel(metrics_database):
51+
"""
52+
Verifies that built-in metrics are collected by default when a
53+
transaction is executed.
54+
"""
55+
reader = InMemoryMetricReader()
56+
meter_provider = MeterProvider(metric_readers=[reader])
57+
58+
# Patch the client's metric setup to use our in-memory reader.
59+
with mock.patch(
60+
"google.cloud.spanner_v1.client.MeterProvider",
61+
return_value=meter_provider,
62+
):
63+
with mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "false"}):
64+
with metrics_database.snapshot() as snapshot:
65+
list(snapshot.execute_sql("SELECT 1"))
66+
67+
metric_data = reader.get_metrics_data()
68+
69+
assert len(metric_data.resource_metrics) >= 1
70+
assert len(metric_data.resource_metrics[0].scope_metrics) >= 1
71+
72+
collected_metrics = {
73+
metric.name
74+
for metric in metric_data.resource_metrics[0].scope_metrics[0].metrics
75+
}
76+
expected_metrics = {
77+
"spanner/operation_latencies",
78+
"spanner/attempt_latencies",
79+
"spanner/operation_count",
80+
"spanner/attempt_count",
81+
"spanner/gfe_latencies",
82+
}
83+
assert expected_metrics.issubset(collected_metrics)
84+
85+
for metric in metric_data.resource_metrics[0].scope_metrics[0].metrics:
86+
if metric.name == "spanner/operation_count":
87+
point = next(iter(metric.data.data_points))
88+
assert point.value == 1
89+
assert point.attributes["method"] == "ExecuteSql"
90+
return
91+
92+
pytest.fail("Metric 'spanner/operation_count' not found.")

tests/unit/test_client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from tests._builders import build_scoped_credentials
2323

2424

25+
@mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "true"})
2526
class TestClient(unittest.TestCase):
2627
PROJECT = "PROJECT"
2728
PATH = "projects/%s" % (PROJECT,)
@@ -161,8 +162,7 @@ def test_constructor_custom_client_info(self):
161162
creds = build_scoped_credentials()
162163
self._constructor_test_helper(expected_scopes, creds, client_info=client_info)
163164

164-
# Disable metrics to avoid google.auth.default calls from Metric Exporter
165-
@mock.patch.dict(os.environ, {"SPANNER_ENABLE_BUILTIN_METRICS": ""})
165+
# Metrics are disabled by default for tests in this class
166166
def test_constructor_implicit_credentials(self):
167167
from google.cloud.spanner_v1 import client as MUT
168168

@@ -255,8 +255,8 @@ def test_constructor_w_directed_read_options(self):
255255
expected_scopes, creds, directed_read_options=self.DIRECTED_READ_OPTIONS
256256
)
257257

258-
@mock.patch.dict(os.environ, {"SPANNER_ENABLE_BUILTIN_METRICS": "true"})
259258
@mock.patch("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory")
259+
@mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "false"})
260260
def test_constructor_w_metrics_initialization_error(
261261
self, mock_spanner_metrics_factory
262262
):

0 commit comments

Comments
 (0)