Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Django gunicorn auto instrumentation #2038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
anirudhbagri opened this issue Aug 13, 2021 · 8 comments
Open

Django gunicorn auto instrumentation #2038

anirudhbagri opened this issue Aug 13, 2021 · 8 comments

Comments

@anirudhbagri
Copy link

Currently, Django autoinstrumentation supports flask and uWSGI servers.
However, when running django app using gunicorn server, the autoinstrumentation does not work.

This is the command I am trying to run.

opentelemetry-instrument --trace-exporter zipkin_json python3.8 gunicorn ${DJANGO_WSGI_MODULE}:application --name ${NAME} --bind=127.0.0.1:80 --preload
The app starts fine, but doesn't report any traces to zipkin.

@owais
Copy link
Contributor

owais commented Aug 17, 2021

It is not supported by the opentelemetry-instrument command. Docs mention it and explain how to instrument gunicorn based services: https://opentelemetry-python.readthedocs.io/en/latest/examples/fork-process-model/README.html?highlight=gunicorn#working-with-fork-process-models

@owais
Copy link
Contributor

owais commented Aug 17, 2021

I'd like to leave this open in the hope of adding native support for gunicorn to the opentelemetry-instrument command.

@anirudhbagri
Copy link
Author

anirudhbagri commented Aug 17, 2021

AFAIK, even the post_fork hook doesn't work with auto-instrumentation using the way it is explained in the docs. The doc explains manual way of instrumenting. Probably we can update the docs to explain how to auto-instrument guincorn based services as well.

we need to add the following to initialize auto-instrumentation

from opentelemetry.instrumentation.auto_instrumentation import sitecustomize in the post_fork method.

@srikanthccv
Copy link
Member

I remember using Gunicorn with auto instrumentation and see it emit traces while debugging some issue.

@doogle-oss
Copy link

Any update on this making to the next version.

@yimipeng
Copy link

Does open-telemetry support manual instrumentation for Gunicorn then?

@lzpfmh
Copy link

lzpfmh commented Jul 23, 2024

At the end there are some solutions for reference open-telemetry/opentelemetry-python-contrib#2086

@devmonkey22
Copy link

I have found it very painful to even manually instrument a gunicorn+Django web server. Several chicken/egg
ordering problems. If I start gunicorn via gunicorn -c gunicorn.conf.py app.wsgi and (at least in my case, I
also use gunicorn's preload_app, it has the following flow:

  1. Loads the gunicorn.conf.py config file with preload_app = True and post_fork callback defined.

  2. The app.wsgi module is imported

  3. That preloads the django.core.wsgi.get_wsgi_application into the pre-fork process.

  4. The django.core.handlers.wsgi.WSGIHandler.__init__ loads the middleware from settings.MIDDLEWARE.

  5. Gunicorn forks each child worker and calls post_fork.

  6. Per opentelemetry docs, we're now able to initialize the tracer, auto-instrument, etc.

    However, the guidance there never talks about how to properly call WSGI's OpenTelemetryMiddleware(application).

    The guidance in WSGI's instrumentation for Django just says to call application = OpenTelemetryMiddleware(application) to wrap the WSGI app with the "middleware" in the app/wsgi.py file. But per this flow, that's loaded in step 2 (pre-fork) before we can initialize telemetry in step 5 and 6.

    If we allowed OpenTelemetryMiddleware(application) in step 2 to happen, it internally initializes its tracer and meter pre-fork, thus grabbing a proxy tracer and meter instead of the real objects we created in step 6. It also more than likely runs into the deadlock scenario that required the post_fork anyway.

I was able to work around part of this flow issue if I wanted to use WSGI's OpenTelemetryMiddleware only:

# gunicorn.conf.py file

def post_fork(server, worker):
    """
    After the gunicorn worker forks, initialize telemetry.

    See https://opentelemetry-python.readthedocs.io/en/latest/examples/fork-process-model/README.html#working-with-fork-process-models etc
    """
    from opentelemetry.instrumentation import auto_instrumentation
    from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
    import app.wsgi

    # Manually call auto-instrumentation
    auto_instrumentation.initialize()

    # Wrap telemetry around the WSGI application/handler.
    # Need to wait until after telemetry initialized, otherwise the internal tracer/meter may be a proxy instance or wrong.
    # That's why I didn't do this in the `app.wsgi` directly (mostly when `preload_app` is True.  I didn't test without preload in my app.)
    app.wsgi.application = OpenTelemetryMiddleware(app.wsgi.application)
	
    # Assuming`preload_app` is True, manually override worker's app WSGI callable that already grabbed `app.wsgi.application` without the OTel middleware with our updated reference
    worker.app.callable = oc.wsgi.application

As a side-note, there is no discussion about the usage of the WSGI instrumentation vs Django instrumentation. Looking at their implementations, seems like we have to use one or the other, not both. To compare:

  • They both track request durations and active request metrics.
  • Only Django instrumentation supports the specific Django traced request attributes, excluded URLs configuration, etc. It seems to be more detailed in its data collection including writing http.route, etc.
  • The Django instrumentation does make certain calls into the WSGI instrumentation modules, so that's a hint that its supposed to be used instead.

However, what we haven't been able to do yet is call DjangoInstrumentor.instrument during post_fork and in a way that actually allows the OTel Django Middleware to be properly inserted into the middleware chain that's held in the WSGI application (WSGIHandler).

When preload_app is True and the WSGI application is created pre-fork and the telemetry hasn't been instrumented, the middleware setting hasn't been modified yet. The django.core.handlers.wsgi.WSGIHandler created by the app.wsgi module calls django.core.handlers.base.BaseHandler.load_middleware before instrumentation. Then in post_fork, we instrument Django and it modifies the middleware setting. But the underlying app.wsgi.application (WSGIHandler) still has the old set of middleware loaded.

So to re-initialize the middleware, I did something like the following:

# gunicorn.conf.py file

def post_fork(server, worker):
    """
    After the gunicorn worker forks, initialize telemetry.

    See https://opentelemetry-python.readthedocs.io/en/latest/examples/fork-process-model/README.html#working-with-fork-process-models etc
    """
    from opentelemetry.instrumentation import auto_instrumentation
    import app.wsgi

    auto_instrumentation.initialize()
	
    # Reload middleware (post fork) since Django instrumentation should have updated middleware list
    app.wsgi.application.load_middleware()

Your mileage may vary, but that seemed to do the trick for me. I wish it was easier to auto-instrument without all these manual steps, but the mix of gunicorn, Django, and OpenTelemetry instrumentation approaches seem to not mix great for that without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants