Feature Idea: New Relic with Django Channels (Daphne/ASGI)

Hi,

the Django Channels module uses a new ASGI protocol instead the old WSGI and the new Daphne server which uses ASGI.

I tried to integrate new relic directly in the asgi.py file

import newrelic.agent
newrelic.agent.initialize(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "newrelic.ini"), 'staging')

But it didn’t work.

I tried it inside the Procfile but it didn’t work either.

web: NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program bin/start-pgbouncer-stunnel daphne myapp.asgi:channel_layer --port $PORT --bind 0.0.0.0 -v2
worker: NEW_RELIC_CONFIG_FILE=newrelic.ini newrelic-admin run-program python manage.py runworker -v2

Is new Relic compatible with Django Channels or did I make something wrong?


New Relic edit

  • I want this, too
  • I have more info to share (reply below)
  • I have a solution for this

0 voters

We take feature ideas seriously and our product managers review every one when plotting their roadmaps. However, there is no guarantee this feature will be implemented. This post ensures the idea is put on the table and discussed though. So please vote and share your extra details with our team.

@alican.toprak Django Channels is exciting because if offers a new way to interact with clients. Rather than being limited to the request -> view handler -> response model, Django Channels now gives you the ability to keep open a persistent connection with the client, and to define a consumer function that will respond to messages that arrive over a channel.

Right now, the Python agent doesn’t automatically record data when these consumer functions handle messages, but we will be adding instrumentation for Django Channels in a future release.

In the meantime, you best bet for adding monitoring to consumers is to use the agent API to define transactions. Each time a consumer handles a message, a new “non-web” transaction will be created.

Take the examples from the Django Channels documentation:

# In consumers.py

def http_consumer(message):
    # Make standard HTTP response - access ASGI path attribute directly
    response = HttpResponse("Hello world! You asked for %s" % message.content['path'])
    # Encode that response into message format (ASGI)
    for chunk in AsgiHandler.encode_response(response):
        message.reply_channel.send(chunk)

def ws_message(message):
    # ASGI WebSocket packet-received and send-packet message types
    # both have a "text" key for their textual data.
    message.reply_channel.send({
        "text": message.content['text'],
    })

In order to add instrumentation, you would make modifications like this:

# In consumers.py, with New Relic

import newrelic.agent

@newrelic.agent.background_task()
def http_consumer(message):
    # Make standard HTTP response - access ASGI path attribute directly
    response = HttpResponse("Hello world! You asked for %s" % message.content['path'])
    # Encode that response into message format (ASGI)
    for chunk in AsgiHandler.encode_response(response):
        message.reply_channel.send(chunk)

@newrelic.agent.background_task()
def ws_message(message):
    # ASGI WebSocket packet-received and send-packet message types
    # both have a "text" key for their textual data.
    message.reply_channel.send({
        "text": message.content['text'],
    })

Note that because you will be creating a transaction for each message handled, this means that each long-lived connection can produce many individual transactions.

2 Likes

Any update on this ?
My current procfile looks like this.

web: daphne lamborghini.asgi:channel_layer --port $PORT --bind 0.0.0.0 -v2
worker: python worker.py
channelworker: python manage.py runworker -v2

Can you tell me how can I start using new relic with this ?

@devenbhooshan Thanks for the post. At this time, we have not released the version of the Python agent with out-of-the-box support for Django Channels. Feel free to monitor our release notes for the Python agent to stay up to date.

Until we add out-of-the-box instrumentation in the agent, you can use the @newrelic.agent.background_task() decorator used in the example as a workaround. Have you added the @newrelic.agent.background_task() to monitor a consumer? Can you provide a code snippet showing us how you’ve done so?

1 Like

I am little confused.
where should I point my newrelic.ini file?
And if I am using django views for http routing, how can I add instrumentation for them ?

As django has started supporting channels natively, I really feel we need an out of the box integration of new relic agent with channels to profile requests.

