Skip to content

Commit 8660dea

Browse files
authored
Add true auto-instrumentation support to opentelemetry-instrument (open-telemetry#1036)
This commit extends the instrument command so it automatically configures tracing with a provider, span processor and exporter. Most of the component used can be customized with env vars or CLI arguments. Details can be found on opentelemetry-instrumentation's README package. Fixes open-telemetry#663
1 parent b5c766a commit 8660dea

File tree

12 files changed

+429
-28
lines changed

12 files changed

+429
-28
lines changed

exporter/opentelemetry-exporter-jaeger/setup.cfg

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ where = src
4848

4949
[options.extras_require]
5050
test =
51+
52+
[options.entry_points]
53+
opentelemetry_exporter =
54+
jaeger = opentelemetry.exporter.jaeger:JaegerSpanExporter

exporter/opentelemetry-exporter-opencensus/setup.cfg

+4
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ where = src
5050

5151
[options.extras_require]
5252
test =
53+
54+
[options.entry_points]
55+
opentelemetry_exporter =
56+
opencensus = opentelemetry.exporter.opencensus.trace_exporter:OpenCensusSpanExporter

exporter/opentelemetry-exporter-otlp/setup.cfg

+5
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,8 @@ test =
5252

5353
[options.packages.find]
5454
where = src
55+
56+
[options.entry_points]
57+
opentelemetry_exporter =
58+
otlp_span = opentelemetry.exporter.otlp.trace_exporter:OTLPSpanExporter
59+
otlp_metric = opentelemetry.exporter.otlp.metrics_exporter:OTLPMetricsExporter

exporter/opentelemetry-exporter-prometheus/setup.cfg

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ where = src
4848

4949
[options.extras_require]
5050
test =
51+
52+
[options.entry_points]
53+
opentelemetry_exporter =
54+
prometheus = opentelemetry.exporter.prometheus:PrometheusMetricsExporter

exporter/opentelemetry-exporter-zipkin/setup.cfg

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ where = src
4848

4949
[options.extras_require]
5050
test =
51+
52+
[options.entry_points]
53+
opentelemetry_exporter =
54+
zipkin = opentelemetry.exporter.zipkin:ZipkinSpanExporter

opentelemetry-instrumentation/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
## Unreleased
44

5+
- Added support for `OTEL_EXPORTER` to the `opentelemetry-instrument` command ([#1036](https://github.com/open-telemetry/opentelemetry-python/pull/1036))
6+
57
## Version 0.14b0
68

79
Released 2020-10-13
810

9-
- Fixed boostrap command to correctly install opentelemetry-instrumentation-falcon instead of opentelemetry-instrumentation-flask
11+
- Fixed bootstrap command to correctly install opentelemetry-instrumentation-falcon instead of opentelemetry-instrumentation-flask. ([#1138](https://github.com/open-telemetry/opentelemetry-python/pull/1138))
1012

1113
## Version 0.13b0
1214

opentelemetry-instrumentation/README.rst

+60-8
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,82 @@ Installation
1616

1717
This package provides a couple of commands that help automatically instruments a program:
1818

19+
20+
opentelemetry-bootstrap
21+
-----------------------
22+
23+
::
24+
25+
opentelemetry-bootstrap --action=install|requirements
26+
27+
This commands inspects the active Python site-packages and figures out which
28+
instrumentation packages the user might want to install. By default it prints out
29+
a list of the suggested instrumentation packages which can be added to a requirements.txt
30+
file. It also supports installing the suggested packages when run with :code:`--action=install`
31+
flag.
32+
33+
1934
opentelemetry-instrument
2035
------------------------
2136

2237
::
2338

2439
opentelemetry-instrument python program.py
2540

41+
The instrument command will try to automatically detect packages used by your python program
42+
and when possible, apply automatic tracing instrumentation on them. This means your program
43+
will get automatic distributed tracing for free without having to make any code changes
44+
at all. This will also configure a global tracer and tracing exporter without you having to
45+
make any code changes. By default, the instrument command will use the OTLP exporter but
46+
this can be overriden when needed.
47+
48+
The command supports the following configuration options as CLI arguments and environment vars:
49+
50+
51+
* ``--exporter`` or ``OTEL_EXPORTER``
52+
53+
Used to specify which trace exporter to use. Can be set to one or more
54+
of the well-known exporter names (see below).
55+
56+
- Defaults to `otlp`.
57+
- Can be set to `none` to disable automatic tracer initialization.
58+
59+
You can pass multiple values to configure multiple exporters e.g, ``zipkin,prometheus``
60+
61+
Well known trace exporter names:
62+
63+
- jaeger
64+
- opencensus
65+
- otlp
66+
- otlp_span
67+
- otlp_metric
68+
- zipkin
69+
70+
``otlp`` is an alias for ``otlp_span,otlp_metric``.
71+
72+
* ``--service-name`` or ``OTEL_SERVICE_NAME``
73+
74+
When present the value is passed on to the relevant exporter initializer as ``service_name`` argument.
75+
2676
The code in ``program.py`` needs to use one of the packages for which there is
2777
an OpenTelemetry integration. For a list of the available integrations please
2878
check `here <https://opentelemetry-python.readthedocs.io/en/stable/index.html#integrations>`_
2979

80+
Examples
81+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3082

31-
opentelemetry-bootstrap
32-
-----------------------
83+
::
84+
85+
opentelemetry-instrument -e otlp flask run --port=3000
86+
87+
The above command will pass ``-e otlp`` to the instrument command and ``--port=3000`` to ``flask run``.
3388

3489
::
3590

36-
opentelemetry-bootstrap --action=install|requirements
91+
opentelemetry-instrument -e zipkin,otlp celery -A tasks worker --loglevel=info
3792

38-
This commands inspects the active Python site-packages and figures out which
39-
instrumentation packages the user might want to install. By default it prints out
40-
a list of the suggested instrumentation packages which can be added to a requirements.txt
41-
file. It also supports installing the suggested packages when run with :code:`--action=install`
42-
flag.
93+
The above command will configure global trace provider, attach zipkin and otlp exporters to it and then
94+
start celery with the rest of the arguments.
4395

4496
References
4597
----------

opentelemetry-instrumentation/src/opentelemetry/instrumentation/auto_instrumentation/__init__.py

+54-4
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,67 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17+
import argparse
1718
from logging import getLogger
1819
from os import environ, execl, getcwd
1920
from os.path import abspath, dirname, pathsep
2021
from shutil import which
21-
from sys import argv
2222

2323
logger = getLogger(__file__)
2424

2525

26+
def parse_args():
27+
parser = argparse.ArgumentParser(
28+
description="""
29+
opentelemetry-instrument automatically instruments a Python
30+
program and it's dependencies and then runs the program.
31+
"""
32+
)
33+
34+
parser.add_argument(
35+
"-e",
36+
"--exporter",
37+
required=False,
38+
help="""
39+
Uses the specified exporter to export spans or metrics.
40+
Accepts multiple exporters as comma separated values.
41+
42+
Examples:
43+
44+
-e=otlp
45+
-e=otlp_span,prometheus
46+
-e=jaeger,otlp_metric
47+
""",
48+
)
49+
50+
parser.add_argument(
51+
"-s",
52+
"--service-name",
53+
required=False,
54+
help="""
55+
The service name that should be passed to a trace exporter.
56+
""",
57+
)
58+
59+
parser.add_argument("command", help="Your Python application.")
60+
parser.add_argument(
61+
"command_args",
62+
help="Arguments for your application.",
63+
nargs=argparse.REMAINDER,
64+
)
65+
return parser.parse_args()
66+
67+
68+
def load_config_from_cli_args(args):
69+
if args.exporter:
70+
environ["OTEL_EXPORTER"] = args.exporter
71+
if args.service_name:
72+
environ["OTEL_SERVICE_NAME"] = args.service_name
73+
74+
2675
def run() -> None:
76+
args = parse_args()
77+
load_config_from_cli_args(args)
2778

2879
python_path = environ.get("PYTHONPATH")
2980

@@ -49,6 +100,5 @@ def run() -> None:
49100

50101
environ["PYTHONPATH"] = pathsep.join(python_path)
51102

52-
executable = which(argv[1])
53-
54-
execl(executable, executable, *argv[2:])
103+
executable = which(args.command)
104+
execl(executable, executable, *args.command_args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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+
from logging import getLogger
16+
from typing import Sequence, Tuple
17+
18+
from pkg_resources import iter_entry_points
19+
20+
from opentelemetry import trace
21+
from opentelemetry.configuration import Configuration
22+
from opentelemetry.sdk.metrics.export import MetricsExporter
23+
from opentelemetry.sdk.resources import Resource
24+
from opentelemetry.sdk.trace import TracerProvider
25+
from opentelemetry.sdk.trace.export import (
26+
BatchExportSpanProcessor,
27+
SpanExporter,
28+
)
29+
30+
logger = getLogger(__file__)
31+
32+
EXPORTER_OTLP = "otlp"
33+
EXPORTER_OTLP_SPAN = "otlp_span"
34+
EXPORTER_OTLP_METRIC = "otlp_metric"
35+
_DEFAULT_EXPORTER = EXPORTER_OTLP
36+
37+
38+
def get_service_name() -> str:
39+
return Configuration().SERVICE_NAME or ""
40+
41+
42+
def get_exporter_names() -> Sequence[str]:
43+
exporter = Configuration().EXPORTER or _DEFAULT_EXPORTER
44+
if exporter.lower().strip() == "none":
45+
return []
46+
47+
names = []
48+
for exp in exporter.split(","):
49+
name = exp.strip()
50+
if name == EXPORTER_OTLP:
51+
names.append(EXPORTER_OTLP_SPAN)
52+
names.append(EXPORTER_OTLP_METRIC)
53+
else:
54+
names.append(name)
55+
return names
56+
57+
58+
def init_tracing(exporters: Sequence[SpanExporter]):
59+
service_name = get_service_name()
60+
provider = TracerProvider(
61+
resource=Resource.create({"service.name": service_name}),
62+
)
63+
trace.set_tracer_provider(provider)
64+
65+
for exporter_name, exporter_class in exporters.items():
66+
exporter_args = {}
67+
if exporter_name not in [
68+
EXPORTER_OTLP,
69+
EXPORTER_OTLP_SPAN,
70+
]:
71+
exporter_args["service_name"] = service_name
72+
73+
provider.add_span_processor(
74+
BatchExportSpanProcessor(exporter_class(**exporter_args))
75+
)
76+
77+
78+
def init_metrics(exporters: Sequence[MetricsExporter]):
79+
if exporters:
80+
logger.warning("automatic metric initialization is not supported yet.")
81+
82+
83+
def import_exporters(
84+
exporter_names: Sequence[str],
85+
) -> Tuple[Sequence[SpanExporter], Sequence[MetricsExporter]]:
86+
trace_exporters, metric_exporters = {}, {}
87+
88+
exporters = {
89+
ep.name: ep for ep in iter_entry_points("opentelemetry_exporter")
90+
}
91+
92+
for exporter_name in exporter_names:
93+
entry_point = exporters.get(exporter_name, None)
94+
if not entry_point:
95+
raise RuntimeError(
96+
"Requested exporter not found: {0}".format(exporter_name)
97+
)
98+
99+
exporter_impl = entry_point.load()
100+
if issubclass(exporter_impl, SpanExporter):
101+
trace_exporters[exporter_name] = exporter_impl
102+
elif issubclass(exporter_impl, MetricsExporter):
103+
metric_exporters[exporter_name] = exporter_impl
104+
else:
105+
raise RuntimeError(
106+
"{0} is neither a trace exporter nor a metric exporter".format(
107+
exporter_name
108+
)
109+
)
110+
return trace_exporters, metric_exporters
111+
112+
113+
def initialize_components():
114+
exporter_names = get_exporter_names()
115+
trace_exporters, metric_exporters = import_exporters(exporter_names)
116+
init_tracing(trace_exporters)
117+
118+
# We don't support automatic initialization for metric yet but have added
119+
# some boilerplate in order to make sure current implementation does not
120+
# lock us out of supporting metrics later without major surgery.
121+
init_metrics(metric_exporters)

0 commit comments

Comments
 (0)