From 0ec25260a6c649cbaf18c0398600444b7d20385c Mon Sep 17 00:00:00 2001 From: Owais Lone Date: Mon, 24 Aug 2020 04:40:22 +0530 Subject: [PATCH] POC: actual auto-instrumentation --- opentelemetry-instrumentation/README.rst | 78 +++++++++- .../auto_instrumentation/__init__.py | 67 ++++++++- .../auto_instrumentation/sitecustomize.py | 28 +++- .../auto_instrumentation/tracing.py | 137 ++++++++++++++++++ .../instrumentation/bootstrap.py | 64 +++++++- .../opentelemetry/instrumentation/symbols.py | 13 ++ .../tests/test_auto_tracing.py | 129 +++++++++++++++++ .../tests/test_bootstrap.py | 24 ++- .../tests/test_run.py | 23 ++- 9 files changed, 531 insertions(+), 32 deletions(-) create mode 100644 opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/tracing.py create mode 100644 opentelemetry-instrumentation/src/opentelemetry/instrumentation/symbols.py create mode 100644 opentelemetry-instrumentation/tests/test_auto_tracing.py diff --git a/opentelemetry-instrumentation/README.rst b/opentelemetry-instrumentation/README.rst index 6be744251b2..05cd263235c 100644 --- a/opentelemetry-instrumentation/README.rst +++ b/opentelemetry-instrumentation/README.rst @@ -16,6 +16,31 @@ Installation This package provides a couple of commands that help automatically instruments a program: + +opentelemetry-bootstrap +----------------------- + +:: + + opentelemetry-bootstrap --action=install|requirements + +This commands inspects the active Python site-packages and figures out which +instrumentation packages the user might want to install. By default it prints out +a list of the suggested instrumentation packages which can be added to a requirements.txt +file. It also supports installing the suggested packages when run with :code:`--action=install` +flag. + +The command also installs the OTLP exporter by default. This can be overriden by specifying another +exporter using the `--exporter` or `-e` CLI flag. Multiple exporters can be installed by specifying +the flag more than once. Run `opentelemetry-bootstrap --help` to list down all supported exporters. + +Manually specifying exporters to install: + +:: + + opentelemetry-bootstrap -e=otlp -e=zipkin + + opentelemetry-instrument ------------------------ @@ -23,23 +48,60 @@ opentelemetry-instrument opentelemetry-instrument python program.py +The instrument command will try to automatically detect packages used by your python program +and when possible, apply automatic tracing instrumentation on them. This means your program +will get automatic distrubuted tracing for free without having to make any code changes +at all. This will also configure a global tracer and tracing exporter without you having to +make any code changes. By default, the instrument command will use the OTLP exporter but +this can be overrided when needed. + +The command supports the following configuration options as CLI arguments and environments vars: + + +* ``--trace-exporter`` or ``OTEL_TRACE_EXPORTER`` + +Used to specify which trace exporter to use. Can be set to one of the well-known +exporter names (see below) or a fully qualified Python import path to a trace +exporter implementation. + + - Defaults to `otlp`. + - Can be set to `none` to disbale automatic tracer initialization. + +Well known trace exporter names: + + - datadog + - jaeger + - opencensus + - otlp + - zipkin + +* ``--tracer-provider`` or ``OTEL_TRACER_PROVIDER`` + +Must be a fully qualified Python import path to a Tracer Provider implementation or +a callable that returns a tracer provider instance. + +Defaults to `opentelemetry.sdk.trace.TracerProvider` + + +* ``--service-name`` or ``OTEL_SERVICE_NAME`` + +When present the value is passed on to the relevant exporter initializer as ``service_name`` argument. + The code in ``program.py`` needs to use one of the packages for which there is an OpenTelemetry integration. For a list of the available integrations please check `here `_ +Passing arguments to program +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -opentelemetry-bootstrap ------------------------ +Any arguments passed apply to the instrument command by default. You can still pass arguments to your program by +separating them from the rest of the command with ``--``. For example, :: - opentelemetry-bootstrap --action=install|requirements + opentelemetry-instrument -e otlp flask run -- --port=3000 -This commands inspects the active Python site-packages and figures out which -instrumentation packages the user might want to install. By default it prints out -a list of the suggested instrumentation packages which can be added to a requirements.txt -file. It also supports installing the suggested packages when run with :code:`--action=install` -flag. +The above command will pass ``-e otlp` to the instrument command and ``--port=3000`` to ``flask run``. References ---------- diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py index 893b8939b93..f68545ddeb8 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py @@ -14,16 +14,77 @@ # See the License for the specific language governing permissions and # limitations under the License. +import argparse from logging import getLogger from os import environ, execl, getcwd from os.path import abspath, dirname, pathsep from shutil import which from sys import argv +from opentelemetry.instrumentation import symbols + logger = getLogger(__file__) +def parse_args(): + parser = argparse.ArgumentParser( + description=""" + opentelemetry-instrument automatically instruments a Python program and it's dependencies + and then runs the program. + """ + ) + + parser.add_argument( + "-tp", + "--tracer-provider", + required=False, + help=""" + Uses the specified tracer provider. + Must be a fully qualified python import path to a tracer provider implementation + or a callable that returns a new instance of a tracer provider. + """ + ) + + parser.add_argument( + "-e", + "--exporter", + required=False, + help=""" + Uses the specified exporter to export spans. + + Must be one of the following: + - Name of a well-known trace exporter. Choices are: + {0} + - A fully qualified python import path to a trace exporter implementation + or a callable that returns a new instance of a trace exporter. + """.format(symbols.trace_exporters) + ) + + parser.add_argument( + "-s", + "--service-name", + required=False, + help=""" + The service name that should be passed to a trace exporter. + """ + ) + + parser.add_argument('command', nargs='+') + return parser.parse_args() + + +def set_default_env_vars(args): + if args.exporter: + environ['OTEL_TRACE_EXPORTER'] = args.exporter + if args.tracer_provider: + environ['OTEL_TRACE_PROVIDER'] = args.tracer_provider + if args.service_name: + environ['OTEL_SERVICE_NAME'] = args.service_name + + def run() -> None: + args = parse_args() + set_default_env_vars(args) python_path = environ.get("PYTHONPATH") @@ -49,6 +110,6 @@ def run() -> None: environ["PYTHONPATH"] = pathsep.join(python_path) - executable = which(argv[1]) - - execl(executable, executable, *argv[2:]) + command = args.command + executable = which(command[0]) + execl(executable, executable, *command[1:]) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py index b070bf5d773..504c96d8c8a 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/sitecustomize.py @@ -12,17 +12,31 @@ # See the License for the specific language governing permissions and # limitations under the License. +from functools import partial +from collections import defaultdict from logging import getLogger - from pkg_resources import iter_entry_points +from opentelemetry import metrics +from opentelemetry import trace +from opentelemetry.configuration import Configuration +from opentelemetry.sdk.resources import Resource + +from opentelemetry.instrumentation.auto_instrumentation.tracing import initialize_tracing + logger = getLogger(__file__) -for entry_point in iter_entry_points("opentelemetry_instrumentor"): - try: - entry_point.load()().instrument() # type: ignore - logger.debug("Instrumented %s", entry_point.name) - except Exception: # pylint: disable=broad-except - logger.exception("Instrumenting of %s failed", entry_point.name) +def auto_instrument(): + for entry_point in iter_entry_points("opentelemetry_instrumentor"): + try: + entry_point.load()().instrument() # type: ignore + logger.debug("Instrumented %s", entry_point.name) + + except Exception: # pylint: disable=broad-except + logger.exception("Instrumenting of %s failed", entry_point.name) + + +initialize_tracing() +auto_instrument() \ No newline at end of file diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/tracing.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/tracing.py new file mode 100644 index 00000000000..e66e63b968b --- /dev/null +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/tracing.py @@ -0,0 +1,137 @@ +# 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. + +from functools import partial +from collections import defaultdict +from logging import getLogger +from pkg_resources import iter_entry_points + +from opentelemetry import metrics +from opentelemetry import trace +from opentelemetry.configuration import Configuration +from opentelemetry.sdk.resources import Resource +from opentelemetry.instrumentation import symbols + +logger = getLogger(__file__) + + +# Defaults +_DEFAULT_TRACE_EXPORTER = symbols.exporter_otlp +_DEFAULT_TRACER_PROVIDER = 'opentelemetry.sdk.trace.TracerProvider' +_DEFAULT_SPAN_PROCESSOR = 'opentelemetry.sdk.trace.export.BatchExportSpanProcessor' + + +_trace_exporter_classes = { + symbols.exporter_dd: 'opentelemetry.exporter.datadog.DatadogSpanExporter', + symbols.exporter_oc: 'opentelemetry.exporter.opencensus.trace_exporter.OpenCensusSpanExporter', + symbols.exporter_otlp: 'opentelemetry.exporter.otlp.trace_exporter.OTLPSpanExporter', + symbols.exporter_jaeger: 'opentelemetry.exporter.jaeger.JaegerSpanExporter', + symbols.exporter_zipkin: 'opentelemetry.exporter.zipkin.ZipkinSpanExporter', +} + +_span_processors_by_exporter = defaultdict(lambda: _DEFAULT_SPAN_PROCESSOR, { + symbols.exporter_dd: 'opentelemetry.exporter.datadog.DatadogExportSpanProcessor', +}) + + +def get_service_name(): + return Configuration().SERVICE_NAME or '' + + +def get_tracer_provider(): + return Configuration().TRACER_PROVIDER or _DEFAULT_TRACER_PROVIDER + + +def get_exporter_name(): + return Configuration().TRACE_EXPORTER or _DEFAULT_TRACE_EXPORTER + + +def _trace_init( + trace_exporter, + tracer_provider, + span_processor, +): + exporter = trace_exporter() + processor = span_processor(exporter) + provider = tracer_provider() + trace.set_tracer_provider(provider) + provider.add_span_processor(processor) + + +def _default_trace_init(exporter, provider, processor): + service_name = get_service_name() + if service_name: + exporter = partial(exporter, service_name=get_service_name()) + _trace_init(exporter, provider, processor) + + +def _otlp_trace_init(exporter, provider, processor): + resource = Resource(labels={ + 'service_name': get_service_name() + }) + provider = partial(provider, resource=resource) + _trace_init(exporter, provider, processor) + + +def _dd_trace_init(exporter, provider, processor): + exporter = partial(exporter, service=get_service_name()) + _trace_init(exporter, provider, processor) + + +_initializers = defaultdict(lambda: _default_trace_init, { + symbols.exporter_dd: _dd_trace_init, + symbols.exporter_otlp: _otlp_trace_init, +}) + + +def _import(import_path): + split_path = import_path.rsplit('.', 1) + if len(split_path) < 2: + raise ModuleNotFoundError('could not import module or class: {0}'.format(import_path)) + module, class_name = split_path + mod = __import__(module, fromlist=[class_name]) + return getattr(mod, class_name) + + +def _load_component(components, name): + if name.lower() == 'none': + return None + + component = components.get(name.lower(), name) + if not component: + logger.info('component not found with name: {0}'.format(name)) + return + + if isinstance(component, str): + try: + return _import(component) + except ModuleNotFoundError as exc: + logger.error(exc.msg) + return None + return component + + +def initialize_tracing(): + exporter_name = get_exporter_name() + print('exporter: ', get_exporter_name()) + TraceExporter = _load_component(_trace_exporter_classes, exporter_name) + if TraceExporter is None: + logger.info('not using any trace exporter') + return + + print('provider: ', get_tracer_provider()) + TracerProvider = _load_component({}, get_tracer_provider()) + SpanProcessor = _import(_span_processors_by_exporter[exporter_name]) + initializer = _initializers[exporter_name] + initializer(TraceExporter, TracerProvider, SpanProcessor) \ No newline at end of file diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py index 125032b95da..83d047903c7 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap.py @@ -20,6 +20,10 @@ import sys from logging import getLogger +from opentelemetry.instrumentation import symbols + +from .version import __version__ + logger = getLogger(__file__) @@ -80,7 +84,7 @@ } -def _install_package(library, instrumentation): +def _install_instrumentation(library, instrumentation): """ Ensures that desired version is installed w/o upgrading its dependencies by uninstalling where necessary (if `target` is not provided). @@ -102,6 +106,19 @@ def _install_package(library, instrumentation): _sys_pip_install(instrumentation) +def _install_exporter(name, package): + print('installing exporter:: ', name, package) + return + pip_list = _sys_pip_freeze() + for package in libraries[library]: + if "{}==".format(package).lower() in pip_list: + logger.info( + "Existing %s installation detected. Uninstalling.", package + ) + _sys_pip_uninstall(package) + _sys_pip_install(instrumentation) + + def _syscall(func): def wrapper(package=None): try: @@ -182,18 +199,36 @@ def _is_installed(library): def _find_installed_libraries(): return {k: v for k, v in instrumentations.items() if _is_installed(k)} - -def _run_requirements(packages): +def _run_requirements(instrumentations, exporters): + packages = {} + packages.update(instrumentations) + packages.update(exporters) print("\n".join(packages.values()), end="") -def _run_install(packages): - for pkg, inst in packages.items(): - _install_package(pkg, inst) +def _run_install(instrumentations, exporters): + for pkg, inst in instrumentations.items(): + print('installing:: ', pkg, inst) + _install_instrumentation(pkg, inst) + + for pkg, inst in exporters.items(): + print('installing:: ', pkg, inst) + _install_exporter(pkg, inst) _pip_check() +def _exporter_packages_from_names(exporters): + return { + exp: 'opentelemetry-exporter-{0}>={1}'.format(exp, __version__) + for exp in exporters + } + +def _compile_package_list(exporters): + packages = _find_installed_libraries() + packages.update(_exporter_packages_from_names(exporters)) + return packages + def run() -> None: action_install = "install" action_requirements = "requirements" @@ -216,10 +251,25 @@ def run() -> None: be piped and appended to a requirements.txt file. """, ) + parser.add_argument( + "-e", + "--exporter", + action='append', + choices=symbols.trace_exporters, + help=""" + Installs one or more support telemetry exporters. Supports multiple + values separated by commas. + + Defaults to `otlp`. + """ + ) args = parser.parse_args() cmd = { action_install: _run_install, action_requirements: _run_requirements, }[args.action] - cmd(_find_installed_libraries()) + cmd( + _find_installed_libraries(), + _exporter_packages_from_names(args.exporter or [symbols.exporter_otlp]) + ) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/symbols.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/symbols.py new file mode 100644 index 00000000000..202e790b786 --- /dev/null +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/symbols.py @@ -0,0 +1,13 @@ +exporter_dd = "datadog" +exporter_jaeger = "jaeger" +exporter_oc = "opencensus" +exporter_otlp = "otlp" +exporter_zipkin = "zipkin" + +trace_exporters = ( + exporter_dd, + exporter_jaeger, + exporter_oc, + exporter_otlp, + exporter_zipkin, +) \ No newline at end of file diff --git a/opentelemetry-instrumentation/tests/test_auto_tracing.py b/opentelemetry-instrumentation/tests/test_auto_tracing.py new file mode 100644 index 00000000000..2629826cca7 --- /dev/null +++ b/opentelemetry-instrumentation/tests/test_auto_tracing.py @@ -0,0 +1,129 @@ +# 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. +# type: ignore + +from os import environ, getcwd +from os.path import abspath, dirname, pathsep +from unittest import TestCase +from unittest.mock import patch + +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.configuration import Configuration +from opentelemetry.instrumentation import auto_instrumentation +from opentelemetry.instrumentation.auto_instrumentation import tracing + + + +class TestDefaultAndConfig(TestCase): + + def test_initializers(self): + pass + + def test_providers(self): + pass + + def test_exporters(self): + pass + + def test_processors(self): + pass + + +class TestLoading(TestCase): + def test_import(self): # pylint: disable=no-self-use + with self.assertRaises(ModuleNotFoundError): + tracing._import('non-existent-module') + + imported = tracing._import('opentelemetry.instrumentation.auto_instrumentation.tracing') + self.assertEqual(imported, tracing) + + def test_load(self): # pylint: disable=no-self-use + self.assertIsNone(tracing._load_component({}, 'None')) + self.assertIsNone(tracing._load_component({}, 'component')) + self.assertIsNotNone(tracing._load_component({}, 'opentelemetry.instrumentation')) + mod = tracing._load_component({ + 'that-module': 'opentelemetry.instrumentation.auto_instrumentation' + }, 'that-module') + self.assertEqual(mod, auto_instrumentation) + + +class TestTraceInit(TestCase): + @patch("opentelemetry.trace.set_tracer_provider") + def test_trace_init_default(self, mock_set_provider): + environ['OTEL_SERVICE_NAME'] = 'my-test-service' + Configuration._reset() + tracing._default_trace_init(Exporter, Provider, Processor) + + self.assertEqual(mock_set_provider.call_count, 1) + provider = mock_set_provider.call_args[0][0] + self.assertIsInstance(provider, Provider) + self.assertIsInstance(provider.processor, Processor) + self.assertIsInstance(provider.processor.exporter, Exporter) + self.assertEqual(provider.processor.exporter.service_name, 'my-test-service') + + @patch("opentelemetry.trace.set_tracer_provider") + def test_trace_init_otlp(self, mock_set_provider): + environ['OTEL_SERVICE_NAME'] = 'my-otlp-test-service' + Configuration._reset() + tracing._otlp_trace_init(OTLPExporter, Provider, Processor) + + self.assertEqual(mock_set_provider.call_count, 1) + provider = mock_set_provider.call_args[0][0] + self.assertIsInstance(provider, Provider) + self.assertIsInstance(provider.processor, Processor) + self.assertIsInstance(provider.processor.exporter, OTLPExporter) + self.assertIsInstance(provider.resource, Resource) + self.assertEqual(provider.resource.labels.get('service_name'), 'my-otlp-test-service') + + @patch("opentelemetry.trace.set_tracer_provider") + def test_trace_init_dd(self, mock_set_provider): + environ['OTEL_SERVICE_NAME'] = 'my-dd-test-service' + Configuration._reset() + tracing._dd_trace_init(DDExporter, Provider, Processor) + + self.assertEqual(mock_set_provider.call_count, 1) + provider = mock_set_provider.call_args[0][0] + self.assertIsInstance(provider, Provider) + self.assertIsInstance(provider.processor, Processor) + self.assertIsInstance(provider.processor.exporter, DDExporter) + self.assertEqual(provider.processor.exporter.service, 'my-dd-test-service') + + +class Provider: + def __init__(self, resource=None): + self.processor = None + self.resource = resource + + def add_span_processor(self, processor): + self.processor = processor + + +class Processor: + def __init__(self, exporter): + self.exporter = exporter + + +class Exporter: + def __init__(self, service_name): + self.service_name = service_name + + +class DDExporter: + def __init__(self, service): + self.service = service + + +class OTLPExporter: + pass \ No newline at end of file diff --git a/opentelemetry-instrumentation/tests/test_bootstrap.py b/opentelemetry-instrumentation/tests/test_bootstrap.py index e5a1a86dda5..6562f404a07 100644 --- a/opentelemetry-instrumentation/tests/test_bootstrap.py +++ b/opentelemetry-instrumentation/tests/test_bootstrap.py @@ -19,7 +19,10 @@ from unittest import TestCase from unittest.mock import call, patch -from opentelemetry.instrumentation import bootstrap +from opentelemetry.instrumentation import bootstrap, version + + +default_exporter = 'otlp' def sample_packages(packages, rate): @@ -27,6 +30,13 @@ def sample_packages(packages, rate): return {k: v for k, v in packages.items() if k in sampled} +def packages_from_exporter_names(exporters): + return [ + 'opentelemetry-exporter-{0}>={1}'.format(exp, version.__version__) + for exp in exporters + ] + + class TestBootstrap(TestCase): installed_libraries = {} @@ -44,6 +54,8 @@ def setUpClass(cls): cls.installed_libraries, 0.5 ) + # cls.installed_libraries['opentelemetry-exporter-otlp'] = 'opentelemetry-exporter-otlp>=0.13dev0' + cls.pkg_patcher = patch( "opentelemetry.instrumentation.bootstrap._find_installed_libraries", return_value=cls.installed_libraries, @@ -91,11 +103,19 @@ def test_run_unknown_cmd(self): @patch("sys.argv", ["bootstrap", "-a", "requirements"]) def test_run_cmd_print(self): + self._test_run([default_exporter]) + + @patch("sys.argv", ["bootstrap", "-e", "zipkin", "-e", "jaeger"]) + def test_exporters(self): + self._test_run(['zipkin', 'jaeger']) + + def _test_run(self, exporters): + exporters = packages_from_exporter_names(exporters) with patch("sys.stdout", new=StringIO()) as fake_out: bootstrap.run() self.assertEqual( fake_out.getvalue(), - "\n".join(self.installed_libraries.values()), + "\n".join(list(self.installed_libraries.values()) + exporters), ) @patch("sys.argv", ["bootstrap", "-a", "install"]) diff --git a/opentelemetry-instrumentation/tests/test_run.py b/opentelemetry-instrumentation/tests/test_run.py index 21f53babc6d..532d7ea4391 100644 --- a/opentelemetry-instrumentation/tests/test_run.py +++ b/opentelemetry-instrumentation/tests/test_run.py @@ -46,6 +46,7 @@ def tearDownClass(cls): cls.execl_patcher.stop() cls.which_patcher.stop() + @patch("sys.argv", ["instrument", ""]) @patch.dict("os.environ", {"PYTHONPATH": ""}) def test_empty(self): auto_instrumentation.run() @@ -54,6 +55,7 @@ def test_empty(self): pathsep.join([self.auto_instrumentation_path, getcwd()]), ) + @patch("sys.argv", ["instrument", ""]) @patch.dict("os.environ", {"PYTHONPATH": "abc"}) def test_non_empty(self): auto_instrumentation.run() @@ -62,6 +64,7 @@ def test_non_empty(self): pathsep.join([self.auto_instrumentation_path, getcwd(), "abc"]), ) + @patch("sys.argv", ["instrument", ""]) @patch.dict( "os.environ", {"PYTHONPATH": pathsep.join(["abc", auto_instrumentation_path])}, @@ -73,6 +76,7 @@ def test_after_path(self): pathsep.join([self.auto_instrumentation_path, getcwd(), "abc"]), ) + @patch("sys.argv", ["instrument", ""]) @patch.dict( "os.environ", { @@ -90,10 +94,7 @@ def test_single_path(self): class TestExecl(TestCase): - @patch( - "opentelemetry.instrumentation.auto_instrumentation.argv", - new=[1, 2, 3], - ) + @patch("sys.argv", ["1", "2", "3"]) @patch("opentelemetry.instrumentation.auto_instrumentation.which") @patch("opentelemetry.instrumentation.auto_instrumentation.execl") def test_execl( @@ -103,4 +104,16 @@ def test_execl( auto_instrumentation.run() - mock_execl.assert_called_with("python", "python", 3) + mock_execl.assert_called_with("python", "python", "3") + + +class TestArgs(TestCase): + @patch("opentelemetry.instrumentation.auto_instrumentation.execl") + def test_exporter(self, mock_execl): # pylint: disable=no-self-use + with patch("sys.argv", ["instrument", "2"]): + auto_instrumentation.run() + self.assertEqual(environ.get('OTEL_EXPORTER'), 'otlp') + + with patch("sys.argv", ["instrument", "-e", "zipkin", "1", "2"]): + auto_instrumentation.run() + self.assertEqual(environ.get('OTEL_EXPORTER'), 'zipkin') \ No newline at end of file