Hi @devenbhooshan Django Channels is on our roadmap, but at this time is not supported out the the box. Hopefully the background_task instrumentation that Tom recommended above will help until we have additional support for it.

To track your interest, I have submitted a feature request on your behalf to our Product Management team. While I don’t have a timeline to offer for when this might be released, if it is in the future, we will notify you.

Thank you.

Thanks :slight_smile:

Any news yet? :slight_smile: I’m also awaiting this feature.

Looks like Channels is not a priority for NR at this point. That’s what I got from my NR account manager last week: “I’m sorry to hear that! I spoke with the Technical Support Engineer who helped you, and it looks like that specific feature won’t be available for a while.”

We are paying for APM (with yearly commitment) but using it with this background task workaround makes APM really poor in functionalities.

Hi! I’ve created a patch for New Relic APM agent that works for most data I need.

The patched is based on agent version 2.86.3.70 and was tested with Daphne 1.3.0 and Channels 1.1.6.

Patch is unofficial and it’s available here: https://gist.github.com/seocam/0751464c2a17f6038646da07e8508dad

Are there any updates on this?

Hi, have you found a way to make NR work with channels, by chance?

I still using my patch with NR agent version 2.86.3.70 but I’m wondering if the new version 4.2.0.100 recently released wouldn’t work with Channels and Channels 2.0 since it’s made for distributed tracing.

What am I supposed to do with this? Where do I place it?

Hi Folks!

While New Relic doesn’t currently support ASGI servers such as Daphne, I’ve put together a solution using the agent API and was able to get data reporting for synchronous HTTP applications in Django that are run with Daphne. Here’s the example asgi.py file I’ve tested, your mileage may vary :slight_smile:

Please keep in mind that this is not supported and thus should be tested in a staging / testing environment first.

import newrelic.agent
newrelic.agent.initialize()

def convert_to_web_transaction(*args, **kwargs):
    newrelic.agent.set_background_task(False)

import django.core.handlers.base as handlers
newrelic.agent.wrap_pre_function(handlers, 'BaseHandler.get_response', convert_to_web_transaction)
newrelic.agent.wrap_background_task(handlers, 'BaseHandler.get_response')

from channels.routing import ProtocolTypeRouter

application = ProtocolTypeRouter({
})
2 Likes

This works for me, thank you :slight_smile:

1 Like

This works for me also… Keeping it on staging for now…

Hoping the NewRelic team will be able to find time to have this officially supported.

Hey @reloaded, Python Agent 4.16.0.116 added a new web_transaction API can be used to instrument non-WSGI web transactions.

For usage of this API check out https://docs.newrelic.com/docs/agents/python-agent/python-agent-api/webtransaction :slight_smile:

You can integrate channels v2 with new relic like this:

  1. Add to you manage.py/asgi.py or any other app starting point this code in first lines:
    import newrelic.agent
    newrelic.agent.initialize()
  1. In routing https://channels.readthedocs.io/en/latest/topics/routing.html :
    import channels.http
    import newrelic.agent

    class AsgiHandlerWithNewrelic(channels.http.AsgiHandler):
        def get_response(self, request):
            headers = None
            if "headers" in request.scope and isinstance(request.scope["headers"], dict):
                headers = request.scope['headers']

            # https://docs.newrelic.com/docs/agents/python-agent/python-agent-api/webtransaction
            # instance of channels/handler.py `AsgiRequest`
            get_response_custom = newrelic.agent.WebTransactionWrapper(
                super().get_response,
                scheme=request.scheme,
                host=request.get_host(),
                port=request.get_port(),
                request_method=request.method,
                request_path=request.path,
                query_string=request.META.get('QUERY_STRING'),
                headers=headers
            )

            return get_response_custom(request)


    application = ProtocolTypeRouter({
        ...
        "http": AsgiHandlerWithNewrelic
    })
1 Like