Skip to content

Commit ed1b8eb

Browse files
Merge pull request #1 from carlosalberto/initial_code_import
Initial code import
2 parents b702b1e + 5fdf8cf commit ed1b8eb

9 files changed

Lines changed: 309 additions & 0 deletions

File tree

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include VERSION LICENSE

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.PHONY: test install clean clean-build clean-pyc build
2+
3+
install:
4+
python setup.py install
5+
6+
clean: clean-build clean-pyc
7+
8+
clean-build:
9+
python setup.py clean
10+
rm -fr build/
11+
rm -fr dist/
12+
rm -fr .eggs/
13+
rm -fr .cache/
14+
find . -name '*.egg-info' -exec rm -fr {} +
15+
find . -name '*.egg' -exec rm -rf {} +
16+
17+
clean-pyc:
18+
find . -name '*.pyc' -exec rm -f {} +
19+
find . -name '*.pyo' -exec rm -f {} +
20+
find . -name '*~' -exec rm -f {} +
21+
find . -name '__pycache__' -exec rm -fr {} +
22+
23+
build:
24+
python setup.py build
25+

README.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,51 @@
11
######################
22
SQLAlchemy Opentracing
33
######################
4+
5+
This package enables OpenTracing support for SQLAlchemy.
6+
7+
Installation
8+
============
9+
10+
Run the following command::
11+
12+
$ pip install sqlalchemy_opentracing
13+
14+
Getting started
15+
===============
16+
17+
Please see the examples directory. Overall, basic usage requires that a tracer gets set, an Engine (or Connection) is registered, and statements get their parent spans assigned (if any):
18+
19+
.. code-block:: python
20+
21+
import sqlalchemy_opentracing
22+
23+
sqlalchemy_opentracing.init_tracing(tracer) # A OpenTracing compatible tracer.
24+
sqlalchemy_opentracing.register_connectable(engine) # A valid SQLAlchemy Engine object.
25+
26+
with engine.begin() as conn:
27+
sel = select([users])
28+
sqlalchemy_opentracing.set_traced(sel)
29+
conn.execute(sel)
30+
31+
By default, only statements marked to be traced are taken into account (explicitly through set_traced() or implicitly when registering its parent span through set_parent_span()). Alternatively, you can enable tracing of all queries under the registered Engine/Connection:
32+
33+
.. code-block:: python
34+
35+
sqlalchemy_opentracing.init_tracing(tracer, trace_all=True)
36+
sqlalchemy_opentracing.register_connectable(engine)
37+
38+
# this statement will be traced too (without a parent span, though)
39+
with engine.begin() as conn:
40+
sel = select([users])
41+
conn.execute(sel)
42+
43+
Further information
44+
===================
45+
46+
If you’re interested in learning more about the OpenTracing standard, please visit `opentracing.io`_ or `join the mailing list`_. If you would like to implement OpenTracing in your project and need help, feel free to send us a note at `community@opentracing.io`_.
47+
48+
.. _opentracing.io: http://opentracing.io/
49+
.. _join the mailing list: http://opentracing.us13.list-manage.com/subscribe?u=180afe03860541dae59e84153&id=19117aa6cd
50+
.. _community@opentracing.io: community@opentracing.io
51+

VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.0

examples/child-spans.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import os
2+
from sqlalchemy import MetaData, Table, Integer, String, Column, create_engine
3+
from sqlalchemy import select
4+
from sqlalchemy.schema import CreateTable
5+
6+
import lightstep
7+
import sqlalchemy_opentracing
8+
9+
DB_LOCATION = '/tmp/simple.db'
10+
11+
tracer = lightstep.Tracer(
12+
component_name='sqlalchemy-childspans',
13+
access_token='{your_lightstep_token}'
14+
)
15+
16+
17+
if __name__ == '__main__':
18+
os.remove(DB_LOCATION) # cleanup
19+
20+
engine = create_engine('sqlite:///%s' % DB_LOCATION)
21+
conn = engine.connect()
22+
23+
sqlalchemy_opentracing.init_tracing(tracer)
24+
sqlalchemy_opentracing.register_connectable(engine)
25+
26+
span = tracer.start_span('create sample')
27+
28+
# 1. Create a table
29+
metadata = MetaData()
30+
users = Table('users', metadata,
31+
Column('id', Integer, primary_key=True),
32+
Column('name', String),
33+
)
34+
creat = CreateTable(users)
35+
sqlalchemy_opentracing.set_parent_span(creat, span)
36+
conn.execute(creat)
37+
38+
# 2. Insert a single value.
39+
ins = users.insert().values(name='John Doe', id=1)
40+
sqlalchemy_opentracing.set_parent_span(ins, span)
41+
conn.execute(ins)
42+
43+
# 3. Select the new value.
44+
sel = select([users])
45+
sqlalchemy_opentracing.set_parent_span(sel, span)
46+
print conn.execute(sel).fetchone()
47+
48+
span.finish()
49+
tracer.flush()
50+

examples/simple.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from sqlalchemy import MetaData, Table, Integer, String, Column, create_engine
2+
from sqlalchemy.schema import CreateTable
3+
4+
import lightstep
5+
import sqlalchemy_opentracing
6+
7+
tracer = lightstep.Tracer(
8+
component_name='sqlalchemy-simple',
9+
access_token='{your_lightstep_token}'
10+
)
11+
12+
if __name__ == '__main__':
13+
engine = create_engine('sqlite:///:memory:')
14+
15+
sqlalchemy_opentracing.init_tracing(tracer)
16+
sqlalchemy_opentracing.register_connectable(engine)
17+
18+
metadata = MetaData()
19+
users = Table('users', metadata,
20+
Column('id', Integer, primary_key=True),
21+
Column('name', String),
22+
)
23+
creat = CreateTable(users)
24+
sqlalchemy_opentracing.set_traced(creat)
25+
26+
with engine.begin() as conn:
27+
conn.execute(creat)
28+
29+
tracer.flush()
30+

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[metadata]
2+
description-file = README.rst

