From 34ec861177b249b3f0175191e4cb8958ae28e819 Mon Sep 17 00:00:00 2001 From: Diego Hurtado Date: Wed, 26 Feb 2020 15:34:26 -0600 Subject: [PATCH] Add configuration manager --- docs/index.rst | 1 - docs/opentelemetry.util.loader.rst | 4 - examples/basic_tracer/tests/conftest.py | 25 +++ examples/basic_tracer/tracer.py | 7 +- examples/http/server.py | 8 +- examples/http/tests/conftest.py | 25 +++ examples/http/tests/test_http.py | 10 +- examples/http/tracer_client.py | 6 +- examples/metrics/observer_example.py | 3 +- examples/metrics/prometheus.py | 3 +- examples/metrics/record.py | 5 +- examples/metrics/simple_example.py | 3 +- .../flask_example.py | 9 +- .../tests/conftest.py | 25 +++ examples/opentracing/main.py | 5 +- ext/opentelemetry-ext-dbapi/README.rst | 1 - ext/opentelemetry-ext-flask/tests/conftest.py | 25 +++ ext/opentelemetry-ext-jaeger/README.rst | 2 - .../examples/jaeger_exporter_example.py | 4 +- .../ext/opentracing_shim/__init__.py | 4 - .../tests/conftest.py | 25 +++ .../tests/test_shim.py | 11 +- ext/opentelemetry-ext-prometheus/README.rst | 3 +- .../tests/conftest.py | 25 +++ .../tests/test_prometheus_exporter.py | 8 +- ext/opentelemetry-ext-psycopg2/README.rst | 2 - .../ext/testutil/wsgitestutil.py | 7 +- ext/opentelemetry-ext-wsgi/tests/conftest.py | 25 +++ ext/opentelemetry-ext-zipkin/README.rst | 2 - opentelemetry-api/setup.py | 44 ++++- .../opentelemetry/configuration/__init__.py | 78 ++++++++ .../configuration/opentelemetry_python.json | 4 + .../src/opentelemetry/configuration/py.typed | 0 .../src/opentelemetry/metrics/__init__.py | 77 +++----- .../src/opentelemetry/trace/__init__.py | 83 +++----- .../src/opentelemetry/util/loader.py | 182 ------------------ .../tests/configuration/__init__.py | 0 .../tests/configuration/test_configuration.py | 85 ++++++++ opentelemetry-api/tests/mypysmoke.py | 2 +- opentelemetry-api/tests/test_loader.py | 112 ----------- opentelemetry-api/tests/trace/test_globals.py | 36 +--- opentelemetry-sdk/setup.py | 8 + 42 files changed, 475 insertions(+), 519 deletions(-) delete mode 100644 docs/opentelemetry.util.loader.rst create mode 100644 examples/basic_tracer/tests/conftest.py create mode 100644 examples/http/tests/conftest.py create mode 100644 examples/opentelemetry-example-app/tests/conftest.py create mode 100644 ext/opentelemetry-ext-flask/tests/conftest.py create mode 100644 ext/opentelemetry-ext-opentracing-shim/tests/conftest.py create mode 100644 ext/opentelemetry-ext-prometheus/tests/conftest.py create mode 100644 ext/opentelemetry-ext-wsgi/tests/conftest.py create mode 100644 opentelemetry-api/src/opentelemetry/configuration/__init__.py create mode 100644 opentelemetry-api/src/opentelemetry/configuration/opentelemetry_python.json create mode 100644 opentelemetry-api/src/opentelemetry/configuration/py.typed delete mode 100644 opentelemetry-api/src/opentelemetry/util/loader.py create mode 100644 opentelemetry-api/tests/configuration/__init__.py create mode 100644 opentelemetry-api/tests/configuration/test_configuration.py delete mode 100644 opentelemetry-api/tests/test_loader.py diff --git a/docs/index.rst b/docs/index.rst index c597d4a681f..ed2cc1a942e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,7 +18,6 @@ and integration packages. opentelemetry.context opentelemetry.metrics opentelemetry.trace - opentelemetry.util.loader .. toctree:: :maxdepth: 1 diff --git a/docs/opentelemetry.util.loader.rst b/docs/opentelemetry.util.loader.rst deleted file mode 100644 index 079d2e4a38d..00000000000 --- a/docs/opentelemetry.util.loader.rst +++ /dev/null @@ -1,4 +0,0 @@ -opentelemetry.util.loader module -================================ - -.. automodule:: opentelemetry.util.loader diff --git a/examples/basic_tracer/tests/conftest.py b/examples/basic_tracer/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/examples/basic_tracer/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/examples/basic_tracer/tracer.py b/examples/basic_tracer/tracer.py index a454eab7a96..a8ba537cc9c 100755 --- a/examples/basic_tracer/tracer.py +++ b/examples/basic_tracer/tracer.py @@ -17,7 +17,6 @@ import os from opentelemetry import trace -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchExportSpanProcessor, ConsoleSpanExporter, @@ -45,10 +44,6 @@ print("Using ConsoleSpanExporter") exporter = ConsoleSpanExporter() -# The preferred tracer implementation must be set, as the opentelemetry-api -# defines the interface with a no-op implementation. -trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) - # We tell OpenTelemetry who it is that is creating spans. In this case, we have # no real name (no setup.py), so we make one up. If we had a version, we would # also specify it here. @@ -57,7 +52,7 @@ # SpanExporter receives the spans and send them to the target location. span_processor = BatchExportSpanProcessor(exporter) -trace.tracer_provider().add_span_processor(span_processor) +trace.get_tracer_provider().add_span_processor(span_processor) with tracer.start_as_current_span("foo"): with tracer.start_as_current_span("bar"): with tracer.start_as_current_span("baz"): diff --git a/examples/http/server.py b/examples/http/server.py index 50bc566b77c..d706a269606 100755 --- a/examples/http/server.py +++ b/examples/http/server.py @@ -22,7 +22,6 @@ from opentelemetry import trace from opentelemetry.ext import http_requests from opentelemetry.ext.wsgi import OpenTelemetryMiddleware -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchExportSpanProcessor, ConsoleSpanExporter, @@ -39,19 +38,16 @@ else: exporter = ConsoleSpanExporter() -# The preferred tracer implementation must be set, as the opentelemetry-api -# defines the interface with a no-op implementation. -trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) # SpanExporter receives the spans and send them to the target location. span_processor = BatchExportSpanProcessor(exporter) -trace.tracer_provider().add_span_processor(span_processor) +trace.get_tracer_provider().add_span_processor(span_processor) # Integrations are the glue that binds the OpenTelemetry API and the # frameworks and libraries that are used together, automatically creating # Spans and propagating context as appropriate. -http_requests.enable(trace.tracer_provider()) +http_requests.enable(trace.get_tracer_provider()) app = flask.Flask(__name__) app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) diff --git a/examples/http/tests/conftest.py b/examples/http/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/examples/http/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/examples/http/tests/test_http.py b/examples/http/tests/test_http.py index 0ae81fe7de7..0444e8d67d6 100644 --- a/examples/http/tests/test_http.py +++ b/examples/http/tests/test_http.py @@ -19,11 +19,10 @@ class TestHttpExample(unittest.TestCase): - @classmethod - def setup_class(cls): + def setUp(self): dirpath = os.path.dirname(os.path.realpath(__file__)) server_script = "{}/../server.py".format(dirpath) - cls.server = subprocess.Popen([sys.executable, server_script]) + self.server = subprocess.Popen([sys.executable, server_script]) sleep(1) def test_http(self): @@ -34,6 +33,5 @@ def test_http(self): ).decode() self.assertIn('name="/"', output) - @classmethod - def teardown_class(cls): - cls.server.terminate() + def tearDown(self): + self.server.terminate() diff --git a/examples/http/tracer_client.py b/examples/http/tracer_client.py index 6fd0a726a42..3ae6dc9ffed 100755 --- a/examples/http/tracer_client.py +++ b/examples/http/tracer_client.py @@ -20,7 +20,6 @@ from opentelemetry import trace from opentelemetry.ext import http_requests -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ( BatchExportSpanProcessor, ConsoleSpanExporter, @@ -37,10 +36,7 @@ else: exporter = ConsoleSpanExporter() -# The preferred tracer implementation must be set, as the opentelemetry-api -# defines the interface with a no-op implementation. -trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) -tracer_provider = trace.tracer_provider() +tracer_provider = trace.get_tracer_provider() # SpanExporter receives the spans and send them to the target location. span_processor = BatchExportSpanProcessor(exporter) diff --git a/examples/metrics/observer_example.py b/examples/metrics/observer_example.py index aff25ee476c..7f5614d058e 100644 --- a/examples/metrics/observer_example.py +++ b/examples/metrics/observer_example.py @@ -19,7 +19,7 @@ import psutil from opentelemetry import metrics -from opentelemetry.sdk.metrics import LabelSet, MeterProvider +from opentelemetry.sdk.metrics import LabelSet from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter from opentelemetry.sdk.metrics.export.batcher import UngroupedBatcher from opentelemetry.sdk.metrics.export.controller import PushController @@ -27,7 +27,6 @@ # Configure a stateful batcher batcher = UngroupedBatcher(stateful=True) -metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider()) meter = metrics.get_meter(__name__) # Exporter to export metrics to the console diff --git a/examples/metrics/prometheus.py b/examples/metrics/prometheus.py index 4d30f8abcca..3c9a33f32a3 100644 --- a/examples/metrics/prometheus.py +++ b/examples/metrics/prometheus.py @@ -21,14 +21,13 @@ from opentelemetry import metrics from opentelemetry.ext.prometheus import PrometheusMetricsExporter -from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics import Counter from opentelemetry.sdk.metrics.export.controller import PushController # Start Prometheus client start_http_server(port=8000, addr="localhost") # Meter is responsible for creating and recording metrics -metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider()) meter = metrics.get_meter(__name__) # exporter to export metrics to Prometheus prefix = "MyAppPrefix" diff --git a/examples/metrics/record.py b/examples/metrics/record.py index a376b2aafc0..7a2a014f456 100644 --- a/examples/metrics/record.py +++ b/examples/metrics/record.py @@ -19,13 +19,10 @@ import time from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, MeterProvider +from opentelemetry.sdk.metrics import Counter from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController -# The preferred tracer implementation must be set, as the opentelemetry-api -# defines the interface with a no-op implementation. -metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider()) # Meter is responsible for creating and recording metrics meter = metrics.get_meter(__name__) # exporter to export metrics to the console diff --git a/examples/metrics/simple_example.py b/examples/metrics/simple_example.py index 2b8f5cfac8b..0108d1620b8 100644 --- a/examples/metrics/simple_example.py +++ b/examples/metrics/simple_example.py @@ -23,7 +23,7 @@ import time from opentelemetry import metrics -from opentelemetry.sdk.metrics import Counter, Measure, MeterProvider +from opentelemetry.sdk.metrics import Counter, Measure from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter from opentelemetry.sdk.metrics.export.controller import PushController @@ -44,7 +44,6 @@ def usage(argv): sys.exit(1) # Meter is responsible for creating and recording metrics -metrics.set_preferred_meter_provider_implementation(lambda _: MeterProvider()) # Meter's namespace corresponds to the string passed as the first argument Pass # in True/False to indicate whether the batcher is stateful. True indicates the diff --git a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py index a33b3b58f4c..12d3eed204a 100644 --- a/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py +++ b/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py @@ -23,7 +23,6 @@ import opentelemetry.ext.http_requests from opentelemetry import trace from opentelemetry.ext.flask import instrument_app -from opentelemetry.sdk.trace import TracerProvider def configure_opentelemetry(flask_app: flask.Flask): @@ -42,12 +41,6 @@ def configure_opentelemetry(flask_app: flask.Flask): """ # Start by configuring all objects required to ensure # a complete end to end workflow. - # The preferred implementation of these objects must be set, - # as the opentelemetry-api defines the interface with a no-op - # implementation. - trace.set_preferred_tracer_provider_implementation( - lambda _: TracerProvider() - ) # Next, we need to configure how the values that are used by # traces and metrics are propagated (such as what specific headers @@ -55,7 +48,7 @@ def configure_opentelemetry(flask_app: flask.Flask): # Integrations are the glue that binds the OpenTelemetry API # and the frameworks and libraries that are used together, automatically # creating Spans and propagating context as appropriate. - opentelemetry.ext.http_requests.enable(trace.tracer_provider()) + opentelemetry.ext.http_requests.enable(trace.get_tracer_provider()) instrument_app(flask_app) diff --git a/examples/opentelemetry-example-app/tests/conftest.py b/examples/opentelemetry-example-app/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/examples/opentelemetry-example-app/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/examples/opentracing/main.py b/examples/opentracing/main.py index 665099aeeef..0c331ddee35 100755 --- a/examples/opentracing/main.py +++ b/examples/opentracing/main.py @@ -3,13 +3,10 @@ from opentelemetry import trace from opentelemetry.ext import opentracing_shim from opentelemetry.ext.jaeger import JaegerSpanExporter -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleExportSpanProcessor from rediscache import RedisCache -# Configure the tracer using the default implementation -trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) -tracer_provider = trace.tracer_provider() +tracer_provider = trace.get_tracer_provider() # Configure the tracer to export traces to Jaeger jaeger_exporter = JaegerSpanExporter( diff --git a/ext/opentelemetry-ext-dbapi/README.rst b/ext/opentelemetry-ext-dbapi/README.rst index 895b47b9ba1..432db9529c9 100644 --- a/ext/opentelemetry-ext-dbapi/README.rst +++ b/ext/opentelemetry-ext-dbapi/README.rst @@ -14,7 +14,6 @@ Usage from opentelemetry.trace import tracer_provider from opentelemetry.ext.dbapi import trace_integration - trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) # Ex: mysql.connector trace_integration(tracer_provider(), mysql.connector, "connect", "mysql") diff --git a/ext/opentelemetry-ext-flask/tests/conftest.py b/ext/opentelemetry-ext-flask/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/ext/opentelemetry-ext-flask/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/ext/opentelemetry-ext-jaeger/README.rst b/ext/opentelemetry-ext-jaeger/README.rst index 04f7c4082b5..65c0832a827 100644 --- a/ext/opentelemetry-ext-jaeger/README.rst +++ b/ext/opentelemetry-ext-jaeger/README.rst @@ -32,10 +32,8 @@ gRPC is still not supported by this implementation. from opentelemetry import trace from opentelemetry.ext import jaeger - from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor - trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) # create a JaegerSpanExporter diff --git a/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py b/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py index 81815da935c..1f0c303e065 100644 --- a/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py +++ b/ext/opentelemetry-ext-jaeger/examples/jaeger_exporter_example.py @@ -2,10 +2,8 @@ from opentelemetry import trace from opentelemetry.ext import jaeger -from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor -trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) # create a JaegerSpanExporter @@ -26,7 +24,7 @@ span_processor = BatchExportSpanProcessor(jaeger_exporter) # add to the tracer factory -trace.tracer_provider().add_span_processor(span_processor) +trace.get_tracer_provider().add_span_processor(span_processor) # create some spans for testing with tracer.start_as_current_span("foo") as foo: diff --git a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py index 1ba196d9e0a..93f7abecd4f 100644 --- a/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py +++ b/ext/opentelemetry-ext-opentracing-shim/src/opentelemetry/ext/opentracing_shim/__init__.py @@ -29,12 +29,8 @@ import time from opentelemetry import trace - from opentelemetry.sdk.trace import TracerProvider from opentelemetry.ext.opentracing_shim import create_tracer - # Tell OpenTelemetry which Tracer implementation to use. - trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) - # Create an OpenTelemetry Tracer. otel_tracer = trace.get_tracer(__name__) diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/conftest.py b/ext/opentelemetry-ext-opentracing-shim/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/ext/opentelemetry-ext-opentracing-shim/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py index 0d099340ecc..87a325a58c3 100644 --- a/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py +++ b/ext/opentelemetry-ext-opentracing-shim/tests/test_shim.py @@ -24,7 +24,6 @@ from opentelemetry import propagators, trace from opentelemetry.context.propagation.httptextformat import HTTPTextFormat from opentelemetry.ext.opentracing_shim import util -from opentelemetry.sdk.trace import TracerProvider class TestShim(TestCase): @@ -33,18 +32,10 @@ class TestShim(TestCase): def setUp(self): """Create an OpenTelemetry tracer and a shim before every test case.""" - self.shim = opentracingshim.create_tracer(trace.tracer_provider()) + self.shim = opentracingshim.create_tracer(trace.get_tracer_provider()) @classmethod def setUpClass(cls): - """Set preferred tracer implementation only once rather than before - every test method. - """ - - trace.set_preferred_tracer_provider_implementation( - lambda T: TracerProvider() - ) - # Save current propagator to be restored on teardown. cls._previous_propagator = propagators.get_global_httptextformat() diff --git a/ext/opentelemetry-ext-prometheus/README.rst b/ext/opentelemetry-ext-prometheus/README.rst index e70332556e2..f7049194a2e 100644 --- a/ext/opentelemetry-ext-prometheus/README.rst +++ b/ext/opentelemetry-ext-prometheus/README.rst @@ -29,7 +29,7 @@ The **OpenTelemetry Prometheus Exporter** allows to export `OpenTelemetry`_ metr from opentelemetry import metrics from opentelemetry.ext.prometheus import PrometheusMetricsExporter - from opentelemetry.sdk.metrics import Counter, Meter + from opentelemetry.sdk.metrics import Counter from opentelemetry.sdk.metrics.export.controller import PushController from prometheus_client import start_http_server @@ -37,7 +37,6 @@ The **OpenTelemetry Prometheus Exporter** allows to export `OpenTelemetry`_ metr start_http_server(port=8000, addr="localhost") # Meter is responsible for creating and recording metrics - metrics.set_preferred_meter_implementation(lambda _: Meter()) meter = metrics.meter() # exporter to export metrics to Prometheus prefix = "MyAppPrefix" diff --git a/ext/opentelemetry-ext-prometheus/tests/conftest.py b/ext/opentelemetry-ext-prometheus/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/ext/opentelemetry-ext-prometheus/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py index f6883475386..a0079b1fee1 100644 --- a/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py +++ b/ext/opentelemetry-ext-prometheus/tests/test_prometheus_exporter.py @@ -21,6 +21,7 @@ CustomCollector, PrometheusMetricsExporter, ) +from opentelemetry.metrics import get_meter_provider from opentelemetry.sdk import metrics from opentelemetry.sdk.metrics.export import MetricRecord, MetricsExportResult from opentelemetry.sdk.metrics.export.aggregate import CounterAggregator @@ -28,7 +29,7 @@ class TestPrometheusMetricExporter(unittest.TestCase): def setUp(self): - self._meter = metrics.MeterProvider().get_meter(__name__) + self._meter = get_meter_provider().get_meter(__name__) self._test_metric = self._meter.create_metric( "testname", "testdesc", @@ -74,7 +75,7 @@ def test_export(self): self.assertIs(result, MetricsExportResult.SUCCESS) def test_counter_to_prometheus(self): - meter = metrics.MeterProvider().get_meter(__name__) + meter = get_meter_provider().get_meter(__name__) metric = meter.create_metric( "test@name", "testdesc", @@ -110,8 +111,7 @@ def test_counter_to_prometheus(self): # TODO: Add unit test once Measure Aggregators are available def test_invalid_metric(self): - - meter = metrics.MeterProvider().get_meter(__name__) + meter = get_meter_provider().get_meter(__name__) metric = meter.create_metric( "tesname", "testdesc", "unit", int, TestMetric ) diff --git a/ext/opentelemetry-ext-psycopg2/README.rst b/ext/opentelemetry-ext-psycopg2/README.rst index 127b74f0c79..3cf4cf110b3 100644 --- a/ext/opentelemetry-ext-psycopg2/README.rst +++ b/ext/opentelemetry-ext-psycopg2/README.rst @@ -13,10 +13,8 @@ Usage import psycopg2 from opentelemetry import trace - from opentelemetry.sdk.trace import TracerProvider from opentelemetry.trace.ext.psycopg2 import trace_integration - trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) trace_integration(tracer) cnx = psycopg2.connect(database='Database') diff --git a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py index 18b64364db9..39c2be27f9e 100644 --- a/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py +++ b/ext/opentelemetry-ext-testutil/src/opentelemetry/ext/testutil/wsgitestutil.py @@ -4,7 +4,7 @@ from importlib import reload from opentelemetry import trace as trace_api -from opentelemetry.sdk.trace import TracerProvider, export +from opentelemetry.sdk.trace import export from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( InMemorySpanExporter, ) @@ -16,10 +16,7 @@ class WsgiTestBase(unittest.TestCase): @classmethod def setUpClass(cls): global _MEMORY_EXPORTER # pylint:disable=global-statement - trace_api.set_preferred_tracer_provider_implementation( - lambda T: TracerProvider() - ) - tracer_provider = trace_api.tracer_provider() + tracer_provider = trace_api.get_tracer_provider() _MEMORY_EXPORTER = InMemorySpanExporter() span_processor = export.SimpleExportSpanProcessor(_MEMORY_EXPORTER) tracer_provider.add_span_processor(span_processor) diff --git a/ext/opentelemetry-ext-wsgi/tests/conftest.py b/ext/opentelemetry-ext-wsgi/tests/conftest.py new file mode 100644 index 00000000000..ff9e5ea43bb --- /dev/null +++ b/ext/opentelemetry-ext-wsgi/tests/conftest.py @@ -0,0 +1,25 @@ +# Copyright 2020, 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. + +from os import environ + + +def pytest_sessionstart(session): # pylint: disable=unused-argument + environ["OPENTELEMETRY_PYTHON_TRACER_PROVIDER"] = "sdk_tracer_provider" + environ["OPENTELEMETRY_PYTHON_METER_PROVIDER"] = "sdk_meter_provider" + + +def pytest_sessionfinish(session): # pylint: disable=unused-argument + environ.pop("OPENTELEMETRY_PYTHON_TRACER_PROVIDER") + environ.pop("OPENTELEMETRY_PYTHON_METER_PROVIDER") diff --git a/ext/opentelemetry-ext-zipkin/README.rst b/ext/opentelemetry-ext-zipkin/README.rst index f933ba4a685..9c88e450603 100644 --- a/ext/opentelemetry-ext-zipkin/README.rst +++ b/ext/opentelemetry-ext-zipkin/README.rst @@ -30,10 +30,8 @@ This exporter always send traces to the configured Zipkin collector using HTTP. from opentelemetry import trace from opentelemetry.ext import zipkin - from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchExportSpanProcessor - trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider()) tracer = trace.get_tracer(__name__) # create a ZipkinSpanExporter diff --git a/opentelemetry-api/setup.py b/opentelemetry-api/setup.py index 20e7f581435..eb10d24b94f 100644 --- a/opentelemetry-api/setup.py +++ b/opentelemetry-api/setup.py @@ -12,18 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +from os.path import dirname, exists, join +from pathlib import Path import setuptools -BASE_DIR = os.path.dirname(__file__) -VERSION_FILENAME = os.path.join( - BASE_DIR, "src", "opentelemetry", "util", "version.py" -) +BASE_DIR = dirname(__file__) +VERSION_FILENAME = join(BASE_DIR, "src", "opentelemetry", "util", "version.py") PACKAGE_INFO = {} with open(VERSION_FILENAME) as f: exec(f.read(), PACKAGE_INFO) +# FIXME Make this script install the configuration file in +# ~/.config/opentelemetry_python.json +configuration_file_path = join( + Path.home(), ".config", "opentelemetry_python.json" +) + +data_files = [] + +if not exists(configuration_file_path): + data_files.append( + ( + dirname(configuration_file_path), + [ + join( + "src", + "opentelemetry", + "configuration", + "opentelemetry_python.json", + ) + ], + ) + ) + setuptools.setup( name="opentelemetry-api", version=PACKAGE_INFO["__version__"], @@ -61,12 +83,22 @@ zip_safe=False, entry_points={ "opentelemetry_context": [ + "default_context = opentelemetry.context:DefaultContext", "contextvars_context = " "opentelemetry.context.contextvars_context:" "ContextVarsRuntimeContext", "threadlocal_context = " "opentelemetry.context.threadlocal_context:" "ThreadLocalRuntimeContext", - ] + ], + "opentelemetry_meter_provider": [ + "default_meter_provider = " + "opentelemetry.metrics:DefaultMeterProvider" + ], + "opentelemetry_tracer_provider": [ + "default_tracer_provider = " + "opentelemetry.trace:DefaultTracerProvider" + ], }, + data_files=data_files, ) diff --git a/opentelemetry-api/src/opentelemetry/configuration/__init__.py b/opentelemetry-api/src/opentelemetry/configuration/__init__.py new file mode 100644 index 00000000000..3e89f5f7d3a --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/configuration/__init__.py @@ -0,0 +1,78 @@ +# Copyright 2020, 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. + +""" +Simple configuration manager + +This configuration manager reads configuration values from a JSON file, the +values read there can be overriden by environment variables. + +It would probably be better to replace this configuration manager with a more +powerful one, Dynaconf, for example. +""" + +from json import load +from os import environ +from os.path import exists, join +from pathlib import Path + + +class Configuration: + _instance = None + + __slots__ = ["tracer_provider", "meter_provider"] + + def __new__(cls): + if Configuration._instance is None: + + configuration = { + key: "default_{}".format(key) for key in cls.__slots__ + } + + configuration_file_path = join( + Path.home(), ".config", "opentelemetry_python.json" + ) + + if exists(configuration_file_path): + + with open(configuration_file_path) as configuration_file: + file_configuration = load(configuration_file) + + for key, value in configuration.items(): + configuration[key] = file_configuration.get(key, value) + + for key, value in configuration.items(): + configuration[key] = environ.get( + "OPENTELEMETRY_PYTHON_{}".format(key.upper()), value + ) + + for key, value in configuration.items(): + underscored_key = "_{}".format(key) + + setattr(Configuration, underscored_key, value) + setattr( + Configuration, + key, + property( + ( + lambda underscored_key: lambda self: getattr( + self, underscored_key + ) + )(underscored_key) + ), + ) + + Configuration._instance = object.__new__(cls) + + return cls._instance diff --git a/opentelemetry-api/src/opentelemetry/configuration/opentelemetry_python.json b/opentelemetry-api/src/opentelemetry/configuration/opentelemetry_python.json new file mode 100644 index 00000000000..c0ac3f4ff93 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/configuration/opentelemetry_python.json @@ -0,0 +1,4 @@ +{ + "tracer_provider": "default_tracer_provider", + "meter_provider": "default_meter_provider" +} diff --git a/opentelemetry-api/src/opentelemetry/configuration/py.typed b/opentelemetry-api/src/opentelemetry/configuration/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-api/src/opentelemetry/metrics/__init__.py b/opentelemetry-api/src/opentelemetry/metrics/__init__.py index 3ba9bcad009..42ac2510043 100644 --- a/opentelemetry-api/src/opentelemetry/metrics/__init__.py +++ b/opentelemetry-api/src/opentelemetry/metrics/__init__.py @@ -27,13 +27,14 @@ """ import abc -import logging -from typing import Callable, Dict, Optional, Sequence, Tuple, Type, TypeVar +from logging import getLogger +from typing import Callable, Dict, Sequence, Tuple, Type, TypeVar -from opentelemetry.util import loader +from pkg_resources import iter_entry_points -logger = logging.getLogger(__name__) +from opentelemetry.configuration import Configuration +_LOGGER = getLogger(__name__) ValueT = TypeVar("ValueT", int, float) @@ -397,15 +398,7 @@ def get_label_set(self, labels: Dict[str, str]) -> "LabelSet": return DefaultLabelSet() -# Once https://github.com/python/mypy/issues/7092 is resolved, -# the following type definition should be replaced with -# from opentelemetry.util.loader import ImplementationFactory -ImplementationFactory = Callable[ - [Type[MeterProvider]], Optional[MeterProvider] -] - _METER_PROVIDER = None -_METER_PROVIDER_FACTORY = None def get_meter( @@ -417,52 +410,34 @@ def get_meter( This function is a convenience wrapper for opentelemetry.metrics.meter_provider().get_meter """ - return meter_provider().get_meter( + return get_meter_provider().get_meter( instrumenting_module_name, stateful, instrumenting_library_version ) -def meter_provider() -> MeterProvider: - """Gets the current global :class:`~.MeterProvider` object. - - If there isn't one set yet, a default will be loaded. - """ - global _METER_PROVIDER, _METER_PROVIDER_FACTORY # pylint:disable=global-statement +def get_meter_provider() -> MeterProvider: + """Gets the current global :class:`~.MeterProvider` object.""" + global _METER_PROVIDER # pylint: disable=global-statement if _METER_PROVIDER is None: - # pylint:disable=protected-access + configured_meter_provider = ( + Configuration().meter_provider # pylint: disable=no-member + ) + try: - _METER_PROVIDER = loader._load_impl( - MeterProvider, _METER_PROVIDER_FACTORY # type: ignore + _METER_PROVIDER = next( + iter_entry_points( + "opentelemetry_meter_provider", configured_meter_provider + ) + ).load()() + except Exception: # pylint: disable=broad-except + # FIXME Decide on how to handle this. Should an exception be + # raised here, or only a message should be logged and should + # we fall back to the default meter provider? + _LOGGER.error( + "Failed to load configured meter provider %s", + configured_meter_provider, ) - except TypeError: - # if we raised an exception trying to instantiate an - # abstract class, default to no-op meter impl - logger.warning( - "Unable to instantiate MeterProvider from meter provider factory.", - exc_info=True, - ) - _METER_PROVIDER = DefaultMeterProvider() - _METER_PROVIDER_FACTORY = None + raise return _METER_PROVIDER - - -def set_preferred_meter_provider_implementation( - factory: ImplementationFactory, -) -> None: - """Set the factory to be used to create the meter provider. - - See :mod:`opentelemetry.util.loader` for details. - - This function may not be called after a meter is already loaded. - - Args: - factory: Callback that should create a new :class:`MeterProvider` instance. - """ - global _METER_PROVIDER_FACTORY # pylint:disable=global-statement - - if _METER_PROVIDER: - raise RuntimeError("MeterProvider already loaded.") - - _METER_PROVIDER_FACTORY = factory diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index a6633e434a0..05c6b962237 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -63,20 +63,23 @@ .. versionadded:: 0.1.0 .. versionchanged:: 0.3.0 `TracerProvider` was introduced and the global ``tracer`` getter was replaced - by `tracer_provider`. + by `get_tracer_provider`. """ import abc import enum -import logging import types as python_types import typing from contextlib import contextmanager +from logging import getLogger +from pkg_resources import iter_entry_points + +from opentelemetry.configuration import Configuration from opentelemetry.trace.status import Status -from opentelemetry.util import loader, types +from opentelemetry.util import types -logger = logging.getLogger(__name__) +_LOGGER = getLogger(__name__) # TODO: quarantine ParentSpan = typing.Optional[typing.Union["Span", "SpanContext"]] @@ -639,15 +642,7 @@ def use_span( yield -# Once https://github.com/python/mypy/issues/7092 is resolved, -# the following type definition should be replaced with -# from opentelemetry.util.loader import ImplementationFactory -ImplementationFactory = typing.Callable[ - [typing.Type[TracerProvider]], typing.Optional[TracerProvider] -] - -_TRACER_PROVIDER = None # type: typing.Optional[TracerProvider] -_TRACER_PROVIDER_FACTORY = None # type: typing.Optional[ImplementationFactory] +_TRACER_PROVIDER = None def get_tracer( @@ -658,53 +653,35 @@ def get_tracer( This function is a convenience wrapper for opentelemetry.trace.tracer_provider().get_tracer """ - return tracer_provider().get_tracer( + return get_tracer_provider().get_tracer( instrumenting_module_name, instrumenting_library_version ) -def tracer_provider() -> TracerProvider: - """Gets the current global :class:`~.TracerProvider` object. - - If there isn't one set yet, a default will be loaded. - """ - global _TRACER_PROVIDER, _TRACER_PROVIDER_FACTORY # pylint:disable=global-statement +def get_tracer_provider() -> TracerProvider: + """Gets the current global :class:`~.TracerProvider` object.""" + global _TRACER_PROVIDER # pylint: disable=global-statement if _TRACER_PROVIDER is None: - # pylint:disable=protected-access + configured_tracer_provider = ( + Configuration().tracer_provider # pylint: disable=no-member + ) + try: - _TRACER_PROVIDER = loader._load_impl( - TracerProvider, _TRACER_PROVIDER_FACTORY # type: ignore + _TRACER_PROVIDER = next( + iter_entry_points( + "opentelemetry_tracer_provider", + name=configured_tracer_provider, + ) + ).load()() + except Exception: # pylint: disable=broad-except + # FIXME Decide on how to handle this. Should an exception be + # raised here, or only a message should be logged and should + # we fall back to the default tracer provider? + _LOGGER.error( + "Failed to load tracer implementation: %s", + configured_tracer_provider, ) - except TypeError: - # if we raised an exception trying to instantiate an - # abstract class, default to no-op tracer impl - logger.warning( - "Unable to instantiate TracerProvider from factory.", - exc_info=True, - ) - _TRACER_PROVIDER = DefaultTracerProvider() - del _TRACER_PROVIDER_FACTORY + raise return _TRACER_PROVIDER - - -def set_preferred_tracer_provider_implementation( - factory: ImplementationFactory, -) -> None: - """Set the factory to be used to create the global TracerProvider. - - See :mod:`opentelemetry.util.loader` for details. - - This function may not be called after a tracer is already loaded. - - Args: - factory: Callback that should create a new :class:`TracerProvider` - instance. - """ - global _TRACER_PROVIDER_FACTORY # pylint:disable=global-statement - - if _TRACER_PROVIDER: - raise RuntimeError("TracerProvider already loaded.") - - _TRACER_PROVIDER_FACTORY = factory diff --git a/opentelemetry-api/src/opentelemetry/util/loader.py b/opentelemetry-api/src/opentelemetry/util/loader.py deleted file mode 100644 index eeda2b3e7f7..00000000000 --- a/opentelemetry-api/src/opentelemetry/util/loader.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2019, 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. - - -""" -The OpenTelemetry loader module is mainly used internally to load the -implementation for global objects like -:func:`opentelemetry.trace.tracer_provider`. - -.. _loader-factory: - -An instance of a global object of type ``T`` is always created with a factory -function with the following signature:: - - def my_factory_for_t(api_type: typing.Type[T]) -> typing.Optional[T]: - # ... - -That function is called with e.g., the type of the global object it should -create as an argument (e.g. the type object -:class:`opentelemetry.trace.TracerProvider`) and should return an instance of that type -(such that ``instanceof(my_factory_for_t(T), T)`` is true). Alternatively, it -may return ``None`` to indicate that the no-op default should be used. - -When loading an implementation, the following algorithm is used to find a -factory function or other means to create the global object: - - 1. If the environment variable - :samp:`OPENTELEMETRY_PYTHON_IMPLEMENTATION_{getter-name}` (e.g., - ``OPENTELEMETRY_PYTHON_IMPLEMENTATION_TRACERPROVIDER``) is set to an - nonempty value, an attempt is made to import a module with that name and - use a factory function named ``get_opentelemetry_implementation`` in it. - 2. Otherwise, the same is tried with the environment variable - ``OPENTELEMETRY_PYTHON_IMPLEMENTATION_DEFAULT``. - 3. Otherwise, if a :samp:`set_preferred_{}_implementation` was - called (e.g. - :func:`opentelemetry.trace.set_preferred_tracer_provider_implementation`), - the callback set there is used (that is, the environment variables - override the callback set in code). - 4. Otherwise, if :func:`set_preferred_default_implementation` was called, - the callback set there is used. - 5. Otherwise, an attempt is made to import and use the OpenTelemetry SDK. - 6. Otherwise the default implementation that ships with the API - distribution (a fast no-op implementation) is used. - -If any of the above steps fails (e.g., a module is loaded but does not define -the required function or a module name is set but the module fails to load), -the search immediatelly skips to the last step. - -Note that the first two steps (those that query environment variables) are -skipped if :data:`sys.flags` has ``ignore_environment`` set (which usually -means that the Python interpreter was invoked with the ``-E`` or ``-I`` flag). -""" - -import importlib -import os -import sys -from typing import Callable, Optional, Type, TypeVar - -_T = TypeVar("_T") - -# "Untrusted" because this is usually user-provided and we don't trust the user -# to really return a _T: by using object, mypy forces us to check/cast -# explicitly. -_UntrustedImplFactory = Callable[[Type[_T]], Optional[object]] - - -# This would be the normal ImplementationFactory which would be used to -# annotate setters, were it not for https://github.com/python/mypy/issues/7092 -# Once that bug is resolved, setters should use this instead of duplicating the -# code. -# ImplementationFactory = Callable[[Type[_T]], Optional[_T]] - -_DEFAULT_FACTORY = None # type: Optional[_UntrustedImplFactory[object]] - - -def _try_load_impl_from_modname( - implementation_modname: str, api_type: Type[_T] -) -> Optional[_T]: - try: - implementation_mod = importlib.import_module(implementation_modname) - except (ImportError, SyntaxError): - # TODO Log/warn - return None - - return _try_load_impl_from_mod(implementation_mod, api_type) - - -def _try_load_impl_from_mod( - implementation_mod: object, api_type: Type[_T] -) -> Optional[_T]: - - try: - # Note: We use such a long name to avoid calling a function that is not - # intended for this API. - - implementation_fn = getattr( - implementation_mod, "get_opentelemetry_implementation" - ) # type: _UntrustedImplFactory[_T] - except AttributeError: - # TODO Log/warn - return None - - return _try_load_impl_from_callback(implementation_fn, api_type) - - -def _try_load_impl_from_callback( - implementation_fn: _UntrustedImplFactory[_T], api_type: Type[_T] -) -> Optional[_T]: - result = implementation_fn(api_type) - if result is None: - return None - if not isinstance(result, api_type): - # TODO Warn if wrong type is returned - return None - - # TODO: Warn if implementation_fn returns api_type(): It should return None - # to indicate using the default. - - return result - - -def _try_load_configured_impl( - api_type: Type[_T], factory: Optional[_UntrustedImplFactory[_T]] -) -> Optional[_T]: - """Attempts to find any specially configured implementation. If none is - configured, or a load error occurs, returns `None` - """ - implementation_modname = None - if not sys.flags.ignore_environment: - implementation_modname = os.getenv( - "OPENTELEMETRY_PYTHON_IMPLEMENTATION_" + api_type.__name__.upper() - ) - if implementation_modname: - return _try_load_impl_from_modname( - implementation_modname, api_type - ) - implementation_modname = os.getenv( - "OPENTELEMETRY_PYTHON_IMPLEMENTATION_DEFAULT" - ) - if implementation_modname: - return _try_load_impl_from_modname( - implementation_modname, api_type - ) - if factory is not None: - return _try_load_impl_from_callback(factory, api_type) - if _DEFAULT_FACTORY is not None: - return _try_load_impl_from_callback(_DEFAULT_FACTORY, api_type) - return None - - -# Public to other opentelemetry-api modules -def _load_impl( - api_type: Type[_T], factory: Optional[Callable[[Type[_T]], Optional[_T]]] -) -> _T: - """Tries to load a configured implementation, if unsuccessful, returns a - fast no-op implemenation that is always available. - """ - - result = _try_load_configured_impl(api_type, factory) - if result is None: - return api_type() - return result - - -def set_preferred_default_implementation( - implementation_factory: _UntrustedImplFactory[_T], -) -> None: - """Sets a factory function that may be called for any implementation - object. See the :ref:`module docs ` for more details.""" - global _DEFAULT_FACTORY # pylint:disable=global-statement - _DEFAULT_FACTORY = implementation_factory diff --git a/opentelemetry-api/tests/configuration/__init__.py b/opentelemetry-api/tests/configuration/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/opentelemetry-api/tests/configuration/test_configuration.py b/opentelemetry-api/tests/configuration/test_configuration.py new file mode 100644 index 00000000000..cd3b9aa3e0a --- /dev/null +++ b/opentelemetry-api/tests/configuration/test_configuration.py @@ -0,0 +1,85 @@ +# Copyright 2020, 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. + +from json import dumps +from os import getcwd +from unittest import TestCase +from unittest.mock import patch + +from opentelemetry.configuration import Configuration +from pytest import fixture # pylint: disable=import-error + + +class TestConfiguration(TestCase): + @fixture(autouse=True) + def configdir(self, tmpdir): # pylint: disable=no-self-use + tmpdir.chdir() + tmpdir.mkdir(".config").join("opentelemetry_python.json").write( + dumps({"tracer_provider": "default_tracer_provider"}) + ) + + def setUp(self): + Configuration._instance = None # pylint: disable=protected-access + + def tearDown(self): + Configuration._instance = None # pylint: disable=protected-access + + def test_singleton(self): + self.assertIs(Configuration(), Configuration()) + + @patch("pathlib.Path.home") + def test_configuration_file(self, mock_home_path): + mock_home_path.return_value = getcwd() + + self.assertEqual( + Configuration().tracer_provider, "default_tracer_provider" + ) # pylint: disable=no-member + self.assertEqual( + Configuration().meter_provider, "default_meter_provider" + ) # pylint: disable=no-member + + @patch.dict( + "os.environ", + {"OPENTELEMETRY_PYTHON_METER_PROVIDER": "overridden_meter_provider"}, + ) + def test_environment_variables(self): + self.assertEqual( + Configuration().tracer_provider, "default_tracer_provider" + ) # pylint: disable=no-member + self.assertEqual( + Configuration().meter_provider, "overridden_meter_provider" + ) # pylint: disable=no-member + + @patch("pathlib.Path.home") + @patch.dict( + "os.environ", + {"OPENTELEMETRY_PYTHON_METER_PROVIDER": "reoverridden_meter_provider"}, + ) + def test_configuration_file_environment_variables(self, mock_home_path): + mock_home_path.return_value = getcwd() + + self.assertEqual( + Configuration().tracer_provider, "default_tracer_provider" + ) + self.assertEqual( + Configuration().meter_provider, "reoverridden_meter_provider" + ) + + def test_property(self): + with self.assertRaises(AttributeError): + Configuration().tracer_provider = "new_tracer_provider" + + def test_slots(self): + with self.assertRaises(AttributeError): + Configuration().xyz = "xyz" # pylint: disable=assigning-non-slot diff --git a/opentelemetry-api/tests/mypysmoke.py b/opentelemetry-api/tests/mypysmoke.py index bbbda93ef29..2891f3ee624 100644 --- a/opentelemetry-api/tests/mypysmoke.py +++ b/opentelemetry-api/tests/mypysmoke.py @@ -16,4 +16,4 @@ def dummy_check_mypy_returntype() -> opentelemetry.trace.TracerProvider: - return opentelemetry.trace.tracer_provider() + return opentelemetry.trace.get_tracer_provider() diff --git a/opentelemetry-api/tests/test_loader.py b/opentelemetry-api/tests/test_loader.py deleted file mode 100644 index 76575df705a..00000000000 --- a/opentelemetry-api/tests/test_loader.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2019, 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. - -import os -import sys -import unittest -from importlib import reload -from typing import Any, Callable - -from opentelemetry import trace -from opentelemetry.util import loader - -DUMMY_TRACER_PROVIDER = None - - -class DummyTracerProvider(trace.TracerProvider): - def get_tracer( - self, - instrumenting_module_name: str, - instrumenting_library_version: str = "", - ) -> "trace.Tracer": - return trace.DefaultTracer() - - -def get_opentelemetry_implementation(type_): - global DUMMY_TRACER_PROVIDER # pylint:disable=global-statement - assert type_ is trace.TracerProvider - DUMMY_TRACER_PROVIDER = DummyTracerProvider() - return DUMMY_TRACER_PROVIDER - - -# pylint:disable=redefined-outer-name,protected-access,unidiomatic-typecheck - - -class TestLoader(unittest.TestCase): - def setUp(self): - reload(loader) - reload(trace) - - # Need to reload self, otherwise DummyTracerProvider will have the wrong - # base class after reloading `trace`. - reload(sys.modules[__name__]) - - def test_get_default(self): - tracer_provider = trace.tracer_provider() - self.assertIs(type(tracer_provider), trace.DefaultTracerProvider) - - def test_preferred_impl(self): - trace.set_preferred_tracer_provider_implementation( - get_opentelemetry_implementation - ) - tracer_provider = trace.tracer_provider() - self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER) - - # NOTE: We use do_* + *_ methods because subtest wouldn't run setUp, - # which we require here. - def do_test_preferred_impl(self, setter: Callable[[Any], Any]) -> None: - setter(get_opentelemetry_implementation) - tracer_provider = trace.tracer_provider() - self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER) - - def test_preferred_impl_with_tracer(self): - self.do_test_preferred_impl( - trace.set_preferred_tracer_provider_implementation - ) - - def test_preferred_impl_with_default(self): - self.do_test_preferred_impl( - loader.set_preferred_default_implementation - ) - - def test_try_set_again(self): - self.assertTrue(trace.tracer_provider()) - # Try setting after the tracer_provider has already been created: - with self.assertRaises(RuntimeError) as einfo: - trace.set_preferred_tracer_provider_implementation( - get_opentelemetry_implementation - ) - self.assertIn("already loaded", str(einfo.exception)) - - def do_test_get_envvar(self, envvar_suffix: str) -> None: - global DUMMY_TRACER_PROVIDER # pylint:disable=global-statement - - # Test is not runnable with this! - self.assertFalse(sys.flags.ignore_environment) - - envname = "OPENTELEMETRY_PYTHON_IMPLEMENTATION_" + envvar_suffix - os.environ[envname] = __name__ - try: - tracer_provider = trace.tracer_provider() - self.assertIs(tracer_provider, DUMMY_TRACER_PROVIDER) - finally: - DUMMY_TRACER_PROVIDER = None - del os.environ[envname] - self.assertIs(type(tracer_provider), DummyTracerProvider) - - def test_get_envvar_tracer(self): - return self.do_test_get_envvar("TRACERPROVIDER") - - def test_get_envvar_default(self): - return self.do_test_get_envvar("DEFAULT") diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py index 7c4d8e3549b..2e0339b99dd 100644 --- a/opentelemetry-api/tests/trace/test_globals.py +++ b/opentelemetry-api/tests/trace/test_globals.py @@ -1,40 +1,18 @@ -import importlib import unittest +from unittest.mock import patch from opentelemetry import trace class TestGlobals(unittest.TestCase): def setUp(self): - importlib.reload(trace) + self._patcher = patch("opentelemetry.trace._TRACER_PROVIDER") + self._mock_tracer_provider = self._patcher.start() - # This class has to be declared after the importlib reload, or else it - # will inherit from an old TracerProvider, rather than the new - # TracerProvider ABC created from reload. - - static_tracer = trace.DefaultTracer() - - class DummyTracerProvider(trace.TracerProvider): - """TracerProvider used for testing""" - - def get_tracer( - self, - instrumenting_module_name: str, - instrumenting_library_version: str = "", - ) -> trace.Tracer: - # pylint:disable=no-self-use,unused-argument - return static_tracer - - trace.set_preferred_tracer_provider_implementation( - lambda _: DummyTracerProvider() - ) - - @staticmethod - def tearDown() -> None: - importlib.reload(trace) + def tearDown(self) -> None: + self._patcher.stop() def test_get_tracer(self): """trace.get_tracer should proxy to the global tracer provider.""" - from_global_api = trace.get_tracer("foo") - from_tracer_api = trace.tracer_provider().get_tracer("foo") - self.assertIs(from_global_api, from_tracer_api) + trace.get_tracer("foo", "var") + self._mock_tracer_provider.get_tracer.assert_called_with("foo", "var") diff --git a/opentelemetry-sdk/setup.py b/opentelemetry-sdk/setup.py index 50fe925605c..c471ff02311 100644 --- a/opentelemetry-sdk/setup.py +++ b/opentelemetry-sdk/setup.py @@ -56,4 +56,12 @@ "/tree/master/opentelemetry-sdk" ), zip_safe=False, + entry_points={ + "opentelemetry_meter_provider": [ + "sdk_meter_provider = opentelemetry.sdk.metrics:MeterProvider" + ], + "opentelemetry_tracer_provider": [ + "sdk_tracer_provider = opentelemetry.sdk.trace:TracerProvider" + ], + }, )