Skip to content

Commit a51fe7d

Browse files
tjzoreyang
authored andcommitted
Integrate with pymongo (census-instrumentation#543)
* Integrate with pymongo * merge * Move pymongo to contrib/ * Resolve conflict in README * Fix install requirements * Fix test * Fix code style and other comments * Add pymongo to nox.py
1 parent 7a73fb6 commit a51fe7d

File tree

13 files changed

+320
-1
lines changed

13 files changed

+320
-1
lines changed

README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ OpenCensus supports integration with popular web frameworks, client libraries an
254254
- `httplib`_
255255
- `MySQL`_
256256
- `PostgreSQL`_
257+
- `pymongo`_
257258
- `PyMySQL`_
258259
- `Pyramid`_
259260
- `requests`_
@@ -267,6 +268,7 @@ OpenCensus supports integration with popular web frameworks, client libraries an
267268
.. _httplib: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-httplib
268269
.. _MySQL: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-mysql
269270
.. _PostgreSQL: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-postgresql
271+
.. _pymongo: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-pymongo
270272
.. _PyMySQL: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-pymysql
271273
.. _Pyramid: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-pyramid
272274
.. _requests: https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-requests
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Initial version.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
OpenCensus pymongo Integration
2+
============================================================================
3+
4+
The integration with MongoDB supports the `pymongo`_ library and is specified
5+
to ``trace_integrations`` using ``'pymongo'``.
6+
7+
.. _pymongo: https://pypi.org/project/pymongo
8+
9+
Installation
10+
------------
11+
12+
::
13+
14+
pip install opencensus-ext-pymongo
15+
16+
Usage
17+
-----
18+
19+
.. code:: python
20+
21+
# TBD
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.ext.pymongo import trace
16+
17+
__all__ = ['trace']
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
from pymongo import monitoring
18+
19+
from opencensus.trace import execution_context
20+
from opencensus.trace import span as span_module
21+
22+
23+
log = logging.getLogger(__name__)
24+
25+
MODULE_NAME = 'pymongo'
26+
27+
COMMAND_ATTRIBUTES = ['filter', 'sort', 'skip', 'limit']
28+
29+
30+
def trace_integration(tracer=None):
31+
"""Integrate with pymongo to trace it using event listener."""
32+
log.info('Integrated module: {}'.format(MODULE_NAME))
33+
monitoring.register(MongoCommandListener(tracer=tracer))
34+
35+
36+
class MongoCommandListener(monitoring.CommandListener):
37+
38+
def __init__(self, tracer=None):
39+
self._tracer = tracer
40+
41+
@property
42+
def tracer(self):
43+
return self._tracer or execution_context.get_opencensus_tracer()
44+
45+
def started(self, event):
46+
span = self.tracer.start_span(
47+
name='{}.{}.{}.{}'.format(MODULE_NAME,
48+
event.database_name,
49+
event.command.get(event.command_name),
50+
event.command_name))
51+
span.span_kind = span_module.SpanKind.CLIENT
52+
53+
for attr in COMMAND_ATTRIBUTES:
54+
_attr = event.command.get(attr, default=None)
55+
if _attr is not None:
56+
self.tracer.add_attribute_to_current_span(attr, str(_attr))
57+
58+
self.tracer.add_attribute_to_current_span(
59+
'request_id', event.request_id)
60+
61+
self.tracer.add_attribute_to_current_span(
62+
'connection_id', str(event.connection_id))
63+
64+
def succeeded(self, event):
65+
self._stop('succeeded')
66+
67+
def failed(self, event):
68+
self._stop('failed')
69+
70+
def _stop(self, status):
71+
self.tracer.add_attribute_to_current_span('status', status)
72+
73+
self.tracer.end_span()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal = 1
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from setuptools import find_packages
16+
from setuptools import setup
17+
from version import __version__
18+
19+
setup(
20+
name='opencensus-ext-pymongo',
21+
version=__version__, # noqa
22+
author='OpenCensus Authors',
23+
author_email='census-developers@googlegroups.com',
24+
classifiers=[
25+
'Intended Audience :: Developers',
26+
'Development Status :: 3 - Alpha',
27+
'Intended Audience :: Developers',
28+
'License :: OSI Approved :: Apache Software License',
29+
'Programming Language :: Python',
30+
'Programming Language :: Python :: 2',
31+
'Programming Language :: Python :: 2.7',
32+
'Programming Language :: Python :: 3',
33+
'Programming Language :: Python :: 3.4',
34+
'Programming Language :: Python :: 3.5',
35+
'Programming Language :: Python :: 3.6',
36+
'Programming Language :: Python :: 3.7',
37+
],
38+
description='OpenCensus pymongo Integration',
39+
include_package_data=True,
40+
long_description=open('README.rst').read(),
41+
install_requires=[
42+
'opencensus >= 0.2.dev0, < 1.0.0',
43+
'pymongo >= 3.1.0',
44+
],
45+
extras_require={},
46+
license='Apache-2.0',
47+
packages=find_packages(exclude=('tests',)),
48+
namespace_packages=[],
49+
url='https://github.com/census-instrumentation/opencensus-python/tree/master/contrib/opencensus-ext-pymongo',
50+
zip_safe=False,
51+
)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
import mock
18+
19+
from opencensus.ext.pymongo import trace
20+
21+
22+
class Test_pymongo_trace(unittest.TestCase):
23+
24+
def test_trace_integration(self):
25+
mock_register = mock.Mock()
26+
27+
patch = mock.patch(
28+
'pymongo.monitoring.register',
29+
side_effect=mock_register)
30+
31+
with patch:
32+
trace.trace_integration()
33+
34+
self.assertTrue(mock_register.called)
35+
36+
def test_started(self):
37+
mock_tracer = MockTracer()
38+
39+
patch = mock.patch(
40+
'opencensus.trace.execution_context.get_opencensus_tracer',
41+
return_value=mock_tracer)
42+
43+
command_attrs = {
44+
'filter': 'filter',
45+
'sort': 'sort',
46+
'limit': 'limit',
47+
'command_name': 'find'
48+
}
49+
50+
expected_attrs = {
51+
'filter': 'filter',
52+
'sort': 'sort',
53+
'limit': 'limit',
54+
'request_id': 'request_id',
55+
'connection_id': 'connection_id'
56+
}
57+
58+
expected_name = 'pymongo.database_name.find.command_name'
59+
60+
with patch:
61+
trace.MongoCommandListener().started(
62+
event=MockEvent(command_attrs))
63+
64+
self.assertEqual(mock_tracer.current_span.attributes, expected_attrs)
65+
self.assertEqual(mock_tracer.current_span.name, expected_name)
66+
67+
def test_succeed(self):
68+
mock_tracer = MockTracer()
69+
mock_tracer.start_span()
70+
71+
patch = mock.patch(
72+
'opencensus.trace.execution_context.get_opencensus_tracer',
73+
return_value=mock_tracer)
74+
75+
expected_attrs = {'status': 'succeeded'}
76+
77+
with patch:
78+
trace.MongoCommandListener().succeeded(event=MockEvent(None))
79+
80+
self.assertEqual(mock_tracer.current_span.attributes, expected_attrs)
81+
mock_tracer.end_span.assert_called_with()
82+
83+
def test_failed(self):
84+
mock_tracer = MockTracer()
85+
mock_tracer.start_span()
86+
87+
patch = mock.patch(
88+
'opencensus.trace.execution_context.get_opencensus_tracer',
89+
return_value=mock_tracer)
90+
91+
expected_attrs = {'status': 'failed'}
92+
93+
with patch:
94+
trace.MongoCommandListener().failed(event=MockEvent(None))
95+
96+
self.assertEqual(mock_tracer.current_span.attributes, expected_attrs)
97+
mock_tracer.end_span.assert_called_with()
98+
99+
100+
class MockCommand(object):
101+
def __init__(self, command_attrs):
102+
self.command_attrs = command_attrs
103+
104+
def get(self, key, default=None):
105+
return self.command_attrs[key] \
106+
if key in self.command_attrs else default
107+
108+
109+
class MockEvent(object):
110+
def __init__(self, command_attrs):
111+
self.command = MockCommand(command_attrs)
112+
113+
def __getattr__(self, item):
114+
return item
115+
116+
117+
class MockTracer(object):
118+
def __init__(self):
119+
self.current_span = None
120+
self.end_span = mock.Mock()
121+
122+
def start_span(self, name=None):
123+
span = mock.Mock()
124+
span.name = name
125+
span.attributes = {}
126+
self.current_span = span
127+
return span
128+
129+
def add_attribute_to_current_span(self, key, value):
130+
self.current_span.attributes[key] = value

0 commit comments

Comments
 (0)