Merge lp:~lifeless/python-oops-amqp/misc into lp:python-oops-amqp

Proposed by Robert Collins
Status: Merged
Approved by: William Grant
Approved revision: 16
Merged at revision: 18
Proposed branch: lp:~lifeless/python-oops-amqp/misc
Merge into: lp:python-oops-amqp
Diff against target: 367 lines (+132/-28)
10 files modified
Makefile (+13/-0)
NEWS (+12/-0)
README (+6/-6)
oops_amqp/__init__.py (+1/-1)
oops_amqp/publisher.py (+3/-3)
oops_amqp/tests/test_publisher.py (+9/-9)
oops_amqp/tests/test_receiver.py (+6/-6)
oops_amqp/trace.py (+74/-0)
setup.py (+6/-2)
versions.cfg (+2/-1)
To merge this branch: bzr merge lp:~lifeless/python-oops-amqp/misc
Reviewer Review Type Date Requested Status
William Grant code Approve
James Westby (community) Approve
Review via email: mp+119076@code.launchpad.net

Description of the change

This updates the API to match current oops releases, and adds a useful trace helper.

To post a comment you must log in.
lp:~lifeless/python-oops-amqp/misc updated
16. By Robert Collins

Release 0.0.7.

Revision history for this message
James Westby (james-w) wrote :

281 + description = "Load OOPS reports into oops-tools from AMQP."

Also, I think setup.py should change the requirement to oops>=0.0.11.

Otherwise I think this is ok.

Thanks,

James

Revision history for this message
James Westby (james-w) :
review: Approve
lp:~lifeless/python-oops-amqp/misc updated
17. By Robert Collins

Fix bad trace description.

18. By Robert Collins

