Skip to content

Commit 3c2f759

Browse files
authored
New psycopg2 instrumentation (#167)
* First run psycopg2 instrumentation & tests * Finished psycopg2 instrumentation & tests * Update default postgres creds for CircleCI * Remove custom pg creds * Try alt db name for CircleCI * Add register_uuid protections * Change hook point * When tracing sqlalchemy, pass through on pep 249
1 parent ae1744e commit 3c2f759

File tree

8 files changed

+282
-26
lines changed

8 files changed

+282
-26
lines changed

instana/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def boot_agent():
7373
from .instrumentation.tornado import server
7474
from .instrumentation import logging
7575
from .instrumentation import pymysql
76+
from .instrumentation import psycopg2
7677
from .instrumentation import redis
7778
from .instrumentation import sqlalchemy
7879
from .instrumentation import sudsjurko

instana/instrumentation/pep0249.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ def __init__(self, cursor, module_name,
2020
def _collect_kvs(self, span, sql):
2121
try:
2222
span.set_tag(ext.SPAN_KIND, 'exit')
23-
span.set_tag(ext.DATABASE_INSTANCE, self._connect_params[1]['db'])
23+
24+
if 'db' in self._connect_params[1]:
25+
span.set_tag(ext.DATABASE_INSTANCE, self._connect_params[1]['db'])
26+
elif 'database' in self._connect_params[1]:
27+
span.set_tag(ext.DATABASE_INSTANCE, self._connect_params[1]['database'])
28+
2429
span.set_tag(ext.DATABASE_STATEMENT, sql_sanitizer(sql))
25-
span.set_tag(ext.DATABASE_TYPE, 'mysql')
30+
# span.set_tag(ext.DATABASE_TYPE, 'mysql')
2631
span.set_tag(ext.DATABASE_USER, self._connect_params[1]['user'])
2732
span.set_tag('host', "%s:%s" %
2833
(self._connect_params[1]['host'],
@@ -35,8 +40,8 @@ def _collect_kvs(self, span, sql):
3540
def execute(self, sql, params=None):
3641
parent_span = tracer.active_span
3742

38-
# If we're not tracing, just return
39-
if parent_span is None:
43+
# If not tracing or we're being called from sqlalchemy, just pass through
44+
if (parent_span is None) or (parent_span.operation_name == "sqlalchemy"):
4045
return self.__wrapped__.execute(sql, params)
4146

4247
with tracer.start_active_span(self._module_name, child_of=parent_span) as scope:
@@ -54,8 +59,8 @@ def execute(self, sql, params=None):
5459
def executemany(self, sql, seq_of_parameters):
5560
parent_span = tracer.active_span
5661

57-
# If we're not tracing, just return
58-
if parent_span is None:
62+
# If not tracing or we're being called from sqlalchemy, just pass through
63+
if (parent_span is None) or (parent_span.operation_name == "sqlalchemy"):
5964
return self.__wrapped__.executemany(sql, seq_of_parameters)
6065

6166
with tracer.start_active_span(self._module_name, child_of=parent_span) as scope:
@@ -73,8 +78,8 @@ def executemany(self, sql, seq_of_parameters):
7378
def callproc(self, proc_name, params):
7479
parent_span = tracer.active_span
7580

76-
# If we're not tracing, just return
77-
if parent_span is None:
81+
# If not tracing or we're being called from sqlalchemy, just pass through
82+
if (parent_span is None) or (parent_span.operation_name == "sqlalchemy"):
7883
return self.__wrapped__.execute(proc_name, params)
7984

8085
with tracer.start_active_span(self._module_name, child_of=parent_span) as scope:
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from __future__ import absolute_import
2+
3+
import copy
4+
import wrapt
5+
6+
from ..log import logger
7+
from .pep0249 import ConnectionFactory
8+
9+
try:
10+
import psycopg2
11+
import psycopg2.extras
12+
13+
cf = ConnectionFactory(connect_func=psycopg2.connect, module_name='postgres')
14+
15+
setattr(psycopg2, 'connect', cf)
16+
if hasattr(psycopg2, 'Connect'):
17+
setattr(psycopg2, 'Connect', cf)
18+
19+
@wrapt.patch_function_wrapper('psycopg2.extensions', 'register_type')
20+
def register_type_with_instana(wrapped, instance, args, kwargs):
21+
args_clone = list(copy.copy(args))
22+
23+
if hasattr(args_clone[1], '__wrapped__'):
24+
args_clone[1] = args_clone[1].__wrapped__
25+
26+
return wrapped(*args_clone, **kwargs)
27+
28+
logger.debug("Instrumenting psycopg2")
29+
except ImportError:
30+
pass

instana/json_span.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Data(BaseSpan):
3636
custom = None
3737
http = None
3838
log = None
39+
pg = None
3940
rabbitmq = None
4041
redis = None
4142
rpc = None
@@ -74,6 +75,15 @@ class MySQLData(BaseSpan):
7475
error = None
7576

7677

78+
class PostgresData(BaseSpan):
79+
db = None
80+
host = None
81+
port = None
82+
user = None
83+
stmt = None
84+
error = None
85+
86+
7787
class RabbitmqData(BaseSpan):
7888
exchange = None
7989
queue = None

instana/recorder.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import instana.singletons
1111

12-
from .json_span import (CustomData, Data, HttpData, JsonSpan, LogData, MySQLData,
12+
from .json_span import (CustomData, Data, HttpData, JsonSpan, LogData, MySQLData, PostgresData,
1313
RabbitmqData, RedisData, RenderData, RPCData, SDKData, SoapData,
1414
SQLAlchemyData)
1515
from .log import logger
@@ -24,12 +24,12 @@
2424
class InstanaRecorder(SpanRecorder):
2525
THREAD_NAME = "Instana Span Reporting"
2626
registered_spans = ("aiohttp-client", "aiohttp-server", "django", "log", "memcache", "mysql",
27-
"rabbitmq", "redis", "render", "rpc-client", "rpc-server", "sqlalchemy", "soap",
27+
"postgres", "rabbitmq", "redis", "render", "rpc-client", "rpc-server", "sqlalchemy", "soap",
2828
"tornado-client", "tornado-server", "urllib3", "wsgi")
2929
http_spans = ("aiohttp-client", "aiohttp-server", "django", "http", "soap", "tornado-client",
3030
"tornado-server", "urllib3", "wsgi")
3131

32-
exit_spans = ("aiohttp-client", "log", "memcache", "mysql", "rabbitmq", "redis", "rpc-client",
32+
exit_spans = ("aiohttp-client", "log", "memcache", "mysql", "postgres", "rabbitmq", "redis", "rpc-client",
3333
"sqlalchemy", "soap", "tornado-client", "urllib3")
3434
entry_spans = ("aiohttp-server", "django", "wsgi", "rabbitmq", "rpc-server", "tornado-server")
3535
local_spans = ("log", "render")
@@ -202,6 +202,16 @@ def build_registered_span(self, span):
202202
tskey = list(data.custom.logs.keys())[0]
203203
data.mysql.error = data.custom.logs[tskey]['message']
204204

205+
if span.operation_name == "postgres":
206+
data.pg = PostgresData(host=span.tags.pop('host', None),
207+
db=span.tags.pop(ext.DATABASE_INSTANCE, None),
208+
user=span.tags.pop(ext.DATABASE_USER, None),
209+
stmt=span.tags.pop(ext.DATABASE_STATEMENT, None),
210+
error=span.tags.pop('pg.error', None))
211+
if (data.custom is not None) and (data.custom.logs is not None) and len(data.custom.logs):
212+
tskey = list(data.custom.logs.keys())[0]
213+
data.pg.error = data.custom.logs[tskey]['message']
214+
205215
if span.operation_name == "log":
206216
data.log = {}
207217
# use last special key values

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ def check_setuptools():
7676
'mock>=2.0.0',
7777
'mysqlclient>=1.3.14;python_version>="3.5"',
7878
'MySQL-python>=1.2.5;python_version<="2.7"',
79-
'psycopg2>=2.7.1',
8079
'PyMySQL[rsa]>=0.9.1',
8180
'pyOpenSSL>=16.1.0;python_version<="2.7"',
8281
'pytest>=3.0.1',
82+
'psycopg2>=2.7.1',
8383
'redis<3.0.0',
8484
'requests>=2.17.1',
8585
'sqlalchemy>=1.1.15',

tests/helpers.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,12 @@
2121
"""
2222
PostgreSQL Environment
2323
"""
24-
if 'POSTGRESQL_HOST' in os.environ:
25-
testenv['postgresql_host']= os.environ['POSTGRESQL_HOST']
26-
elif 'TRAVIS_POSTGRESQL_HOST' in os.environ:
27-
testenv['postgresql_host'] = os.environ['TRAVIS_POSTGRESQL_HOST']
28-
else:
29-
testenv['postgresql_host'] = '127.0.0.1'
30-
31-
testenv['postgresql_port'] = int(os.environ.get('POSTGRESQL_PORT', '3306'))
24+
testenv['postgresql_host'] = os.environ.get('POSTGRESQL_HOST', '127.0.0.1')
25+
testenv['postgresql_port'] = int(os.environ.get('POSTGRESQL_PORT', '5432'))
3226
testenv['postgresql_db'] = os.environ.get('POSTGRESQL_DB', 'circle_test')
3327
testenv['postgresql_user'] = os.environ.get('POSTGRESQL_USER', 'root')
28+
testenv['postgresql_pw'] = os.environ.get('POSTGRESQL_PW', '')
3429

35-
if 'POSTGRESQL_PW' in os.environ:
36-
testenv['postgresql_pw'] = os.environ['POSTGRESQL_PW']
37-
elif 'TRAVIS_POSTGRESQL_PASS' in os.environ:
38-
testenv['postgresql_pw'] = os.environ['TRAVIS_POSTGRESQL_PASS']
39-
else:
40-
testenv['postgresql_pw'] = ''
4130

4231
"""
4332
Redis Environment

0 commit comments

Comments
 (0)