Skip to content

Commit a137bc2

Browse files
ocelotlmauriciovasquezbernalc24t
authored
Add an autoinstrumentation mechanism and an instrumentor for Flask (#327)
Adding an autoinstrumentation mechanism and a Flask instrumentor (an instrumentor is a class that implements the _instrument and _uninstrument methods). It works like this: A console command is defined. This makes it possible to run a command named opentelemetry-auto-instrumentation that will execute this function. When the opentelemetry-auto-instrumentation command is executed, then the instrument method of the different instrumentors is called, which are made available via an entry-point. 2.In the case of the Flask instrumentor, the original flask.Flask gets replaced with _InstrumentedFlask (in this case, the Flask instrumentor uses monkey patching to perform the instrumentation, nevertheless, monkey patching is not always the method used to do this, so the name instrumentor is preferred over patcher). Once all instrumentation is enabled, the app is executed. Co-Authored-By: Mauricio Vásquez <[email protected]> Co-authored-by: Chris Kleinknecht <[email protected]>
1 parent 7b1d866 commit a137bc2

File tree

25 files changed

+701
-82
lines changed

25 files changed

+701
-82
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OpenTelemetry Python Autoinstrumentation
2+
========================================
3+
4+
.. toctree::
5+
:maxdepth: 1
6+
7+
instrumentor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
opentelemetry.auto_instrumentation.instrumentor package
2+
=======================================================
3+
4+
.. automodule:: opentelemetry.auto_instrumentation.instrumentor
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

docs/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
source_dirs = [
1919
os.path.abspath("../opentelemetry-api/src/"),
2020
os.path.abspath("../opentelemetry-sdk/src/"),
21+
os.path.abspath("../opentelemetry-auto-instrumentation/src/"),
2122
]
2223

2324
ext = "../ext"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Overview
2+
3+
This example shows how to use auto-instrumentation in OpenTelemetry. This example is also based on a previous example
4+
for OpenTracing that can be found [here](https://github.com/yurishkuro/opentracing-tutorial/tree/master/python).
5+
6+
This example uses 2 scripts whose main difference is they being instrumented manually or not:
7+
8+
1. `server_instrumented.py` which has been instrumented manually
9+
2. `server_uninstrumented.py` which has not been instrumented manually
10+
11+
The former will be run without the automatic instrumentation agent and the latter with the automatic instrumentation
12+
agent. They should produce the same result, showing that the automatic instrumentation agent does the equivalent
13+
of what manual instrumentation does.
14+
15+
In order to understand this better, here is the relevant part of both scripts:
16+
17+
## Manually instrumented server
18+
19+
`server_instrumented.py`
20+
21+
```python
22+
@app.route("/server_request")
23+
def server_request():
24+
with tracer.start_as_current_span(
25+
"server_request",
26+
parent=propagators.extract(
27+
lambda dict_, key: dict_.get(key, []), request.headers
28+
)["current-span"],
29+
):
30+
print(request.args.get("param"))
31+
return "served"
32+
```
33+
34+
## Publisher not instrumented manually
35+
36+
`server_uninstrumented.py`
37+
38+
```python
39+
@app.route("/server_request")
40+
def server_request():
41+
print(request.args.get("param"))
42+
return "served"
43+
```
44+
45+
# Preparation
46+
47+
This example will be executed in a separate virtual environment:
48+
49+
```sh
50+
$ mkdir auto_instrumentation
51+
$ virtualenv auto_instrumentation
52+
$ source auto_instrumentation/bin/activate
53+
```
54+
55+
# Installation
56+
57+
```sh
58+
$ pip install opentelemetry-api
59+
$ pip install opentelemetry-sdk
60+
$ pip install opentelemetry-auto-instrumentation
61+
$ pip install ext/opentelemetry-ext-flask
62+
$ pip install flask
63+
$ pip install requests
64+
```
65+
66+
# Execution
67+
68+
## Execution of the manually instrumented server
69+
70+
This is done in 2 separate consoles, one to run each of the scripts that make up this example:
71+
72+
```sh
73+
$ source auto_instrumentation/bin/activate
74+
$ python opentelemetry-python/opentelemetry-auto-instrumentation/example/server_instrumented.py
75+
```
76+
77+
```sh
78+
$ source auto_instrumentation/bin/activate
79+
$ python opentelemetry-python/opentelemetry-auto-instrumentation/example/client.py testing
80+
```
81+
82+
The execution of `server_instrumented.py` should return an output similar to:
83+
84+
```sh
85+
Hello, testing!
86+
Span(name="serv_request", context=SpanContext(trace_id=0x9c0e0ce8f7b7dbb51d1d6e744a4dad49, span_id=0xd1ba3ec4c76a0d7f, trace_state={}), kind=SpanKind.INTERNAL, parent=None, start_time=2020-03-19T00:06:31.275719Z, end_time=2020-03-19T00:06:31.275920Z)
87+
127.0.0.1 - - [18/Mar/2020 18:06:31] "GET /serv_request?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
88+
```
89+
90+
## Execution of an automatically instrumented server
91+
92+
Now, kill the execution of `server_instrumented.py` with `ctrl + c` and run this instead:
93+
94+
```sh
95+
$ opentelemetry-auto-instrumentation opentelemetry-python/opentelemetry-auto-instrumentation/example/server_uninstrumented.py
96+
```
97+
98+
In the console where you previously executed `client.py`, run again this again:
99+
100+
```sh
101+
$ python opentelemetry-python/opentelemetry-auto-instrumentation/example/client.py testing
102+
```
103+
104+
The execution of `server_uninstrumented.py` should return an output similar to:
105+
106+
```sh
107+
Hello, testing!
108+
Span(name="serv_request", context=SpanContext(trace_id=0xf26b28b5243e48f5f96bfc753f95f3f0, span_id=0xbeb179a095d087ed, trace_state={}), kind=SpanKind.SERVER, parent=<opentelemetry.trace.DefaultSpan object at 0x7f1a20a54908>, start_time=2020-03-19T00:24:18.828561Z, end_time=2020-03-19T00:24:18.845127Z)
109+
127.0.0.1 - - [18/Mar/2020 18:24:18] "GET /serv_request?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
110+
```
111+
112+
As you can see, both outputs are equivalentsince the automatic instrumentation does what the manual instrumentation does too.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 sys import argv
16+
17+
from flask import Flask
18+
from requests import get
19+
20+
from opentelemetry import propagators, trace
21+
from opentelemetry.sdk.trace import TracerProvider
22+
from opentelemetry.sdk.trace.export import (
23+
ConsoleSpanExporter,
24+
SimpleExportSpanProcessor,
25+
)
26+
27+
app = Flask(__name__)
28+
29+
trace.set_tracer_provider(TracerProvider())
30+
tracer = trace.get_tracer_provider().get_tracer(__name__)
31+
32+
trace.get_tracer_provider().add_span_processor(
33+
SimpleExportSpanProcessor(ConsoleSpanExporter())
34+
)
35+
36+
37+
assert len(argv) == 2
38+
39+
with tracer.start_as_current_span("client"):
40+
41+
with tracer.start_as_current_span("client-server"):
42+
headers = {}
43+
propagators.inject(dict.__setitem__, headers)
44+
requested = get(
45+
"http://localhost:8082/server_request",
46+
params={"param": argv[1]},
47+
headers=headers,
48+
)
49+
50+
assert requested.status_code == 200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 flask import Flask, request
16+
17+
from opentelemetry import propagators, trace
18+
from opentelemetry.sdk.trace import TracerProvider
19+
from opentelemetry.sdk.trace.export import (
20+
ConsoleSpanExporter,
21+
SimpleExportSpanProcessor,
22+
)
23+
24+
app = Flask(__name__)
25+
26+
trace.set_tracer_provider(TracerProvider())
27+
tracer = trace.get_tracer_provider().get_tracer(__name__)
28+
29+
trace.get_tracer_provider().add_span_processor(
30+
SimpleExportSpanProcessor(ConsoleSpanExporter())
31+
)
32+
33+
34+
@app.route("/server_request")
35+
def server_request():
36+
with tracer.start_as_current_span(
37+
"server_request",
38+
parent=propagators.extract(
39+
lambda dict_, key: dict_.get(key, []), request.headers
40+
)["current-span"],
41+
):
42+
print(request.args.get("param"))
43+
return "served"
44+
45+
46+
if __name__ == "__main__":
47+
app.run(port=8082)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 flask import Flask, request
16+
17+
from opentelemetry import trace
18+
from opentelemetry.sdk.trace import TracerProvider
19+
from opentelemetry.sdk.trace.export import (
20+
ConsoleSpanExporter,
21+
SimpleExportSpanProcessor,
22+
)
23+
24+
app = Flask(__name__)
25+
26+
trace.set_tracer_provider(TracerProvider())
27+
28+
trace.get_tracer_provider().add_span_processor(
29+
SimpleExportSpanProcessor(ConsoleSpanExporter())
30+
)
31+
32+
33+
@app.route("/server_request")
34+
def server_request():
35+
print(request.args.get("param"))
36+
return "served"
37+
38+
39+
if __name__ == "__main__":
40+
app.run(port=8082)

docs/examples/opentelemetry-example-app/src/opentelemetry_example_app/flask_example.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import opentelemetry.ext.http_requests
2323
from opentelemetry import trace
24-
from opentelemetry.ext.flask import instrument_app
24+
from opentelemetry.ext.flask import FlaskInstrumentor
2525
from opentelemetry.sdk.trace import TracerProvider
2626
from opentelemetry.sdk.trace.export import (
2727
ConsoleSpanExporter,
@@ -33,9 +33,9 @@
3333
SimpleExportSpanProcessor(ConsoleSpanExporter())
3434
)
3535

36+
FlaskInstrumentor().instrument()
3637
app = flask.Flask(__name__)
3738
opentelemetry.ext.http_requests.enable(trace.get_tracer_provider())
38-
instrument_app(app)
3939

4040

4141
@app.route("/")

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ install <https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs>
6161

6262
getting-started
6363

64-
6564
.. toctree::
6665
:maxdepth: 1
6766
:caption: OpenTelemetry Python Packages
6867
:name: packages
6968

7069
api/api
7170
sdk/sdk
71+
auto_instrumentation/auto_instrumentation
7272

7373
.. toctree::
7474
:maxdepth: 2

ext/opentelemetry-ext-flask/setup.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@
2323
with open(VERSION_FILENAME) as f:
2424
exec(f.read(), PACKAGE_INFO)
2525

26-
setuptools.setup(version=PACKAGE_INFO["__version__"])
26+
setuptools.setup(
27+
version=PACKAGE_INFO["__version__"],
28+
entry_points={
29+
"opentelemetry_instrumentor": [
30+
"flask = opentelemetry.ext.flask:FlaskInstrumentor"
31+
]
32+
},
33+
)

0 commit comments

Comments
 (0)