Skip to content

Commit

Permalink
Add autoinstrumentation prototype for Flask
Browse files Browse the repository at this point in the history
  • Loading branch information
ocelotl committed Mar 17, 2020
1 parent d870abf commit 26af7a9
Show file tree
Hide file tree
Showing 26 changed files with 874 additions and 81 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
source_dirs = [
os.path.abspath("../opentelemetry-api/src/"),
os.path.abspath("../opentelemetry-sdk/src/"),
os.path.abspath("../opentelemetry-auto-instrumentation/src/"),
]

ext = "../ext"
Expand Down
4 changes: 3 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ install <https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs>

getting-started


.. toctree::
:maxdepth: 1
:caption: OpenTelemetry Python Packages
Expand All @@ -83,6 +82,9 @@ install <https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs>
:caption: Examples
:name: examples
:glob:
:caption: OpenTelemetry Auto Instrumentation:

opentelemetry.auto_instrumentation.patcher

examples/**

Expand Down
8 changes: 8 additions & 0 deletions docs/opentelemetry.auto_instrumentation.patcher.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
opentelemetry.auto_instrumentation.patcher package
==================================================


Module contents
---------------

.. automodule:: opentelemetry.auto_instrumentation.patcher
119 changes: 119 additions & 0 deletions examples/auto_instrumentation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Overview

This example shows how to use auto-instrumentation in OpenTelemetry. This example is also based on a previous example
for OpenTracing that can be found [here](https://github.com/yurishkuro/opentracing-tutorial/tree/master/python).

This example uses 2 scripts whose main difference is they being instrumented manually or not:

1. `publisher_instrumented.py` which has been instrumented manually
2. `publisher_uninstrumented.py` which has not been instrumented manually

The former will be run without the automatic instrumentation agent and the latter with the automatic instrumentation
agent. They should produce an equal result, showing that the automatic instrumentation agent does the equivalent
of what manual instrumentation does.

In order to understand this better, here is the relevant part of both scripts:

## Publisher instrumented manually

`publisher_instrumented.py`

```python
@app.route("/publish_request")
def publish_request():

with tracer.start_as_current_span(
"publish_request", propagators.extract(get_as_list, request.headers)
):
hello_str = request.args.get("helloStr")
print(hello_str)
return "published"
```

## Publisher not instrumented manually

`publisher_uninstrumented.py`

```python
@app.route("/publish_request")
def publish_request():
hello_str = request.args.get("helloStr")
print(hello_str)
return "published"
```

# Preparation

This example will be executed in a separate virtual environment:

```sh
$ mkdir auto_instrumentation
$ virtualenv auto_instrumentation
$ source auto_instrumentation/bin/activate
```

# Installation

```sh
$ git clone [email protected]:open-telemetry/opentelemetry-python.git
$ cd opentelemetry-python
$ pip3 install -e opentelemetry-api
$ pip3 install -e opentelemetry-sdk
$ pip3 install -e opentelemetry-auto-instrumentation
$ pip3 install -e ext/opentelemetry-ext-flask
$ pip3 install flask
$ pip3 install requests
```

# Execution

## Execution of the manually instrumented publisher

This is done in 3 separate consoles, one to run each of the scripts that make up this example:

```sh
$ source auto_instrumentation/bin/activate
$ python3 opentelemetry-python/examples/auto_instrumentation/formatter.py
```

```sh
$ source auto_instrumentation/bin/activate
$ python3 opentelemetry-python/examples/auto_instrumentation/publisher_instrumented.py
```

```sh
$ source auto_instrumentation/bin/activate
$ python3 opentelemetry-python/examples/auto_instrumentation/hello.py testing
```

The execution of `publisher_instrumented.py` should return an output similar to:

```sh
Hello, testing!
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)
127.0.0.1 - - [18/Dec/2019 19:11:12] "GET /publish?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
```

## Execution of an automatically instrumented publisher

Now, kill the execution of `publisher_instrumented.py` with `ctrl + c` and run this instead:

```sh
$ opentelemetry-auto-instrumentation opentelemetry-python/examples/auto_instrumentation/publisher_uninstrumented.py
```

In the console where you previously executed `hello.py`, run again this again:

```sh
$ python3 opentelemetry-python/examples/auto_instrumentation/hello.py testing
```

The execution of `publisher_uninstrumented.py` should return an output similar to:

```sh
Hello, testing!
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)
127.0.0.1 - - [18/Dec/2019 19:11:12] "GET /publish?helloStr=Hello%2C+testing%21 HTTP/1.1" 200 -
```

As you can see, both outputs are equal since the automatic instrumentation does what the manual instrumentation does too.
52 changes: 52 additions & 0 deletions examples/auto_instrumentation/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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 flask import Flask, request

from opentelemetry import propagators, trace
from opentelemetry.context.propagation.tracecontexthttptextformat import (
TraceContextHTTPTextFormat,
)
from opentelemetry.propagators import set_global_httptextformat
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
)
from utils import get_as_list

app = Flask(__name__)

trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.tracer_provider().get_tracer(__name__)

trace.tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)
set_global_httptextformat(TraceContextHTTPTextFormat)


@app.route("/format_request")
def format_request():

with tracer.start_as_current_span(
"format_request",
parent=propagators.extract(get_as_list, request.headers),
):
hello_to = request.args.get("helloTo")
return "Hello, %s!" % hello_to


if __name__ == "__main__":
app.run(port=8081)
67 changes: 67 additions & 0 deletions examples/auto_instrumentation/hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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 sys import argv

from flask import Flask
from requests import get

from opentelemetry import propagators, trace
from opentelemetry.context.propagation.tracecontexthttptextformat import (
TraceContextHTTPTextFormat,
)
from opentelemetry.propagators import set_global_httptextformat
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
)

app = Flask(__name__)

trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.tracer_provider().get_tracer(__name__)

trace.tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)
set_global_httptextformat(TraceContextHTTPTextFormat)


def http_get(port, path, param, value):

headers = {}
propagators.inject(tracer, dict.__setitem__, headers)

requested = get(
"http://localhost:{}/{}".format(port, path),
params={param: value},
headers=headers,
)

assert requested.status_code == 200
return requested.text


assert len(argv) == 2

hello_to = argv[1]

with tracer.start_as_current_span("hello") as hello_span:

with tracer.start_as_current_span("hello-format", parent=hello_span):
hello_str = http_get(8081, "format_request", "helloTo", hello_to)

with tracer.start_as_current_span("hello-publish", parent=hello_span):
http_get(8082, "publish_request", "helloStr", hello_str)
52 changes: 52 additions & 0 deletions examples/auto_instrumentation/publisher_instrumented.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 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 flask import Flask, request

from opentelemetry import propagators, trace
from opentelemetry.context.propagation.tracecontexthttptextformat import (
TraceContextHTTPTextFormat,
)
from opentelemetry.propagators import set_global_httptextformat
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
)
from utils import get_as_list

app = Flask(__name__)

trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())
tracer = trace.tracer_provider().get_tracer(__name__)

trace.tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)
set_global_httptextformat(TraceContextHTTPTextFormat)


@app.route("/publish_request")
def publish_request():

with tracer.start_as_current_span(
"publish_request", propagators.extract(get_as_list, request.headers)
):
hello_str = request.args.get("helloStr")
print(hello_str)
return "published"


if __name__ == "__main__":
app.run(port=8082)
41 changes: 41 additions & 0 deletions examples/auto_instrumentation/publisher_uninstrumented.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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 flask import Flask, request

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
ConsoleSpanExporter,
SimpleExportSpanProcessor,
)

app = Flask(__name__)

trace.set_preferred_tracer_provider_implementation(lambda T: TracerProvider())

trace.tracer_provider().add_span_processor(
SimpleExportSpanProcessor(ConsoleSpanExporter())
)


@app.route("/publish_request")
def publish_request():
hello_str = request.args.get("helloStr")
print(hello_str)
return "published"


if __name__ == "__main__":
app.run(port=8082)
21 changes: 21 additions & 0 deletions examples/auto_instrumentation/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.


def get_as_list(dict_object, key):
value = dict_object.get(key)
return value if value is not None else []


__all__ = ["get_as_list"]
Loading

0 comments on commit 26af7a9

Please sign in to comment.