Skip to content

Commit

Permalink
tracing: enable OpenTelemetry as a first class dependency
Browse files Browse the repository at this point in the history
This change attempts to make OpenTelemetry a first class
dependency used without any guards.
  • Loading branch information
odeke-em committed Nov 10, 2024
1 parent 41604fe commit 5fedcb1
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 243 deletions.
34 changes: 18 additions & 16 deletions google/cloud/spanner_v1/_opentelemetry_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,12 @@
from google.cloud.spanner_v1 import SpannerClient
from google.cloud.spanner_v1 import gapic_version

try:
from opentelemetry import trace
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.semconv.attributes.otel_attributes import (
OTEL_SCOPE_NAME,
OTEL_SCOPE_VERSION,
)

HAS_OPENTELEMETRY_INSTALLED = True
except ImportError:
HAS_OPENTELEMETRY_INSTALLED = False
from opentelemetry import trace
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.semconv.attributes.otel_attributes import (
OTEL_SCOPE_NAME,
OTEL_SCOPE_VERSION,
)

TRACER_NAME = "cloud.google.com/python/spanner"
TRACER_VERSION = gapic_version.__version__
Expand All @@ -51,19 +46,18 @@ def get_tracer(tracer_provider=None):


@contextmanager
def trace_call(name, session, extra_attributes=None):
if not HAS_OPENTELEMETRY_INSTALLED or not session:
# Empty context manager. Users will have to check if the generated value is None or a span
def trace_call(name, session, extra_attributes=None, tracer_provider=None):
if not name:
yield None
return

tracer = get_tracer()
tracer = get_tracer(tracer_provider)

# Set base attributes that we know for every trace created
attributes = {
"db.type": "spanner",
"db.url": SpannerClient.DEFAULT_ENDPOINT,
"db.instance": session._database.name,
"db.instance": db_name(session),
"net.host.name": SpannerClient.DEFAULT_ENDPOINT,
OTEL_SCOPE_NAME: TRACER_NAME,
OTEL_SCOPE_VERSION: TRACER_VERSION,
Expand All @@ -83,3 +77,11 @@ def trace_call(name, session, extra_attributes=None):
raise
else:
span.set_status(Status(StatusCode.OK))


def db_name(session):
if not session:
return ""
if not session._database:
return ""
return session._database.name
8 changes: 3 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,11 @@
"proto-plus >= 1.22.2, <2.0.0dev; python_version>='3.11'",
"protobuf>=3.20.2,<6.0.0dev,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
"grpc-interceptor >= 0.15.4",
"opentelemetry-api >= 1.22.0",
"opentelemetry-sdk >= 1.22.0",
"opentelemetry-semantic-conventions >= 0.43b0",
]
extras = {
"tracing": [
"opentelemetry-api >= 1.22.0",
"opentelemetry-sdk >= 1.22.0",
"opentelemetry-semantic-conventions >= 0.43b0",
],
"libcst": "libcst >= 0.2.5",
}

Expand Down
74 changes: 35 additions & 39 deletions tests/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,20 @@

LIB_VERSION = gapic_version.__version__

try:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
from opentelemetry.semconv.attributes.otel_attributes import (
OTEL_SCOPE_NAME,
OTEL_SCOPE_VERSION,
)
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
from opentelemetry.semconv.attributes.otel_attributes import (
OTEL_SCOPE_NAME,
OTEL_SCOPE_VERSION,
)

from opentelemetry.trace.status import StatusCode
from opentelemetry.trace.status import StatusCode

trace.set_tracer_provider(TracerProvider())

HAS_OPENTELEMETRY_INSTALLED = True
except ImportError:
HAS_OPENTELEMETRY_INSTALLED = False

StatusCode = mock.Mock()
trace.set_tracer_provider(TracerProvider())

_TEST_OT_EXPORTER = None
_TEST_OT_PROVIDER_INITIALIZED = False
Expand All @@ -44,9 +37,8 @@ def enrich_with_otel_scope(attrs):
This helper enriches attrs with OTEL_SCOPE_NAME and OTEL_SCOPE_VERSION
for the purpose of avoiding cumbersome duplicated imports.
"""
if HAS_OPENTELEMETRY_INSTALLED:
attrs[OTEL_SCOPE_NAME] = "cloud.google.com/python/spanner"
attrs[OTEL_SCOPE_VERSION] = LIB_VERSION
attrs[OTEL_SCOPE_NAME] = "cloud.google.com/python/spanner"
attrs[OTEL_SCOPE_VERSION] = LIB_VERSION

return attrs

Expand All @@ -67,28 +59,32 @@ def use_test_ot_exporter():
class OpenTelemetryBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
if HAS_OPENTELEMETRY_INSTALLED:
use_test_ot_exporter()
cls.ot_exporter = get_test_ot_exporter()
use_test_ot_exporter()
cls.ot_exporter = get_test_ot_exporter()

def tearDown(self):
if HAS_OPENTELEMETRY_INSTALLED:
self.ot_exporter.clear()
self.ot_exporter.clear()

def assertNoSpans(self):
if HAS_OPENTELEMETRY_INSTALLED:
span_list = self.ot_exporter.get_finished_spans()
self.assertEqual(len(span_list), 0)
span_list = self.ot_exporter.get_finished_spans()
self.assertEqual(len(span_list), 0, f"Got: {span_names(span_list)}")

def assertSpanAttributes(
self, name, status=StatusCode.OK, attributes=None, span=None
):
if HAS_OPENTELEMETRY_INSTALLED:
if not span:
span_list = self.ot_exporter.get_finished_spans()
self.assertEqual(len(span_list), 1)
span = span_list[0]

self.assertEqual(span.name, name)
self.assertEqual(span.status.status_code, status)
self.assertEqual(dict(span.attributes), attributes)
if not span:
span_list = self.ot_exporter.get_finished_spans()
self.assertEqual(len(span_list), 1)
span = span_list[0]

self.assertEqual(span.name, name)
self.assertEqual(span.status.status_code, status)
self.assertEqual(
dict(span.attributes),
attributes,
f"\n\tGot: {dict(span.attributes)}\n\tWant: {attributes}",
)


def span_names(span_list):
return list((span.name, span.to_json()) for span in span_list)
18 changes: 5 additions & 13 deletions tests/system/test_session_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,18 +305,14 @@ def sessions_to_delete():

@pytest.fixture(scope="function")
def ot_exporter():
if ot_helpers.HAS_OPENTELEMETRY_INSTALLED:
ot_helpers.use_test_ot_exporter()
ot_exporter = ot_helpers.get_test_ot_exporter()
ot_helpers.use_test_ot_exporter()
ot_exporter = ot_helpers.get_test_ot_exporter()

ot_exporter.clear() # XXX?
ot_exporter.clear() # XXX?

yield ot_exporter
yield ot_exporter

ot_exporter.clear()

else:
yield None
ot_exporter.clear()


def assert_no_spans(ot_exporter):
Expand Down Expand Up @@ -1127,10 +1123,6 @@ def test_transaction_batch_update_wo_statements(sessions_database, sessions_to_d
transaction.batch_update([])


@pytest.mark.skipif(
not ot_helpers.HAS_OPENTELEMETRY_INSTALLED,
reason="trace requires OpenTelemetry",
)
def test_transaction_batch_update_w_parent_span(
sessions_database, sessions_to_delete, ot_exporter, database_dialect
):
Expand Down
Loading

0 comments on commit 5fedcb1

Please sign in to comment.