Skip to content

Commit 8774276

Browse files
authored
Prototype proxy tracer/provider to enable lazy setup of tracing pipeline (#1726)
1 parent 96fd84f commit 8774276

File tree

4 files changed

+137
-2
lines changed

4 files changed

+137
-2
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ env:
1010
# Otherwise, set variable to the commit of your branch on
1111
# opentelemetry-python-contrib which is compatible with these Core repo
1212
# changes.
13-
CONTRIB_REPO_SHA: fbd7e07d0b3f9be37ddfdbc8b12b35f0a8d71ede
13+
CONTRIB_REPO_SHA: 1064da4bf2afaa0fb84fa10f573b211efeba72bc
1414

1515
jobs:
1616
build:

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
([#1722](https://github.com/open-telemetry/opentelemetry-python/pull/1722))
2222
- Fix B3 propagator to never return None.
2323
([#1750](https://github.com/open-telemetry/opentelemetry-python/pull/1750))
24+
- Added ProxyTracerProvider and ProxyTracer implementations to allow fetching provider
25+
and tracer instances before a global provider is set up.
26+
([#1726](https://github.com/open-telemetry/opentelemetry-python/pull/1726))
2427

2528

2629
## [1.0.0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.0.0) - 2021-03-26

opentelemetry-api/src/opentelemetry/trace/__init__.py

+61-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"""
7575

7676

77+
import os
7778
from abc import ABC, abstractmethod
7879
from contextlib import contextmanager
7980
from enum import Enum
@@ -220,6 +221,21 @@ def get_tracer(
220221
return _DefaultTracer()
221222

222223

224+
class ProxyTracerProvider(TracerProvider):
225+
def get_tracer(
226+
self,
227+
instrumenting_module_name: str,
228+
instrumenting_library_version: str = "",
229+
) -> "Tracer":
230+
if _TRACER_PROVIDER:
231+
return _TRACER_PROVIDER.get_tracer(
232+
instrumenting_module_name, instrumenting_library_version
233+
)
234+
return ProxyTracer(
235+
instrumenting_module_name, instrumenting_library_version
236+
)
237+
238+
223239
class Tracer(ABC):
224240
"""Handles span creation and in-process context propagation.
225241
@@ -349,6 +365,40 @@ def start_as_current_span(
349365
"""
350366

351367

368+
class ProxyTracer(Tracer):
369+
# pylint: disable=W0222,signature-differs
370+
def __init__(
371+
self,
372+
instrumenting_module_name: str,
373+
instrumenting_library_version: str,
374+
):
375+
self._instrumenting_module_name = instrumenting_module_name
376+
self._instrumenting_library_version = instrumenting_library_version
377+
self._real_tracer: Optional[Tracer] = None
378+
self._noop_tracer = _DefaultTracer()
379+
380+
@property
381+
def _tracer(self) -> Tracer:
382+
if self._real_tracer:
383+
return self._real_tracer
384+
385+
if _TRACER_PROVIDER:
386+
self._real_tracer = _TRACER_PROVIDER.get_tracer(
387+
self._instrumenting_module_name,
388+
self._instrumenting_library_version,
389+
)
390+
return self._real_tracer
391+
return self._noop_tracer
392+
393+
def start_span(self, *args, **kwargs) -> Span: # type: ignore
394+
return self._tracer.start_span(*args, **kwargs) # type: ignore
395+
396+
def start_as_current_span( # type: ignore
397+
self, *args, **kwargs
398+
) -> Span:
399+
return self._tracer.start_as_current_span(*args, **kwargs) # type: ignore
400+
401+
352402
class _DefaultTracer(Tracer):
353403
"""The default Tracer, used when no Tracer implementation is available.
354404
@@ -387,6 +437,7 @@ def start_as_current_span(
387437

388438

389439
_TRACER_PROVIDER = None
440+
_PROXY_TRACER_PROVIDER = None
390441

391442

392443
def get_tracer(
@@ -425,9 +476,18 @@ def set_tracer_provider(tracer_provider: TracerProvider) -> None:
425476

426477
def get_tracer_provider() -> TracerProvider:
427478
"""Gets the current global :class:`~.TracerProvider` object."""
428-
global _TRACER_PROVIDER # pylint: disable=global-statement
479+
# pylint: disable=global-statement
480+
global _TRACER_PROVIDER
481+
global _PROXY_TRACER_PROVIDER
429482

430483
if _TRACER_PROVIDER is None:
484+
# if a global tracer provider has not been set either via code or env
485+
# vars, return a proxy tracer provider
486+
if OTEL_PYTHON_TRACER_PROVIDER not in os.environ:
487+
if not _PROXY_TRACER_PROVIDER:
488+
_PROXY_TRACER_PROVIDER = ProxyTracerProvider()
489+
return _PROXY_TRACER_PROVIDER
490+
431491
_TRACER_PROVIDER = cast( # type: ignore
432492
"TracerProvider",
433493
_load_provider(OTEL_PYTHON_TRACER_PROVIDER, "tracer_provider"),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# pylint: disable=W0212,W0222,W0221
16+
17+
import unittest
18+
19+
from opentelemetry import trace
20+
from opentelemetry.trace.span import INVALID_SPAN_CONTEXT, NonRecordingSpan
21+
22+
23+
class TestProvider(trace._DefaultTracerProvider):
24+
def get_tracer(
25+
self, instrumentation_module_name, instrumentaiton_library_version=None
26+
):
27+
return TestTracer()
28+
29+
30+
class TestTracer(trace._DefaultTracer):
31+
def start_span(self, *args, **kwargs):
32+
return TestSpan(INVALID_SPAN_CONTEXT)
33+
34+
35+
class TestSpan(NonRecordingSpan):
36+
pass
37+
38+
39+
class TestProxy(unittest.TestCase):
40+
def test_proxy_tracer(self):
41+
original_provider = trace._TRACER_PROVIDER
42+
43+
provider = trace.get_tracer_provider()
44+
# proxy provider
45+
self.assertIsInstance(provider, trace.ProxyTracerProvider)
46+
47+
# provider returns proxy tracer
48+
tracer = provider.get_tracer("proxy-test")
49+
self.assertIsInstance(tracer, trace.ProxyTracer)
50+
51+
with tracer.start_span("span1") as span:
52+
self.assertIsInstance(span, trace.NonRecordingSpan)
53+
54+
with tracer.start_as_current_span("span2") as span:
55+
self.assertIsInstance(span, trace.NonRecordingSpan)
56+
57+
# set a real provider
58+
trace.set_tracer_provider(TestProvider())
59+
60+
# tracer provider now returns real instance
61+
self.assertIsInstance(trace.get_tracer_provider(), TestProvider)
62+
63+
# references to the old provider still work but return real tracer now
64+
real_tracer = provider.get_tracer("proxy-test")
65+
self.assertIsInstance(real_tracer, TestTracer)
66+
67+
# reference to old proxy tracer now delegates to a real tracer and
68+
# creates real spans
69+
with tracer.start_span("") as span:
70+
self.assertIsInstance(span, TestSpan)
71+
72+
trace._TRACER_PROVIDER = original_provider

0 commit comments

Comments
 (0)