# How to add contextual information with Flask plugin?

**URL:** <https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066>\
**Category:** APM\
**Created:** [August 5, 2018, 3:08pm UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066 "2018-08-05T15:08:28Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![fr0der1c](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/fr0der1c/32/34125_2.png) [@fr0der1c](https://discuss.elastic.co/u/fr0der1c)\
**Post date:** [August 5, 2018, 3:08pm UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/1 "2018-08-05T15:08:28Z")

</div>

I started to use Flask APM package today. It worked fine. But when I tried to add contextual information, I got into trouble.

I looked for official doc ([Flask support | APM Python Agent Reference [2.x] | Elastic](https://www.elastic.co/guide/en/apm/agent/python/2.x/flask-support.html)) and got nothing. Then I started looking into `site-packages/elasticapm/contrib/flask/ __init__.py` and found that there isn't any method in `ElasticAPM` class to add contextual information. (The `elasticapm` package does have a series of methods like `set_user_context` though)

Since there is already a plugin for Flask, I don't want to make a wheel again. So I started hacking the package. Here is how I think and what I've done:

The `request_finished` method in `ElasticAPM` class connected to Flask's `request_finished` signal. And when a request is finished, the signal would be triggered. So what I need to do is to add contextual information before `request_finished` method get executed, by using a monkey patch to this method.

Here the original `request_finished` method:

```auto
    def request_finished(self, app, response):
        if not self.app.debug or self.client.config.debug:
            ...[omitted]...
            elasticapm.set_transaction_name(rule, override=False)
            elasticapm.set_transaction_result(result, override=False)
            # Instead of calling end_transaction here, we defer the call until the response is closed.
            # This ensures that we capture things that happen until the WSGI server closes the response.
            response.call_on_close(self.client.end_transaction)

```

Here is my `app/ __init__.py`:

```python
from .utils import monkey_patch
apm = ElasticAPM()
apm.request_finished = monkey_patch.elastic_apm_request_finished(apm.request_finished)

def create_app():
    app = Flask( __name__ )
    app.config.from_object(config)
    apm.init_app(app,
                 service_name=app.config['ELASTIC_APM']['SERVICE_NAME'],
                 secret_token=app.config['ELASTIC_APM']['SECRET_TOKEN'],
                 server_url=app.config['ELASTIC_APM']['SERVER_URL'])
    ...
    return app

```

And my `monkey_patch.py`:

```auto
import functools
import elasticapm
from flask import request

def elastic_apm_request_finished(original_func):
    @functools.wraps(original_func)
    def _patched(self, app, response):
        if not self.app.debug or self.client.config.debug:
            # add user_id in context
            elasticapm.set_user_context(user_id=request.cookies.get('UM_distinctid', None))

        # execute the original `request_finished` function
        original_func(self, app, response)

    return _patched

```

It should work as expected. But unfortunately it didn't. I got

> TypeError: \_patched() missing 1 required positional argument: 'app'

After a quite-deep look into the code, I found the reason for the error.

The `blinker` (which is the package handles the signals) only pass two parameters when invoking the `request_finished`. `app` as positional parameter, and `response` as named parameter. As a method in a class, Python should add parameter `self` automatically. But it is not actually adding this parameter, after I decorated the `request_finished` method. So the actual `app` parameter is taking the position of `self`, and the second second parameter gets nothing!

I'm working with this problem for hours and getting quite frustrated. Does anyone knows where I'm wrong, or is there any better way to add contextual information?

---

<div class="post-metadata">

**Author:** ![fr0der1c](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/fr0der1c/32/34125_2.png) [@fr0der1c](https://discuss.elastic.co/u/fr0der1c)\
**Post date:** [August 5, 2018, 5:09pm UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/2 "2018-08-05T17:09:35Z")

</div>

Okey...I finally got the solution.

The monkey patch should be applied on the method of the class, not the method of the instance of the class. Methods of instance of the class is type `BoundMethod`, which has ` __self__ ` and ` __func__ ` attribute. When you do monkey patch on it, it's replaced with a normal function (you can use `type(instance)` to see the difference).

In short, monkey patch by:

```auto
ElasticAPM.request_finished = monkey_patch.elastic_apm_request_finished(ElasticAPM.request_finished)

```

not

```auto
apm.request_finished = monkey_patch.elastic_apm_request_finished(apm.request_finished)

```

will solve the problem.

Hope that APM team improve Flask plugin so we don't need to use such this geek way to add contextual information in the future.

---

<div class="post-metadata">

**Author:** ![simitt](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/simitt/32/106406_2.png) [@simitt](https://discuss.elastic.co/u/simitt)\
**Post date:** [August 7, 2018, 8:18am UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/3 "2018-08-07T08:18:17Z")

</div>

Thanks for trying out Elastic APM!  
You might want to take a look at the _Public API_ section on how to [set custom context](https://www.elastic.co/guide/en/apm/agent/python/current/api.html#api-set-custom-context) and [add tags](https://www.elastic.co/guide/en/apm/agent/python/current/api.html#api-tag). I hope that helps.

---

<div class="post-metadata">

**Author:** ![fr0der1c](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/fr0der1c/32/34125_2.png) [@fr0der1c](https://discuss.elastic.co/u/fr0der1c)\
**Post date:** [August 7, 2018, 4:11pm UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/4 "2018-08-07T16:11:02Z")

</div>

Thanks for the link.

I actually looked at these docs before hacking into the package. However users are unable to use Public API if they're using the Flask APM plugin. I think the Flask plugin needs to provide a hook to allow user to use Elastic APM Public API.

---

<div class="post-metadata">

**Author:** ![simitt](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/simitt/32/106406_2.png) [@simitt](https://discuss.elastic.co/u/simitt)\
**Post date:** [August 8, 2018, 7:41am UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/5 "2018-08-08T07:41:32Z")

</div>

Here is a little snippet combining Elastic APM in a flask app and adding some custom context:

```auto
import elasticapm
from flask import Flask
from elasticapm.contrib.flask import ElasticAPM

app = Flask( __name__ )

app.config['ELASTIC_APM'] = {
    'DEBUG': True,
    'SERVER_URL': 'http://localhost:8200',
    'SERVICE_NAME': 'test-service',
}
apm = ElasticAPM(app)

@app.route('/')
def index():
    elasticapm.set_custom_context({'my-context': 123})
    return 'OK'

```

Please let me know what you are missing from this.

---

<div class="post-metadata">

**Author:** ![fr0der1c](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/fr0der1c/32/34125_2.png) [@fr0der1c](https://discuss.elastic.co/u/fr0der1c)\
**Post date:** [August 9, 2018, 3:46am UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/6 "2018-08-09T03:46:02Z")

</div>

Setting context in Flask view functions in your way works for one route. However if I want to set a tag or context for all requests (In my case, I read userid from cookies and add to user context), I have no option but to do some hacking. 😅

By the way, what's the difference between tags and custom context? They look pretty alike.

---

<div class="post-metadata">

**Author:** ![beniwohli](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/beniwohli/32/21256_2.png) [@beniwohli](https://discuss.elastic.co/u/beniwohli)\
**Post date:** [August 9, 2018, 9:09am UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/7 "2018-08-09T09:09:25Z")

</div>

@fr0der1c a somewhat less hacky solution could be to use a middleware

```
class UserContextMiddleware(object):
    def __init__ (self, app):
        self.app = app

    def __call__ (self, environ, start_response):
        resp = self.app(environ, start_response)
        elasticapm.set_custom_context({'user-id': 1234})
        return resp

app.wsgi_app = TaggingMiddleware(app.wsgi_app)

```

One drawback with this solution is that `request` isn't available in the middleware, so you'd need to grab the cookie from `environ`.

As for the difference between custom context and tags: the custom context can be any JSON serializable object (e.g. nested data). However, it is not indexed in Elasticsearch, so you can't search/filter/aggregate on it. Tags are flat key/value string pairs, and they are indexed in Elasticsearch.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)\
**Post date:** [August 30, 2018, 5:09am UTC](https://discuss.elastic.co/t/how-to-add-contextual-information-with-flask-plugin/143066/8 "2018-08-30T05:09:30Z")

</div>

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.
