Skip to content

Commit c169c4f

Browse files
committed
all: implement custom tracer_provider injection
An important feature for observability is to allow the injection of a custom tracer_provider instead of always using the global tracer_provider by sending in observability_options=dict( tracer_provider=tracer_provider, enable_extended_tracing=True, )
1 parent 6d64e9f commit c169c4f

File tree

9 files changed

+117
-18
lines changed

9 files changed

+117
-18
lines changed

docs/opentelemetry-tracing.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ We also need to tell OpenTelemetry which exporter to use. To export Spanner trac
2525
2626
# Create and export one trace every 1000 requests
2727
sampler = TraceIdRatioBased(1/1000)
28-
# Use the default tracer provider
29-
trace.set_tracer_provider(TracerProvider(sampler=sampler))
30-
trace.get_tracer_provider().add_span_processor(
28+
tracer_provider = TracerProvider(sampler=sampler)
29+
tracer_provider.add_span_processor(
3130
# Initialize the cloud tracing exporter
3231
BatchSpanProcessor(CloudTraceSpanExporter())
3332
)
33+
observability_options = dict(
34+
tracer_provider=tracer_provider,
35+
enable_extended_tracing=True,
36+
)
37+
spanner = spanner.NewClient(project_id, observability_options=observability_options)
3438
3539
3640
To get more fine-grained traces from gRPC, you can enable the gRPC instrumentation by the following

examples/trace.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ def main():
3232
tracer_provider = TracerProvider(sampler=ALWAYS_ON)
3333
trace_exporter = CloudTraceSpanExporter(project_id=project_id)
3434
tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
35-
trace.set_tracer_provider(tracer_provider)
36-
# Retrieve a tracer from the global tracer provider.
37-
tracer = tracer_provider.get_tracer('MyApp')
3835

3936
# Setup the Cloud Spanner Client.
40-
spanner_client = spanner.Client(project_id)
37+
spanner_client = spanner.Client(
38+
project_id,
39+
observability_options=dict(tracer_provider=tracer_provider, enable_extended_tracing=True),
40+
)
4141
instance = spanner_client.instance('test-instance')
4242
database = instance.database('test-db')
4343

44+
# Retrieve a tracer from our custom tracer provider.
45+
tracer = tracer_provider.get_tracer('MyApp')
46+
4447
# Now run our queries
4548
with tracer.start_as_current_span('QueryInformationSchema'):
4649
with database.snapshot() as snapshot:

google/cloud/spanner_v1/_opentelemetry_tracing.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@
3535
TRACER_VERSION = gapic_version.__version__
3636

3737

38+
class ObservabilityOptions:
39+
def __init__(self, tracer_provider=None, enable_extended_tracing=False):
40+
self.__tracer_provider = tracer_provider
41+
self.__enable_extended_tracing = enable_extended_tracing
42+
43+
@property
44+
def tracer_provider(self):
45+
return self.__tracer_provider
46+
47+
@property
48+
def enable_extended_tracing(self):
49+
return self.__enable_extended_tracing
50+
51+
3852
def get_tracer(tracer_provider=None):
3953
"""
4054
get_tracer is a utility to unify and simplify retrieval of the tracer, without
@@ -51,13 +65,21 @@ def get_tracer(tracer_provider=None):
5165

5266

5367
@contextmanager
54-
def trace_call(name, session, extra_attributes=None):
68+
def trace_call(name, session, extra_attributes=None, observability_options=None):
5569
if not HAS_OPENTELEMETRY_INSTALLED or not session:
5670
# Empty context manager. Users will have to check if the generated value is None or a span
5771
yield None
5872
return
5973

60-
tracer = get_tracer()
74+
tracer_provider = None
75+
enable_extended_tracing = False
76+
if getattr(session, "_observability_options", None):
77+
opts = session._observability_options
78+
if opts:
79+
tracer_provider = opts.tracer_provider
80+
enable_extended_tracing = opts.enable_extended_tracing
81+
82+
tracer = get_tracer(tracer_provider)
6183

6284
# Set base attributes that we know for every trace created
6385
attributes = {
@@ -72,6 +94,13 @@ def trace_call(name, session, extra_attributes=None):
7294
if extra_attributes:
7395
attributes.update(extra_attributes)
7496

97+
# TODO(@odeke-em) enable after discussion with team and agreement
98+
# over extended tracing changes as the legacy default is always to
99+
# record SQL statements on spans.
100+
if False and not enable_extended_tracing:
101+
attributes.pop("db.statement", False)
102+
attributes.pop("sql", False)
103+
75104
with tracer.start_as_current_span(
76105
name, kind=trace.SpanKind.CLIENT, attributes=attributes
77106
) as span:

google/cloud/spanner_v1/client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ class Client(ClientWithProject):
126126
for all ReadRequests and ExecuteSqlRequests that indicates which replicas
127127
or regions should be used for non-transactional reads or queries.
128128
129+
:type labels: dict (str -> any) or None
130+
:param observability_options: (Optional) the configuration to control
131+
the tracer's behavior.
132+
tracer_provider is the injected tracer provider
133+
enable_extended_tracing: :type:boolean when set to true will allow for
134+
spans that issue SQL statements to be annotated with SQL.
135+
129136
:raises: :class:`ValueError <exceptions.ValueError>` if both ``read_only``
130137
and ``admin`` are :data:`True`
131138
"""
@@ -146,6 +153,7 @@ def __init__(
146153
query_options=None,
147154
route_to_leader_enabled=True,
148155
directed_read_options=None,
156+
observability_options=None,
149157
):
150158
self._emulator_host = _get_spanner_emulator_host()
151159

@@ -187,6 +195,7 @@ def __init__(
187195

188196
self._route_to_leader_enabled = route_to_leader_enabled
189197
self._directed_read_options = directed_read_options
198+
self._observability_options = observability_options
190199

191200
@property
192201
def credentials(self):
@@ -371,6 +380,7 @@ def instance(
371380
self._emulator_host,
372381
labels,
373382
processing_units,
383+
observability_options=self._observability_options,
374384
)
375385

376386
def list_instances(self, filter_="", page_size=None):

google/cloud/spanner_v1/database.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def __init__(
156156
database_role=None,
157157
enable_drop_protection=False,
158158
proto_descriptors=None,
159+
observability_options=None,
159160
):
160161
self.database_id = database_id
161162
self._instance = instance
@@ -178,11 +179,16 @@ def __init__(
178179
self._reconciling = False
179180
self._directed_read_options = self._instance._client.directed_read_options
180181
self._proto_descriptors = proto_descriptors
182+
self._observability_options = observability_options
181183

182184
if pool is None:
183-
pool = BurstyPool(database_role=database_role)
185+
pool = BurstyPool(
186+
database_role=database_role,
187+
observability_options=self._observability_options,
188+
)
184189

185190
self._pool = pool
191+
self._pool._observability_options = observability_options
186192
pool.bind(self)
187193

188194
@classmethod
@@ -742,7 +748,12 @@ def session(self, labels=None, database_role=None):
742748
# If role is specified in param, then that role is used
743749
# instead.
744750
role = database_role or self._database_role
745-
return Session(self, labels=labels, database_role=role)
751+
return Session(
752+
self,
753+
labels=labels,
754+
database_role=role,
755+
observability_options=self._observability_options,
756+
)
746757

747758
def snapshot(self, **kw):
748759
"""Return an object which wraps a snapshot.

google/cloud/spanner_v1/instance.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ class Instance(object):
110110
111111
:type labels: dict (str -> str) or None
112112
:param labels: (Optional) User-assigned labels for this instance.
113+
114+
:type labels: dict (str -> any) or None
115+
:param observability_options: (Optional) the configuration to control
116+
the tracer's behavior.
117+
tracer_provider is the injected tracer provider
118+
enable_extended_tracing: :type:boolean when set to true will allow for
119+
spans that issue SQL statements to be annotated with SQL.
113120
"""
114121

115122
def __init__(
@@ -122,6 +129,7 @@ def __init__(
122129
emulator_host=None,
123130
labels=None,
124131
processing_units=None,
132+
observability_options=None,
125133
):
126134
self.instance_id = instance_id
127135
self._client = client
@@ -145,6 +153,7 @@ def __init__(
145153
if labels is None:
146154
labels = {}
147155
self.labels = labels
156+
self._observability_options = observability_options
148157

149158
def _update_from_pb(self, instance_pb):
150159
"""Refresh self from the server-provided protobuf.
@@ -499,6 +508,7 @@ def database(
499508
database_role=database_role,
500509
enable_drop_protection=enable_drop_protection,
501510
proto_descriptors=proto_descriptors,
511+
observability_options=self._observability_options,
502512
)
503513
else:
504514
return TestDatabase(

google/cloud/spanner_v1/pool.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ class AbstractSessionPool(object):
4242

4343
_database = None
4444

45-
def __init__(self, labels=None, database_role=None):
45+
def __init__(self, labels=None, database_role=None, observability_options=None):
4646
if labels is None:
4747
labels = {}
4848
self._labels = labels
4949
self._database_role = database_role
50+
self._observability_options = observability_options
5051

5152
@property
5253
def labels(self):
@@ -178,8 +179,13 @@ def __init__(
178179
default_timeout=DEFAULT_TIMEOUT,
179180
labels=None,
180181
database_role=None,
182+
observability_options=None,
181183
):
182-
super(FixedSizePool, self).__init__(labels=labels, database_role=database_role)
184+
super(FixedSizePool, self).__init__(
185+
labels=labels,
186+
database_role=database_role,
187+
observability_options=observability_options,
188+
)
183189
self.size = size
184190
self.default_timeout = default_timeout
185191
self._sessions = queue.LifoQueue(size)
@@ -284,8 +290,18 @@ class BurstyPool(AbstractSessionPool):
284290
:param database_role: (Optional) user-assigned database_role for the session.
285291
"""
286292

287-
def __init__(self, target_size=10, labels=None, database_role=None):
288-
super(BurstyPool, self).__init__(labels=labels, database_role=database_role)
293+
def __init__(
294+
self,
295+
target_size=10,
296+
labels=None,
297+
database_role=None,
298+
observability_options=None,
299+
):
300+
super(BurstyPool, self).__init__(
301+
labels=labels,
302+
database_role=database_role,
303+
observability_options=observability_options,
304+
)
289305
self.target_size = target_size
290306
self._database = None
291307
self._sessions = queue.LifoQueue(target_size)
@@ -392,8 +408,13 @@ def __init__(
392408
ping_interval=3000,
393409
labels=None,
394410
database_role=None,
411+
observability_options=None,
395412
):
396-
super(PingingPool, self).__init__(labels=labels, database_role=database_role)
413+
super(PingingPool, self).__init__(
414+
labels=labels,
415+
database_role=database_role,
416+
observability_options=observability_options,
417+
)
397418
self.size = size
398419
self.default_timeout = default_timeout
399420
self._delta = datetime.timedelta(seconds=ping_interval)
@@ -546,6 +567,7 @@ def __init__(
546567
ping_interval=3000,
547568
labels=None,
548569
database_role=None,
570+
observability_options=None,
549571
):
550572
"""This throws a deprecation warning on initialization."""
551573
warn(
@@ -561,6 +583,7 @@ def __init__(
561583
ping_interval,
562584
labels=labels,
563585
database_role=database_role,
586+
observability_options=observability_options,
564587
)
565588

566589
self.begin_pending_transactions()

google/cloud/spanner_v1/session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,15 @@ class Session(object):
6363
_session_id = None
6464
_transaction = None
6565

66-
def __init__(self, database, labels=None, database_role=None):
66+
def __init__(
67+
self, database, labels=None, database_role=None, observability_options=None
68+
):
6769
self._database = database
6870
if labels is None:
6971
labels = {}
7072
self._labels = labels
7173
self._database_role = database_role
74+
self._observability_options = observability_options
7275

7376
def __lt__(self, other):
7477
return self._session_id < other._session_id

tests/unit/test__opentelemetry_tracing.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@ def _make_rpc_error(error_cls, trailing_metadata=None):
3030
def _make_session():
3131
from google.cloud.spanner_v1.session import Session
3232

33-
return mock.Mock(autospec=Session, instance=True)
33+
session = mock.Mock(autospec=Session, instance=True)
34+
# Setting _observability_options to None is to avoid the nasty spill-over
35+
# of mock._tracer_provider spuriously failing tests, because per
36+
# unittest.mock.Mock's definition invoking any attribute or method
37+
# returns another mock.
38+
setattr(session, "_observability_options", None)
39+
return session
3440

3541

3642
# Skip all of these tests if we don't have OpenTelemetry

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy