newrelic-python-agent icon indicating copy to clipboard operation
newrelic-python-agent copied to clipboard

psycopg3 support

Open alexeyshockov opened this issue 2 years ago • 21 comments

Hey, thanks for the great monitoring integration! psycong3 was released in October 2021, do you have any plans to support it? Would be really nice to have a hook for this lib, as it brings a lot of useful features.

alexeyshockov avatar Jul 20 '22 12:07 alexeyshockov

Hi @alexeyshockov! I'll start by saying that I am not an expert of psycopg3, or New Relic, or even Python, but we came across this problem today and also this issue that you opened.

Perhaps you've solved this already, but, if not, here's how I addressed it.

For context, startup.py is the entry point for our web server.

startup.py

import psycopg
from newrelic_custom_instrumentation import instrument_psycopg3_async_cursor
instrument_psycopg3_async_cursor(psycopg)

# ... loads of other unrelated stuff

newrelic_custom_instrumentation.py

from newrelic.api.database_trace import (
    DatabaseTrace,
    enable_datastore_instance_feature,
    register_database_client,
)
from newrelic.common.object_wrapper import wrap_function_wrapper


# TODO – I'm not certain we're getting everything we want out of this – I based it off database_asyncpg
class PostgresApi(object):
    @staticmethod
    def _instance_info(addr, connected_fut, con_params, *args, **kwargs):
        if isinstance(addr, str):
            host = "localhost"
            port = addr
        else:
            host, port = addr

        return host, str(port), getattr(con_params, "database", None)

    @classmethod
    def instance_info(cls, args, kwargs):
        return cls._instance_info(*args, **kwargs)


register_database_client(
    PostgresApi,
    "Postgres",
    quoting_style="single+dollar",
    instance_info=PostgresApi.instance_info,
)
enable_datastore_instance_feature(PostgresApi)


# TODO – Similarly, there may be more things we can pass to DatabaseTrace
async def wrap_execute(wrapped, instance, args, kwargs):
    query, params = args
    with DatabaseTrace(
            query,
            database_name=instance.connection.info.dbname,
            dbapi2_module=PostgresApi,
            host=instance.connection.info.host,
            port_path_or_id=instance.connection.info.port,
            source=instance.execute,
            sql_parameters=params,
    ):
        return await wrapped(*args, **kwargs)


def instrument_psycopg3_async_cursor(module):
    wrap_function_wrapper(module, "AsyncCursor.execute", wrap_execute)

As you see in my TODO comments above, I'm sure that we're missing out on more capabilities. We also have a limited use of psycopg3, hence only needing to instrument AsyncCursor.execute. Nevertheless, hopefully this helps.

gushecht avatar Sep 13 '22 22:09 gushecht

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Nov 12 '22 23:11 stale[bot]

Hey @gushecht, thanks a lot for the snippet! Currently I put this on hold in my app, in favour of "good old" psycopg2. But I'm really looking forward to hear any updates from New Relic team here...

alexeyshockov avatar Nov 14 '22 09:11 alexeyshockov

We have reviewed this ticket with the team and have created a Feature Request to get this through in our backlog. cc: @ak-war

Ak-x avatar Nov 16 '22 18:11 Ak-x