Skip to content

Commit

Permalink
tracing: update OpenTelemetry dependencies from 2021 to 2024
Browse files Browse the repository at this point in the history
This change non-invasively introduces dependencies of opentelemetry
bringing in the latest dependencies and modernizing them.

While here also brought in modern span attributes:
* otel.scope.name
* otel.scope.version

Also added a modernized example and updated the docs.

Updates googleapis#1170
Fixes googleapis#1173
Built from PR googleapis#1172
  • Loading branch information
odeke-em committed Sep 11, 2024
1 parent a941adb commit 3389806
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 16 deletions.
54 changes: 46 additions & 8 deletions docs/opentelemetry-tracing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,69 @@ To take advantage of these traces, we first need to install OpenTelemetry:
.. code-block:: sh
pip install opentelemetry-api opentelemetry-sdk opentelemetry-instrumentation
# [Optional] Installs the cloud monitoring exporter, however you can use any exporter of your choice
pip install opentelemetry-exporter-google-cloud
pip install opentelemetry-exporter-gcp-trace
We also need to tell OpenTelemetry which exporter to use. To export Spanner traces to `Cloud Tracing <https://cloud.google.com/trace>`_, add the following lines to your application:

.. code:: python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.trace.sampling import ProbabilitySampler
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
# BatchExportSpanProcessor exports spans to Cloud Trace
# BatchSpanProcessor exports spans to Cloud Trace
# in a seperate thread to not block on the main thread
from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Create and export one trace every 1000 requests
sampler = ProbabilitySampler(1/1000)
sampler = TraceIdRatioBased(1/1000)
# Use the default tracer provider
trace.set_tracer_provider(TracerProvider(sampler=sampler))
trace.get_tracer_provider().add_span_processor(
# Initialize the cloud tracing exporter
BatchExportSpanProcessor(CloudTraceSpanExporter())
BatchSpanProcessor(CloudTraceSpanExporter())
)
Alternatively you can pass in a tracer provider into the Cloud Spanner
initialization, otherwise the global tracer will be used:

.. code:: python
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.sampling import TraceIdRatioBased
from opentelemetry.exporter.cloud_trace import CloudTraceSpanExporter
# Create and export one trace every 1000 requests
sampler = TraceIdRatioBased(1/1000)
tracerProvider = TracerProvider(sampler=sampler)
tracerProvider.add_span_processor(
# Initialize the cloud tracing exporter
BatchSpanProcessor(CloudTraceSpanExporter())
)
options = dict(tracer_provider=tracerProvider)
# Pass the tracer provider while creating the Spanner client.
spanner_client = spanner.Client(observability_options=options)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
To get more fine-grained traces from gRPC, you can enable the gRPC instrumentation by the following

.. code-block:: sh
pip install opentelemetry-instrumentation-grpc
and then in your Python code, please add the following lines:

.. code:: python
from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient
grpc_client_instrumentor = GrpcInstrumentorClient()
grpc_client_instrumentor.instrument()
Generated spanner traces should now be available on `Cloud Trace <https://console.cloud.google.com/traces>`_.

Tracing is most effective when many libraries are instrumented to provide insight over the entire lifespan of a request.
Expand Down
29 changes: 28 additions & 1 deletion google/cloud/spanner_v1/_opentelemetry_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,40 @@

from google.api_core.exceptions import GoogleAPICallError
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

LIB_VERSION = gapic_version.__version__
LIB_FQNAME = 'cloud.google.com/python/spanner'
TRACER_NAME = LIB_FQNAME
TRACER_VERSION = LIB_VERSION


def get_tracer(tracer_provider=None):
"""
get_tracer is a utility to unify and simplify retrieval of the tracer, without
leaking implementation details given that retrieving a tracer requires providing
the full qualified library name and version.
When the tracer_provider is set, it'll retrieve the tracer from it, otherwise
it'll fall back to the global tracer provider and use this library's specific semantics.
"""
if not tracer_provider:
# Acquire the global tracer provider.
tracer_provider = trace.get_tracer_provider()

return tracer_provider.get_tracer(TRACER_NAME, TRACER_VERSION)


@contextmanager
def trace_call(name, session, extra_attributes=None):
Expand All @@ -35,14 +60,16 @@ def trace_call(name, session, extra_attributes=None):
yield None
return