setup.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from setuptools import setup
2+
3+
version = open('VERSION').read()
4+
setup(
5+
name='sqlalchemy_opentracing',
6+
version=version,
7+
url='https://github.com/carlosalberto/python-sqlalchemy/',
8+
download_url='https://github.com/carlosalberto/python-sqlalchemy/tarball/'+version,
9+
license='BSD',
10+
author='Carlos Alberto Cortez',
11+
author_email='calberto.cortez@gmail.com',
12+
description='OpenTracing support for SQLAlchemy',
13+
long_description=open('README.rst').read(),
14+
packages=['sqlalchemy_opentracing'],
15+
platforms='any',
16+
install_requires=[
17+
'sqlalchemy',
18+
'opentracing>=1.1,<1.2'
19+
],
20+
classifiers=[
21+
'Intended Audience :: Developers',
22+
'License :: OSI Approved :: BSD License',
23+
'Operating System :: OS Independent',
24+
'Programming Language :: Python',
25+
'Topic :: Software Development :: Libraries :: Python Modules',
26+
]
27+
)

sqlalchemy_opentracing/__init__.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
from sqlalchemy.event import listen, remove
2+
3+
g_tracer = None
4+
g_trace_all = True
5+
6+
def init_tracing(tracer, trace_all=False):
7+
'''
8+
Set our global tracer.
9+
Tracer objects from our pyramid/flask/django libraries
10+
can be passed as well.
11+
'''
12+
global g_tracer, g_trace_all
13+
if hasattr(tracer, '_tracer'):
14+
g_tracer = tracer._tracer
15+
else:
16+
g_tracer = tracer
17+
18+
g_trace_all = trace_all
19+
20+
def get_traced(stmt_obj):
21+
'''
22+
Gets a bool indicating whether or not this
23+
statement is marked for tracing.
24+
'''
25+
return getattr(stmt_obj, '_traced', False)
26+
27+
def set_traced(stmt_obj):
28+
'''
29+
Mark a statement to be traced.
30+
'''
31+
stmt_obj._traced = True
32+
33+
def get_parent_span(stmt_obj):
34+
'''
35+
Gets a parent span for this statement, if any.
36+
'''
37+
return getattr(stmt_obj, '_parent_span', None)
38+
39+
def set_parent_span(stmt_obj, parent_span):
40+
'''
41+
Marks a statement as a child of a span.
42+
It gets marked to be traced if it wasn't before.
43+
'''
44+
stmt_obj._parent_span = parent_span
45+
stmt_obj._traced = True
46+
47+
def has_parent_span(stmt_obj):
48+
'''
49+
Get whether or not the statement has
50+
a parent span.
51+
'''
52+
return hasattr(stmt_obj, '_parent_span')
53+
54+
def get_span(stmt_obj):
55+
'''
56+
Get the span of a statement object, if any.
57+
'''
58+
return getattr(stmt_obj, '_span', None)
59+
60+
def register_connectable(obj):
61+
'''
62+
Register an object to have its events be traced.
63+
Any Connectable object is accepted, which
64+
includes Connection and Engine.
65+
'''
66+
listen(obj, 'before_cursor_execute', _before_cursor_handler)
67+
listen(obj, 'after_cursor_execute', _after_cursor_handler)
68+
listen(obj, 'handle_error', _error_handler)
69+
70+
def unregister_connectable(obj):
71+
'''
72+
Remove a connectable from having its events being
73+
traced.
74+
'''
75+
remove(obj, 'before_cursor_execute', _before_cursor_handler)
76+
remove(obj, 'after_cursor_execute', _after_cursor_handler)
77+
remove(obj, 'handle_error', _error_handler)
78+
79+
def _get_operation_name(stmt_obj):
80+
return stmt_obj.__visit_name__
81+
82+
def _normalize_stmt(statement):
83+
return statement.strip().replace('\n', '').replace('\t', '')
84+
85+
def _before_cursor_handler(conn, cursor, statement, parameters, context, executemany):
86+
if context.compiled is None: # PRAGMA
87+
return
88+
89+
# Don't trace if trace_all is disabled and the statement wasn't marked
90+
stmt_obj = context.compiled.statement
91+
if not (g_trace_all or get_traced(stmt_obj)):
92+
return
93+
94+
parent_span = get_parent_span(stmt_obj)
95+
operation_name = _get_operation_name(stmt_obj)
96+
97+
# Start a new span for this query.
98+
span = g_tracer.start_span(operation_name=operation_name, child_of=parent_span)
99+
span.set_tag('component', 'sqlalchemy')
100+
span.set_tag('db.type', 'sql')
101+
span.set_tag('db.statement', _normalize_stmt(statement))
102+
103+
stmt_obj._span = span
104+
105+
def _after_cursor_handler(conn, cursor, statement, parameters, context, executemany):
106+
if context.compiled is None: # PRAGMA
107+
return
108+
109+
stmt_obj = context.compiled.statement
110+
span = get_span(stmt_obj)
111+
if span is None:
112+
return
113+
114+
span.finish()
115+
116+
def _error_handler(exception_context):
117+
execution_context = exception_context.execution_context
118+
stmt_obj = execution_context.compiled.statement
119+
span = get_span(stmt_obj)
120+
if span is None:
121+
return
122+
123+
span.set_tag('error', 'true')
124+
span.finish()
125+

0 commit comments

Comments
 (0)