Skip to content

Proxy tracer/provider to enable lazy setup of tracing pipeline #1726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ env:
# Otherwise, set variable to the commit of your branch on
# opentelemetry-python-contrib which is compatible with these Core repo
# changes.
CONTRIB_REPO_SHA: fbd7e07d0b3f9be37ddfdbc8b12b35f0a8d71ede
CONTRIB_REPO_SHA: 1064da4bf2afaa0fb84fa10f573b211efeba72bc

jobs:
build:
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#1722](https://github.com/open-telemetry/opentelemetry-python/pull/1722))
- Fix B3 propagator to never return None.
([#1750](https://github.com/open-telemetry/opentelemetry-python/pull/1750))
- Added ProxyTracerProvider and ProxyTracer implementations to allow fetching provider
and tracer instances before a global provider is set up.
([#1726](https://github.com/open-telemetry/opentelemetry-python/pull/1726))


## [1.0.0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.0.0) - 2021-03-26
Expand Down
62 changes: 61 additions & 1 deletion opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"""


import os
from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum
Expand Down Expand Up @@ -220,6 +221,21 @@ def get_tracer(
return _DefaultTracer()


class ProxyTracerProvider(TracerProvider):
def get_tracer(
self,
instrumenting_module_name: str,
instrumenting_library_version: str = "",
) -> "Tracer":
if _TRACER_PROVIDER:
return _TRACER_PROVIDER.get_tracer(
instrumenting_module_name, instrumenting_library_version
)
return ProxyTracer(
instrumenting_module_name, instrumenting_library_version
)


class Tracer(ABC):
"""Handles span creation and in-process context propagation.

Expand Down Expand Up @@ -349,6 +365,40 @@ def start_as_current_span(
"""


class ProxyTracer(Tracer):
# pylint: disable=W0222,signature-differs
def __init__(
self,
instrumenting_module_name: str,
instrumenting_library_version: str,
):
self._instrumenting_module_name = instrumenting_module_name
self._instrumenting_library_version = instrumenting_library_version
self._real_tracer: Optional[Tracer] = None
self._noop_tracer = _DefaultTracer()

@property
def _tracer(self) -> Tracer:
if self._real_tracer:
return self._real_tracer

if _TRACER_PROVIDER:
self._real_tracer = _TRACER_PROVIDER.get_tracer(
self._instrumenting_module_name,
self._instrumenting_library_version,
)
return self._real_tracer
return self._noop_tracer

def start_span(self, *args, **kwargs) -> Span: # type: ignore
return self._tracer.start_span(*args, **kwargs) # type: ignore

def start_as_current_span( # type: ignore
self, *args, **kwargs
) -> Span:
return self._tracer.start_as_current_span(*args, **kwargs) # type: ignore


class _DefaultTracer(Tracer):
"""The default Tracer, used when no Tracer implementation is available.

Expand Down Expand Up @@ -387,6 +437,7 @@ def start_as_current_span(


_TRACER_PROVIDER = None
_PROXY_TRACER_PROVIDER = None


def get_tracer(
Expand Down Expand Up @@ -425,9 +476,18 @@ def set_tracer_provider(tracer_provider: TracerProvider) -> None:

def get_tracer_provider() -> TracerProvider:
"""Gets the current global :class:`~.TracerProvider` object."""
global _TRACER_PROVIDER # pylint: disable=global-statement
# pylint: disable=global-statement
global _TRACER_PROVIDER
global _PROXY_TRACER_PROVIDER

if _TRACER_PROVIDER is None:
# if a global tracer provider has not been set either via code or env
# vars, return a proxy tracer provider
if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
if not _PROXY_TRACER_PROVIDER:
_PROXY_TRACER_PROVIDER = ProxyTracerProvider()
return _PROXY_TRACER_PROVIDER

_TRACER_PROVIDER = cast( # type: ignore
"TracerProvider",
_load_provider(OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"),
Expand Down
72 changes: 72 additions & 0 deletions opentelemetry-api/tests/trace/test_proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=W0212,W0222,W0221

import unittest

from opentelemetry import trace
from opentelemetry.trace.span import INVALID_SPAN_CONTEXT, NonRecordingSpan


class TestProvider(trace._DefaultTracerProvider):
def get_tracer(
self, instrumentation_module_name, instrumentaiton_library_version=None
):
return TestTracer()


class TestTracer(trace._DefaultTracer):
def start_span(self, *args, **kwargs):
return TestSpan(INVALID_SPAN_CONTEXT)


class TestSpan(NonRecordingSpan):
pass


class TestProxy(unittest.TestCase):
def test_proxy_tracer(self):
original_provider = trace._TRACER_PROVIDER

provider = trace.get_tracer_provider()
# proxy provider
self.assertIsInstance(provider, trace.ProxyTracerProvider)

# provider returns proxy tracer
tracer = provider.get_tracer("proxy-test")
self.assertIsInstance(tracer, trace.ProxyTracer)

with tracer.start_span("span1") as span:
self.assertIsInstance(span, trace.NonRecordingSpan)

with tracer.start_as_current_span("span2") as span:
self.assertIsInstance(span, trace.NonRecordingSpan)

# set a real provider
trace.set_tracer_provider(TestProvider())

# tracer provider now returns real instance
self.assertIsInstance(trace.get_tracer_provider(), TestProvider)

# references to the old provider still work but return real tracer now
real_tracer = provider.get_tracer("proxy-test")
self.assertIsInstance(real_tracer, TestTracer)

# reference to old proxy tracer now delegates to a real tracer and
# creates real spans
with tracer.start_span("") as span:
self.assertIsInstance(span, TestSpan)

trace._TRACER_PROVIDER = original_provider