Skip to content

Commit

Permalink
Prototype proxy tracer/provider to enable lazy setup of tracing pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
owais committed Apr 7, 2021
1 parent 96fd84f commit 4d01775
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 2 deletions.
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

0 comments on commit 4d01775

Please sign in to comment.