Skip to content

ISSUE-80: Add patch support for psycopg2 #83

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

Merged
merged 10 commits into from
Aug 31, 2018
1 change: 1 addition & 0 deletions aws_xray_sdk/core/patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'mysql',
'httplib',
'pymongo',
'psycopg2'
)

_PATCHED_MODULES = set()
Expand Down
4 changes: 4 additions & 0 deletions aws_xray_sdk/ext/psycopg2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .patch import patch


__all__ = ['patch']
26 changes: 26 additions & 0 deletions aws_xray_sdk/ext/psycopg2/patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import re
import wrapt

from aws_xray_sdk.ext.dbapi2 import XRayTracedConn


def patch():

wrapt.wrap_function_wrapper(
'psycopg2',
'connect',
_xray_traced_connect
)


def _xray_traced_connect(wrapped, instance, args, kwargs):

conn = wrapped(*args, **kwargs)
meta = {
'database_type': 'postgresql',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@haotianw465, how do I see the custom metadata in XRay? I system tested this code by deploying it along with one of my Lambda functions; I did see the correct database_type, however I did not see database_host, database_name, or database_user in the Raw Trace Data in the AWS XRay Console.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service will drop unknown keys. Here is a sample sql document the service support.

"sql" : {
     "url": "jdbc:postgresql://aawijb5u25wdoy.cpamxznpdoq8.us-west-2.rds.amazonaws.com:5432/ebdb",
     "preparation": "statement",
     "database_type": "PostgreSQL",
     "database_version": "9.5.4",
     "driver_version": "PostgreSQL 9.4.1211.jre7",
     "user" : "dbuser"
}

'database_host': kwargs['host'] if 'host' in kwargs else re.search(r'host=(\S+)\b', args[0]).groups()[0],
'database_name': kwargs['dbname'] if 'dbname' in kwargs else re.search(r'dbname=(\S+)\b', args[0]).groups()[0],
'database_user': kwargs['user'] if 'user' in kwargs else re.search(r'user=(\S+)\b', args[0]).groups()[0],
}

return XRayTracedConn(conn, meta)
Empty file.
117 changes: 117 additions & 0 deletions tests/ext/psycopg2/test_psycopg2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import psycopg2
import psycopg2.pool

import pytest
import testing.postgresql

from aws_xray_sdk.core import patch
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core.context import Context

patch(('psycopg2',))


@pytest.fixture(autouse=True)
def construct_ctx():
"""
Clean up context storage on each test run and begin a segment
so that later subsegment can be attached. After each test run
it cleans up context storage again.
"""
xray_recorder.configure(service='test', sampling=False, context=Context())
xray_recorder.clear_trace_entities()
xray_recorder.begin_segment('name')
yield
xray_recorder.clear_trace_entities()


def test_execute_dsn_kwargs():
q = 'SELECT 1'
with testing.postgresql.Postgresql() as postgresql:
dsn = postgresql.dsn()
conn = psycopg2.connect(dbname=dsn['database'],
user=dsn['user'],
password='',
host=dsn['host'],
port=dsn['port'])
cur = conn.cursor()
cur.execute(q)

subsegment = xray_recorder.current_segment().subsegments[0]
assert subsegment.name == 'execute'
sql = subsegment.sql
assert sql['database_type'] == 'postgresql'
assert sql['database_host'] == dsn['host']
assert sql['database_name'] == dsn['database']
assert sql['database_user'] == dsn['user']


def test_execute_dsn_string():
q = 'SELECT 1'
with testing.postgresql.Postgresql() as postgresql:
dsn = postgresql.dsn()
conn = psycopg2.connect('dbname=' + dsn['database'] +
' password=mypassword' +
' host=' + dsn['host'] +
' port=' + str(dsn['port']) +
' user=' + dsn['user'])
cur = conn.cursor()
cur.execute(q)

subsegment = xray_recorder.current_segment().subsegments[0]
assert subsegment.name == 'execute'
sql = subsegment.sql
assert sql['database_type'] == 'postgresql'
assert sql['database_host'] == dsn['host']
assert sql['database_name'] == dsn['database']
assert sql['database_user'] == dsn['user']



def test_execute_in_pool():
q = 'SELECT 1'
with testing.postgresql.Postgresql() as postgresql:
dsn = postgresql.dsn()
pool = psycopg2.pool.SimpleConnectionPool(1, 1,
dbname=dsn['database'],
user=dsn['user'],
password='',
host=dsn['host'],
port=dsn['port'])
cur = pool.getconn(key=dsn['user']).cursor()
cur.execute(q)

subsegment = xray_recorder.current_segment().subsegments[0]
assert subsegment.name == 'execute'
sql = subsegment.sql
assert sql['database_type'] == 'postgresql'
assert sql['database_host'] == dsn['host']
assert sql['database_name'] == dsn['database']
assert sql['database_user'] == dsn['user']


def test_execute_bad_query():
q = 'SELECT blarg'
with testing.postgresql.Postgresql() as postgresql:
dsn = postgresql.dsn()
conn = psycopg2.connect(dbname=dsn['database'],
user=dsn['user'],
password='',
host=dsn['host'],
port=dsn['port'])
cur = conn.cursor()
try:
cur.execute(q)
except Exception:
pass

subsegment = xray_recorder.current_segment().subsegments[0]
assert subsegment.name == 'execute'
sql = subsegment.sql
assert sql['database_type'] == 'postgresql'
assert sql['database_host'] == dsn['host']
assert sql['database_name'] == dsn['database']
assert sql['database_user'] == dsn['user']

exception = subsegment.cause['exceptions'][0]
assert exception.type == 'ProgrammingError'
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ deps =
# the sdk doesn't support earlier version of django
django >= 1.10, <2.0
pynamodb
psycopg2
testing.postgresql
# Python3.5+ only deps
py{35,36}: aiohttp >= 3.0.0
py{35,36}: pytest-aiohttp
Expand Down