tracer = trace.get_tracer(__name__)
tracer = get_tracer()

# Set base attributes that we know for every trace created
attributes = {
"db.type": "spanner",
"db.url": SpannerClient.DEFAULT_ENDPOINT,
"db.instance": session._database.name,
"net.host.name": SpannerClient.DEFAULT_ENDPOINT,
OTEL_SCOPE_NAME: LIB_FQNAME,
OTEL_SCOPE_VERSION: LIB_VERSION,
}

if extra_attributes:
Expand Down
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@
]
extras = {
"tracing": [
"opentelemetry-api >= 1.1.0",
"opentelemetry-sdk >= 1.1.0",
"opentelemetry-instrumentation >= 0.20b0, < 0.23dev",
"opentelemetry-api >= 1.24.0",
"opentelemetry-sdk >= 1.24.0",
"opentelemetry-instrumentation >= 0.46b0",
"opentelemetry-semantic-conventions >= 0.46b0",
],
"libcst": "libcst >= 0.2.5",
}
Expand Down
7 changes: 4 additions & 3 deletions testing/constraints-3.7.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ grpc-google-iam-v1==0.12.4
libcst==0.2.5
proto-plus==1.22.0
sqlparse==0.4.4
opentelemetry-api==1.1.0
opentelemetry-sdk==1.1.0
opentelemetry-instrumentation==0.20b0
opentelemetry-api>=1.24.0
opentelemetry-sdk>=1.24.0
opentelemetry-instrumentation==0.46b0
opentelemetry-semantic-conventions==0.46b0
protobuf==3.20.2
deprecated==1.2.14
grpc-interceptor==0.15.4
5 changes: 5 additions & 0 deletions tests/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@
InMemorySpanExporter,
)
from opentelemetry.trace.status import StatusCode
from opentelemetry.semconv.attributes import (
OTEL_SCOPE_NAME,
OTEL_SCOPE_VERSION,
)

trace.set_tracer_provider(TracerProvider())

HAS_OPENTELEMETRY_INSTALLED = True
except ImportError:
HAS_OPENTELEMETRY_INSTALLED = False
OTEL_SCOPE_NAME = "otel.scope.name"

StatusCode = mock.Mock()

Expand Down
10 changes: 9 additions & 1 deletion tests/unit/test__opentelemetry_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
from google.api_core.exceptions import GoogleAPICallError
from google.cloud.spanner_v1 import _opentelemetry_tracing

from tests._helpers import OpenTelemetryBase, HAS_OPENTELEMETRY_INSTALLED
from tests._helpers import (
OpenTelemetryBase,
HAS_OPENTELEMETRY_INSTALLED,
OTEL_SCOPE_NAME,
)


def _make_rpc_error(error_cls, trailing_metadata=None):
Expand Down Expand Up @@ -59,6 +63,7 @@ def test_trace_call(self):
"db.type": "spanner",
"db.url": "spanner.googleapis.com",
"net.host.name": "spanner.googleapis.com",
OTEL_SCOPE_NAME: "cloud.google.com/python/spanner",
}
expected_attributes.update(extra_attributes)

Expand All @@ -84,6 +89,7 @@ def test_trace_error(self):
"db.type": "spanner",
"db.url": "spanner.googleapis.com",
"net.host.name": "spanner.googleapis.com",
OTEL_SCOPE_NAME: "cloud.google.com/python/spanner",
}
expected_attributes.update(extra_attributes)

Expand All @@ -110,6 +116,7 @@ def test_trace_grpc_error(self):
"db.type": "spanner",
"db.url": "spanner.googleapis.com:443",
"net.host.name": "spanner.googleapis.com:443",
OTEL_SCOPE_NAME: "cloud.google.com/python/spanner",
}
expected_attributes.update(extra_attributes)

Expand All @@ -133,6 +140,7 @@ def test_trace_codeless_error(self):
"db.type": "spanner",
"db.url": "spanner.googleapis.com:443",
"net.host.name": "spanner.googleapis.com:443",
OTEL_SCOPE_NAME: "cloud.google.com/python/spanner",
}
expected_attributes.update(extra_attributes)

Expand Down

0 comments on commit 3389806

Please sign in to comment.