Encode the version constraint for oops in setup.py.

Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'Makefile'
--- Makefile 1970-01-01 00:00:00 +0000
+++ Makefile 2012-08-10 03:02:19 +0000
@@ -0,0 +1,13 @@
1all:
2
3bin/buildout: buildout.cfg versions.cfg setup.py download-cache eggs
4 ./bootstrap.py \
5 --setup-source=download-cache/ez_setup.py \
6 --download-base=download-cache/dist --eggs=eggs
7
8
9download-cache:
10 bzr checkout --lightweight lp:lp-source-dependencies download-cache
11
12eggs:
13 mkdir eggs
014
=== modified file 'NEWS'
--- NEWS 2012-02-10 14:47:11 +0000
+++ NEWS 2012-08-10 03:02:19 +0000
@@ -3,6 +3,18 @@
33
4Changes and improvements to oops-amqp, grouped by release.4Changes and improvements to oops-amqp, grouped by release.
55
6NEXT
7----
8
90.0.7
10-----
11
12* New script 'oops-amqp-trace' which will trace oops reports received on an
13 AMQP exchange. (Robert Collins)
14
15* Updated to support the new publisher API in oops 0.0.11 and above. This is
16 incompatible with older versions of oops. (Robert Collins)
17
60.0.6180.0.6
7-----19-----
820
921
=== modified file 'README'
--- README 2011-12-08 10:52:36 +0000
+++ README 2012-08-10 03:02:19 +0000
@@ -29,7 +29,7 @@
2929
30* bson30* bson
3131
32* oops (http://pypi.python.org/pypi/oops)32* oops (http://pypi.python.org/pypi/oops) 0.0.11 or newer.
3333
34* amqplib34* amqplib
3535
@@ -63,7 +63,7 @@
63Provide the publisher to your OOPS config::63Provide the publisher to your OOPS config::
6464
65 >>> config = oops.Config()65 >>> config = oops.Config()
66 >>> config.publishers.append(publisher)66 >>> config.publisher = publisher
6767
68Any oops published via that config will now be sent via amqp.68Any oops published via that config will now be sent via amqp.
6969
@@ -82,14 +82,14 @@
82the publication failed. To prevent losing the OOPS its a good idea to have a 82the publication failed. To prevent losing the OOPS its a good idea to have a
83fallback publisher - either another AMQP publisher (to a different server) or83fallback publisher - either another AMQP publisher (to a different server) or
84one that spools locally (where you can pick up the OOPSes via rsync or some84one that spools locally (where you can pick up the OOPSes via rsync or some
85other mechanism. Using the oops standard helper publish_new_only will let you85other mechanism. Using the oops standard helper publish_with_fallback will let
86wrap the fallback publisher so that it only gets invoked if the primary86you wrap the fallback publisher so that it only gets invoked if the primary
87method failed::87method failed::
8888
89 >>> fallback_factory = partial(amqp.Connection, host="otherserver:5672",89 >>> fallback_factory = partial(amqp.Connection, host="otherserver:5672",
90 ... userid="guest", password="guest", virtual_host="/", insist=False)90 ... userid="guest", password="guest", virtual_host="/", insist=False)
91 >>> fallback_publisher = oops_amqp.Publisher(fallback_factory, "oopses", "")91 >>> fallback_publisher = oops_amqp.Publisher(fallback_factory, "oopses", "")
92 >>> config.publishers.append(publish_new_only(fallback_publisher))92 >>> config.publisher = publish_with_fallback(publisher, fallback_publisher)
9393
94Receiving from AMQP94Receiving from AMQP
95+++++++++++++++++++95+++++++++++++++++++
@@ -106,7 +106,7 @@
106106
107 >>> publisher = oops_datedir_repo.DateDirRepo('.', inherit_id=True)107 >>> publisher = oops_datedir_repo.DateDirRepo('.', inherit_id=True)
108 >>> config = oops.Config()108 >>> config = oops.Config()
109 >>> config.publishers.append(publisher.publish)109 >>> config.publisher = publisher.publish
110 >>> receiver = oops_amqp.Receiver(config, factory, "my queue")110 >>> receiver = oops_amqp.Receiver(config, factory, "my queue")
111 >>> receiver.run_forever()111 >>> receiver.run_forever()
112112
113113
=== modified file 'oops_amqp/__init__.py'
--- oops_amqp/__init__.py 2012-02-13 00:03:12 +0000
+++ oops_amqp/__init__.py 2012-08-10 03:02:19 +0000
@@ -97,7 +97,7 @@
97# established at this point, and setup.py will use a version of next-$(revno).97# established at this point, and setup.py will use a version of next-$(revno).
98# If the releaselevel is 'final', then the tarball will be major.minor.micro.98# If the releaselevel is 'final', then the tarball will be major.minor.micro.
99# Otherwise it is major.minor.micro~$(revno).99# Otherwise it is major.minor.micro~$(revno).
100__version__ = (0, 0, 6, 'beta', 0)100__version__ = (0, 0, 7, 'final', 0)
101101
102__all__ = [102__all__ = [
103 'Publisher',103 'Publisher',
104104
=== modified file 'oops_amqp/publisher.py'
--- oops_amqp/publisher.py 2012-02-09 23:13:45 +0000
+++ oops_amqp/publisher.py 2012-08-10 03:02:19 +0000
@@ -88,7 +88,7 @@
88 message.properties["delivery_mode"] = 288 message.properties["delivery_mode"] = 2
89 channel = self.get_channel()89 channel = self.get_channel()
90 if channel is None:90 if channel is None:
91 return None91 return []
92 try:92 try:
93 channel.basic_publish(93 channel.basic_publish(
94 message, self.exchange_name, routing_key=self.routing_key)94 message, self.exchange_name, routing_key=self.routing_key)
@@ -96,7 +96,7 @@
96 self.channels.channel = None96 self.channels.channel = None
97 if is_amqplib_connection_error(e):97 if is_amqplib_connection_error(e):
98 # Could not connect / interrupted connection98 # Could not connect / interrupted connection
99 return None99 return []
100 # Unknown error mode : don't hide it.100 # Unknown error mode : don't hide it.
101 raise101 raise
102 return report['id']102 return [report['id']]
103103
=== modified file 'oops_amqp/tests/test_publisher.py'
--- oops_amqp/tests/test_publisher.py 2012-02-09 23:13:45 +0000
+++ oops_amqp/tests/test_publisher.py 2012-08-10 03:02:19 +0000
@@ -40,9 +40,9 @@
40 reference_oops = {'id': 'kept', 'akey': 'avalue'}40 reference_oops = {'id': 'kept', 'akey': 'avalue'}
41 oops = dict(reference_oops)41 oops = dict(reference_oops)
42 expected_id = 'kept'42 expected_id = 'kept'
43 oops_id = publisher(oops)43 oops_ids = publisher(oops)
44 # Publication returns the oops ID allocated.44 # Publication returns the oops ID allocated.
45 self.assertEqual(expected_id, oops_id)45 self.assertEqual([expected_id], oops_ids)
46 # The oops should not be altered by publication.46 # The oops should not be altered by publication.
47 self.assertEqual(reference_oops, oops)47 self.assertEqual(reference_oops, oops)
48 # The received OOPS should have the ID embedded and be a bson dict.48 # The received OOPS should have the ID embedded and be a bson dict.
@@ -66,14 +66,14 @@
66 oops = dict(reference_oops)66 oops = dict(reference_oops)
67 id_bson = md5(bson.dumps(oops)).hexdigest()67 id_bson = md5(bson.dumps(oops)).hexdigest()
68 expected_id = "OOPS-%s" % id_bson68 expected_id = "OOPS-%s" % id_bson
69 oops_id = publisher(oops)69 oops_ids = publisher(oops)
70 # Publication returns the oops ID allocated.70 # Publication returns the oops ID allocated.
71 self.assertEqual(expected_id, oops_id)71 self.assertEqual([expected_id], oops_ids)
72 # The oops should not be altered by publication.72 # The oops should not be altered by publication.
73 self.assertEqual(reference_oops, oops)73 self.assertEqual(reference_oops, oops)
74 # The received OOPS should have the ID embedded and be a bson dict.74 # The received OOPS should have the ID embedded and be a bson dict.
75 expected_oops = dict(reference_oops)75 expected_oops = dict(reference_oops)
76 expected_oops['id'] = oops_id76 expected_oops['id'] = oops_ids[0]
77 def check_oops(msg):77 def check_oops(msg):
78 self.assertEqual(expected_oops, bson.loads(msg.body))78 self.assertEqual(expected_oops, bson.loads(msg.body))
79 channel.basic_ack(msg.delivery_tag)79 channel.basic_ack(msg.delivery_tag)
@@ -98,11 +98,11 @@
98 publisher = Publisher(98 publisher = Publisher(
99 self.connection_factory, queue.exchange_name, "")99 self.connection_factory, queue.exchange_name, "")
100 oops = {'akey': 42}100 oops = {'akey': 42}
101 self.assertEqual(None, publisher(oops))101 self.assertEqual([], publisher(oops))
102 finally:102 finally:
103 self.rabbit.runner._start()103 self.rabbit.runner._start()
104 queue.channel = self.connection_factory().channel()104 queue.channel = self.connection_factory().channel()
105 self.assertNotEqual(None, publisher(oops))105 self.assertNotEqual([], publisher(oops))
106106
107 def test_publish_amqp_down_after_use(self):107 def test_publish_amqp_down_after_use(self):
108 # If amqp goes down after its been successfully used, None is returned108 # If amqp goes down after its been successfully used, None is returned
@@ -119,9 +119,9 @@
119 # release.119 # release.
120 self.rabbit.runner._stop()120 self.rabbit.runner._stop()
121 try:121 try:
122 self.assertEqual(None, publisher(oops))122 self.assertEqual([], publisher(oops))
123 finally:123 finally:
124 self.rabbit.runner._start()124 self.rabbit.runner._start()
125 queue.channel = self.connection_factory().channel()125 queue.channel = self.connection_factory().channel()
126 self.assertNotEqual(None, publisher(oops))126 self.assertNotEqual([], publisher(oops))
127127
128128
=== modified file 'oops_amqp/tests/test_receiver.py'
--- oops_amqp/tests/test_receiver.py 2012-02-09 23:13:45 +0000
+++ oops_amqp/tests/test_receiver.py 2012-08-10 03:02:19 +0000
@@ -39,7 +39,7 @@
39 reports = []39 reports = []
40 def capture(report):40 def capture(report):
41 reports.append(report)41 reports.append(report)
42 return report['id']42 return [report['id']]
43 expected_report = {'id': 'foo', 'otherkey': 42}43 expected_report = {'id': 'foo', 'otherkey': 42}
44 message = amqp.Message(bson.dumps(expected_report))44 message = amqp.Message(bson.dumps(expected_report))
45 channel = self.useFixture(45 channel = self.useFixture(
@@ -51,7 +51,7 @@
51 channel.basic_publish(51 channel.basic_publish(
52 amqp.Message(sentinel), queue.exchange_name, routing_key="")52 amqp.Message(sentinel), queue.exchange_name, routing_key="")
53 config = Config()53 config = Config()
54 config.publishers.append(capture)54 config.publisher = capture
55 receiver = Receiver(config, self.connection_factory, queue.queue_name)55 receiver = Receiver(config, self.connection_factory, queue.queue_name)
56 receiver.sentinel = sentinel56 receiver.sentinel = sentinel
57 receiver.run_forever()57 receiver.run_forever()
@@ -62,7 +62,7 @@
62 reports = []62 reports = []
63 def capture(report):63 def capture(report):
64 reports.append(report)64 reports.append(report)
65 return report['id']65 return [report['id']]
66 expected_report = {'id': 'foo', 'otherkey': 42}66 expected_report = {'id': 'foo', 'otherkey': 42}
67 message = amqp.Message(bson.dumps(expected_report))67 message = amqp.Message(bson.dumps(expected_report))
68 channel = self.useFixture(68 channel = self.useFixture(
@@ -71,7 +71,7 @@
71 channel.basic_publish(71 channel.basic_publish(
72 message, queue.exchange_name, routing_key="")72 message, queue.exchange_name, routing_key="")
73 config = Config()73 config = Config()
74 config.publishers.append(capture)74 config.publisher = capture
75 # We don't want to loop forever: patch the channel so that after one75 # We don't want to loop forever: patch the channel so that after one
76 # call to wait (which will get our injected message) the loop will shut76 # call to wait (which will get our injected message) the loop will shut
77 # down.77 # down.
@@ -134,7 +134,7 @@
134 reports = []134 reports = []
135 def capture(report):135 def capture(report):
136 reports.append(report)136 reports.append(report)
137 return report['id']137 return [report['id']]
138 expected_report = {'id': 'foo', 'otherkey': 42}138 expected_report = {'id': 'foo', 'otherkey': 42}
139 message = amqp.Message(bson.dumps(expected_report))139 message = amqp.Message(bson.dumps(expected_report))
140 channel = self.useFixture(140 channel = self.useFixture(
@@ -142,7 +142,7 @@
142 queue = self.useFixture(QueueFixture(channel, self.getUniqueString))142 queue = self.useFixture(QueueFixture(channel, self.getUniqueString))
143 channel.basic_publish(message, queue.exchange_name, routing_key="")143 channel.basic_publish(message, queue.exchange_name, routing_key="")
144 config = Config()144 config = Config()
145 config.publishers.append(capture)145 config.publisher = capture
146 state = {}146 state = {}
147 def error_once(func):147 def error_once(func):
148 def wrapped(*args, **kwargs):148 def wrapped(*args, **kwargs):
149149
=== added file 'oops_amqp/trace.py'
--- oops_amqp/trace.py 1970-01-01 00:00:00 +0000
+++ oops_amqp/trace.py 2012-08-10 03:02:19 +0000
@@ -0,0 +1,74 @@
1# Copyright (c) 2012, Canonical Ltd
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published by
5# the Free Software Foundation, version 3 only.
6#
7# This program is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU Lesser General Public License for more details.
11#
12# You should have received a copy of the GNU Lesser General Public License
13# along with this program. If not, see <http://www.gnu.org/licenses/>.
14# GNU Lesser General Public License version 3 (see the file LICENSE).
15
16"""Trace OOPS reports coming from an AMQP queue."""
17
18from functools import partial
19import sys
20import optparse
21from textwrap import dedent
22
23import amqplib.client_0_8 as amqp
24import oops
25import oops_amqp
26
27import anybson as bson
28
29
30def main(argv=None):
31 if argv is None:
32 argv=sys.argv
33 usage = dedent("""\
34 %prog [options]
35
36 The following options must be supplied:
37 --host
38
39 e.g.
40 oops-amqp-trace --host "localhost:3472"
41
42 If you do not have a persistent queue, you should run this script
43 before generating oopses, as AMQP will discard messages with no
44 consumers.
45 """)
46 description = "Trace OOPS reports coming from an AMQP queue."
47 parser = optparse.OptionParser(
48 description=description, usage=usage)
49 parser.add_option('--host', help="AQMP host / host:port.")
50 parser.add_option('--username', help="AQMP username.", default="guest")
51 parser.add_option('--password', help="AQMP password.", default="guest")
52 parser.add_option('--vhost', help="AMQP vhost.", default="/")
53 parser.add_option('--exchange', help="AMQP exchange name.", default="oopses")
54 options, args = parser.parse_args(argv[1:])
55 def needed(optname):
56 if getattr(options, optname, None) is None:
57 raise ValueError('option "%s" must be supplied' % optname)
58 needed('host')
59 factory = partial(
60 amqp.Connection, host=options.host, userid=options.username,
61 password=options.password, virtual_host=options.vhost)
62 connection = factory()
63 channel = connection.channel()
64 channel.exchange_declare(options.exchange, type="fanout", durable=False,
65 auto_delete=True)
66 queue = channel.queue_declare(durable=False, auto_delete=True)[0]
67 channel.queue_bind(queue, options.exchange)
68 config = oops.Config()
69 config.publisher = oops.pprint_to_stream(sys.stdout)
70 receiver = oops_amqp.Receiver(config, factory, queue)
71 try:
72 receiver.run_forever()
73 except KeyboardInterrupt:
74 pass
075
=== modified file 'setup.py'
--- setup.py 2012-02-10 14:47:11 +0000
+++ setup.py 2012-08-10 03:02:19 +0000
@@ -23,7 +23,7 @@
23 os.path.join(os.path.dirname(__file__), 'README'), 'rb').read()23 os.path.join(os.path.dirname(__file__), 'README'), 'rb').read()
2424
25setup(name="oops_amqp",25setup(name="oops_amqp",
26 version="0.0.6",26 version="0.0.7",
27 description=\27 description=\
28 "OOPS AMQP transport.",28 "OOPS AMQP transport.",
29 long_description=description,29 long_description=description,
@@ -41,7 +41,7 @@
41 ],41 ],
42 install_requires = [42 install_requires = [
43 'pymongo',43 'pymongo',
44 'oops',44 'oops>=0.0.11',
45 'amqplib',45 'amqplib',
46 ],46 ],
47 extras_require = dict(47 extras_require = dict(
@@ -51,4 +51,8 @@
51 'testtools',51 'testtools',
52 ]52 ]
53 ),53 ),
54 entry_points=dict(
55 console_scripts=[ # `console_scripts` is a magic name to setuptools
56 'oops-amqp-trace = oops_amqp.trace:main',
57 ]),
54 )58 )
5559
=== modified file 'versions.cfg'
--- versions.cfg 2011-10-10 21:49:50 +0000
+++ versions.cfg 2012-08-10 03:02:19 +0000
@@ -6,7 +6,8 @@
6bson = 0.3.26bson = 0.3.2
7fixtures = 0.3.67fixtures = 0.3.6
8iso8601 = 0.1.48iso8601 = 0.1.4
9oops = 0.0.69oops = 0.0.13
10pymongo = 2.1.1
10pytz = 2010o11pytz = 2010o
11rabbitfixture = 0.3.212rabbitfixture = 0.3.2
12setuptools = 0.6c1113setuptools = 0.6c11

Subscribers

People subscribed via source and target branches

to all changes: