Skip to content

Commit fb9cb43

Browse files
committed
Add autoinstrumentation prototype for Flask
Fixes open-telemetry#300
1 parent d870abf commit fb9cb43

File tree

26 files changed

+875
-79
lines changed

26 files changed

+875
-79
lines changed

Diff for: 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"

Diff for: docs/index.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ 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
@@ -83,6 +82,9 @@ install <https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs>
8382
:caption: Examples
8483
:name: examples
8584
:glob:
85+
:caption: OpenTelemetry Auto Instrumentation:
86+
87+
opentelemetry.auto_instrumentation.patcher
8688

8789
examples/**
8890

Diff for: docs/opentelemetry.auto_instrumentation.patcher.rst

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
opentelemetry.auto_instrumentation.patcher package
2+
==================================================
3+
4+
5+
Module contents
6+
---------------
7+
8+
.. automodule:: opentelemetry.auto_instrumentation.patcher

Diff for: examples/auto_instrumentation/README.md

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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. `publisher_instrumented.py` which has been instrumented manually
9+
2. `publisher_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 an equal 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+
## Publisher instrumented manually
18+
19+
`publisher_instrumented.py`
20+
21+
```python
22+
@app.route("/publish_request")
23+
def publish_request():
24+
25+
with tracer.start_as_current_span(
26+
"publish_request", propagators.extract(get_as_list, request.headers)
27+
):
28+
hello_str = request.args.get("helloStr")
29+
print(hello_str)
30+
return "published"
31+
```
32+
33+
## Publisher not instrumented manually
34+
35+
`publisher_uninstrumented.py`
36+
37+
```python
38+
@app.route("/publish_request")
39+
def publish_request():
40+
hello_str = request.args.get("helloStr")
41+
print(hello_str)
42+
return "published"
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+
$ git clone [email protected]:open-telemetry/opentelemetry-python.git
59+
$ cd opentelemetry-python
60+
$ pip3 install -e opentelemetry-api
61+
$ pip3 install -e opentelemetry-sdk
62+
$ pip3 install -e opentelemetry-auto-instrumentation
63+
$ pip3 install -e ext/opentelemetry-ext-flask
64+
$ pip3 install flask
65+
$ pip3 install requests
66+
```
67+
68+
# Execution
69+
70+
## Execution of the manually instrumented publisher
71+
72+
This is done in 3 separate consoles, one to run each of the scripts that make up this example:
73+
74+
```sh
75+
$ source auto_instrumentation/bin/activate
76+
$ python3 opentelemetry-python/examples/auto_instrumentation/formatter.py
77+
```
78+
79+
```sh
80+
$ source auto_instrumentation/bin/activate
81+
$ python3 opentelemetry-python/examples/auto_instrumentation/publisher_instrumented.py
82+
```
83+
84+
```sh
85+
$ source auto_instrumentation/bin/activate
86+
$ python3 opentelemetry-python/examples/auto_instrumentation/hello.py testing
87+
```
88+
89+
The execution of `publisher_instrumented.py` should return an output similar to:
90+
91+
```sh
92+
Hello, testing!
93+
Span(name="publish", context=SpanContext(trace_id=0xd18be4c644d3be57a8623bbdbdbcef76, span_id=0x6162c475bab8d365, trace_state={}), kind=SpanKind.SERVER, parent=SpanContext(trace_id=0xd18be4c644d3be57a8623bbdbdbcef76, span_id=0xdafb264c5b1b6ed0, trace_state={}), start_time=2019-12-19T01:11:12.172866Z, end_time=2019-12-19T01:11:12.173383Z)
94+
127.0.0.1 - - [18/Dec/2019 19:11:12] "GET /publish?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
95+
```
96+
97+
## Execution of an automatically instrumented publisher
98+
99+
Now, kill the execution of `publisher_instrumented.py` with `ctrl + c` and run this instead:
100+
101+
```sh
102+
$ opentelemetry-auto-instrumentation opentelemetry-python/examples/auto_instrumentation/publisher_uninstrumented.py
103+
```
104+
105+
In the console where you previously executed `hello.py`, run again this again:
106+
107+
```sh
108+
$ python3 opentelemetry-python/examples/auto_instrumentation/hello.py testing
109+
```
110+
111+
The execution of `publisher_uninstrumented.py` should return an output similar to:
112+
113+
```sh
114+
Hello, testing!
115+
Span(name="publish", context=SpanContext(trace_id=0xd18be4c644d3be57a8623bbdbdbcef76, span_id=0x6162c475bab8d365, trace_state={}), kind=SpanKind.SERVER, parent=SpanContext(trace_id=0xd18be4c644d3be57a8623bbdbdbcef76, span_id=0xdafb264c5b1b6ed0, trace_state={}), start_time=2019-12-19T01:11:12.172866Z, end_time=2019-12-19T01:11:12.173383Z)
116+
127.0.0.1 - - [18/Dec/2019 19:11:12] "GET /publish?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
117+
```
118+
119+
As you can see, both outputs are equal since the automatic instrumentation does what the manual instrumentation does too.

Diff for: examples/auto_instrumentation/formatter.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2020, 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.context.propagation.tracecontexthttptextformat import (
19+
TraceContextHTTPTextFormat,
20+
)
21+
from opentelemetry.propagators import set_global_httptextformat
22+
from opentelemetry.sdk.trace import TracerProvider
23+
from opentelemetry.sdk.trace.export import (
24+
ConsoleSpanExporter,
25+
SimpleExportSpanProcessor,
26+
)
27+
from utils import get_as_list
28+
29+
app = Flask(__name__)
30+
31+
trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
32+
tracer = trace.tracer_provider().get_tracer(__name__)
33+
34+
trace.tracer_provider().add_span_processor(
35+
SimpleExportSpanProcessor(ConsoleSpanExporter())
36+
)
37+
set_global_httptextformat(TraceContextHTTPTextFormat)
38+
39+
40+
@app.route("/format_request")
41+
def format_request():
42+
43+
with tracer.start_as_current_span(
44+
"format_request",
45+
parent=propagators.extract(get_as_list, request.headers),
46+
):
47+
hello_to = request.args.get("helloTo")
48+
return "Hello, %s!" % hello_to
49+
50+
51+
if __name__ == "__main__":
52+
app.run(port=8081)

Diff for: examples/auto_instrumentation/hello.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2020, 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.context.propagation.tracecontexthttptextformat import (
22+
TraceContextHTTPTextFormat,
23+
)
24+
from opentelemetry.propagators import set_global_httptextformat
25+
from opentelemetry.sdk.trace import TracerProvider
26+
from opentelemetry.sdk.trace.export import (
27+
ConsoleSpanExporter,
28+
SimpleExportSpanProcessor,
29+
)
30+
31+
app = Flask(__name__)
32+
33+
trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
34+
tracer = trace.tracer_provider().get_tracer(__name__)
35+
36+
trace.tracer_provider().add_span_processor(
37+
SimpleExportSpanProcessor(ConsoleSpanExporter())
38+
)
39+
set_global_httptextformat(TraceContextHTTPTextFormat)
40+
41+
42+
def http_get(port, path, param, value):
43+
44+
headers = {}
45+
propagators.inject(tracer, dict.__setitem__, headers)
46+
47+
requested = get(
48+
"http://localhost:{}/{}".format(port, path),
49+
params={param: value},
50+
headers=headers,
51+
)
52+
53+
assert requested.status_code == 200
54+
return requested.text
55+
56+
57+
assert len(argv) == 2
58+
59+
hello_to = argv[1]
60+
61+
with tracer.start_as_current_span("hello") as hello_span:
62+
63+
with tracer.start_as_current_span("hello-format", parent=hello_span):
64+
hello_str = http_get(8081, "format_request", "helloTo", hello_to)
65+
66+
with tracer.start_as_current_span("hello-publish", parent=hello_span):
67+
http_get(8082, "publish_request", "helloStr", hello_str)
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2020, 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.context.propagation.tracecontexthttptextformat import (
19+
TraceContextHTTPTextFormat,
20+
)
21+
from opentelemetry.propagators import set_global_httptextformat
22+
from opentelemetry.sdk.trace import TracerProvider
23+
from opentelemetry.sdk.trace.export import (
24+
ConsoleSpanExporter,
25+
SimpleExportSpanProcessor,
26+
)
27+
from utils import get_as_list
28+
29+
app = Flask(__name__)
30+
31+
trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
32+
tracer = trace.tracer_provider().get_tracer(__name__)
33+
34+
trace.tracer_provider().add_span_processor(
35+
SimpleExportSpanProcessor(ConsoleSpanExporter())
36+
)
37+
set_global_httptextformat(TraceContextHTTPTextFormat)
38+
39+
40+
@app.route("/publish_request")
41+
def publish_request():
42+
43+
with tracer.start_as_current_span(
44+
"publish_request", propagators.extract(get_as_list, request.headers)
45+
):
46+
hello_str = request.args.get("helloStr")
47+
print(hello_str)
48+
return "published"
49+
50+
51+
if __name__ == "__main__":
52+
app.run(port=8082)
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2020, 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_preferred_tracer_provider_implementation(lambda T: TracerProvider())
27+
28+
trace.tracer_provider().add_span_processor(
29+
SimpleExportSpanProcessor(ConsoleSpanExporter())
30+
)
31+
32+
33+
@app.route("/publish_request")
34+
def publish_request():
35+
hello_str = request.args.get("helloStr")
36+
print(hello_str)
37+
return "published"
38+
39+
40+
if __name__ == "__main__":
41+
app.run(port=8082)

Diff for: examples/auto_instrumentation/utils.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright 2020, 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+
16+
def get_as_list(dict_object, key):
17+
value = dict_object.get(key)
18+
return value if value is not None else []
19+
20+
21+
__all__ = ["get_as_list"]

0 commit comments

Comments
 (0)