Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'jbenet-crashsms'

  • Loading branch information...
commit 7f90eeb476568c3e498c321d0f76a880d61ec036 2 parents 9b5e58e + 2a2e924
Roger Hoover authored
1  setup.py
View
@@ -44,6 +44,7 @@
entry_points = """\
[console_scripts]
httpok = superlance.httpok:main
+ crashsms = superlance.crashsms:main
crashmail = superlance.crashmail:main
crashmailbatch = superlance.crashmailbatch:main
fatalmailbatch = superlance.fatalmailbatch:main
2  superlance/crashmailbatch.py
View
@@ -21,7 +21,7 @@
# as a listener is below.
#
# [eventlistener:crashmailbatch]
-# command=python crashmailbatch
+# command=python crashmailbatch --toEmail=you@bar.com --fromEmail=me@bar.com
# events=PROCESS_STATE,TICK_60
doc = """\
87 superlance/crashsms.py
View
@@ -0,0 +1,87 @@
+#!/usr/bin/env python -u
+##############################################################################
+#
+# Copyright (c) 2007 Agendaless Consulting and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the BSD-like license at
+# http://www.repoze.org/LICENSE.txt. A copy of the license should accompany
+# this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
+# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
+# FITNESS FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+##############################################################################
+# crashsms
+# author: Juan Batiz-Benet (http://github.com/jbenet)
+# based on crashmailbatch.py
+##############################################################################
+
+
+# A event listener meant to be subscribed to PROCESS_STATE_CHANGE
+# events. It will send mail when processes that are children of
+# supervisord transition unexpectedly to the EXITED state.
+
+# A supervisor config snippet that tells supervisor to use this script
+# as a listener is below.
+#
+# [eventlistener:crashsms]
+# command=python crashsms -t <mobile phone>@<mobile provider> -f me@bar.com -e TICK_5
+# events=PROCESS_STATE,TICK_5
+
+doc = """\
+crashsms.py [--interval=<batch interval in minutes>]
+ [--toEmail=<email address>]
+ [--fromEmail=<email address>]
+ [--subject=<email subject>]
+
+Options:
+
+-i,--interval - batch cycle length (in minutes). The default is 1 minute.
+ This means that all events in each cycle are batched together
+ and sent as a single email
+
+-t,--toEmail - the email address to send alerts to. Mobile providers
+ tend to allow sms messages to be sent to their phone numbers
+ via an email address (e.g.: 1234567890@txt.att.net)
+
+-f,--fromEmail - the email address to send alerts from
+
+-s,--subject - the email subject line
+
+-e, --tickEvent - specify which TICK event to use (e.g. TICK_5, TICK_60, TICK_3600)
+
+A sample invocation:
+
+crashsms.py -t <mobile phone>@<mobile provider> -f me@bar.com -e TICK_5
+
+"""
+
+from supervisor import childutils
+from superlance.process_state_email_monitor import ProcessStateEmailMonitor
+
+class CrashSMS(ProcessStateEmailMonitor):
+ processStateEvents = ['PROCESS_STATE_EXITED']
+
+ def __init__(self, **kwargs):
+ ProcessStateEmailMonitor.__init__(self, **kwargs)
+ self.now = kwargs.get('now', None)
+
+ def getProcessStateChangeMsg(self, headers, payload):
+ pheaders, pdata = childutils.eventdata(payload+'\n')
+
+ if int(pheaders['expected']):
+ return None
+
+ txt = '[%(groupname)s:%(processname)s](%(pid)s) exited unexpectedly' \
+ % pheaders
+ return '%s %s' % (txt, childutils.get_asctime(self.now))
+
+def main():
+ crash = CrashSMS.createFromCmdLine()
+ crash.run()
+
+if __name__ == '__main__':
+ main()
10 superlance/process_state_email_monitor.py
View
@@ -33,7 +33,7 @@ def createFromCmdLine(cls):
from optparse import OptionParser
parser = OptionParser()
- parser.add_option("-i", "--interval", dest="interval", type="int",
+ parser.add_option("-i", "--interval", dest="interval", type="float", default=1.0,
help="batch interval in minutes (defaults to 1 minute)")
parser.add_option("-t", "--toEmail", dest="toEmail",
help="destination email address")
@@ -43,6 +43,9 @@ def createFromCmdLine(cls):
help="email subject")
parser.add_option("-H", "--smtpHost", dest="smtpHost", default="localhost",
help="SMTP server hostname or address")
+ parser.add_option("-e", "--tickEvent", dest="eventname", default="TICK_60",
+ help="TICK event name (defaults to TICK_60)")
+
(options, args) = parser.parse_args()
if not options.toEmail:
@@ -63,7 +66,7 @@ def __init__(self, **kwargs):
self.fromEmail = kwargs['fromEmail']
self.toEmail = kwargs['toEmail']
- self.subject = kwargs.get('subject', 'Alert from supervisord')
+ self.subject = kwargs.get('subject')
self.smtpHost = kwargs.get('smtpHost', 'localhost')
self.digestLen = 76
@@ -92,7 +95,8 @@ def getBatchEmail(self):
def sendEmail(self, email):
msg = MIMEText(email['body'])
- msg['Subject'] = email['subject']
+ if self.subject:
+ msg['Subject'] = email['subject']
msg['From'] = email['from']
msg['To'] = email['to']
27 superlance/process_state_monitor.py
View
@@ -27,15 +27,28 @@ class ProcessStateMonitor:
processStateEvents = []
def __init__(self, **kwargs):
- self.interval = kwargs.get('interval', 1)
+ self.interval = kwargs.get('interval', 1.0)
self.debug = kwargs.get('debug', False)
self.stdin = kwargs.get('stdin', sys.stdin)
self.stdout = kwargs.get('stdout', sys.stdout)
self.stderr = kwargs.get('stderr', sys.stderr)
+ self.eventname = kwargs.get('eventname', 'TICK_60')
+ self.tickmins = self._get_tick_mins(self.eventname)
self.batchMsgs = []
- self.batchMins = 0
+ self.batchMins = 0.0
+
+ def _get_tick_mins(self, eventname):
+ return float(self._get_tick_secs(eventname))/60.0
+
+ def _get_tick_secs(self, eventname):
+ self._validate_tick_name(eventname)
+ return int(eventname.split('_')[1])
+
+ def _validate_tick_name(self, eventname):
+ if not eventname.startswith('TICK_'):
+ raise ValueError("Invalid TICK event name: %s" % eventname)
def run(self):
while 1:
@@ -46,8 +59,8 @@ def run(self):
def handleEvent(self, headers, payload):
if headers['eventname'] in self.processStateEvents:
self.handleProcessStateChangeEvent(headers, payload)
- elif headers['eventname'] == 'TICK_60':
- self.handleTick60Event(headers, payload)
+ elif headers['eventname'] == self.eventname:
+ self.handleTickEvent(headers, payload)
def handleProcessStateChangeEvent(self, headers, payload):
msg = self.getProcessStateChangeMsg(headers, payload)
@@ -61,8 +74,8 @@ def handleProcessStateChangeEvent(self, headers, payload):
def getProcessStateChangeMsg(self, headers, payload):
return None
- def handleTick60Event(self, headers, payload):
- self.batchMins += 1
+ def handleTickEvent(self, headers, payload):
+ self.batchMins += self.tickmins
if self.batchMins >= self.interval:
self.sendBatchNotification()
self.clearBatch()
@@ -80,7 +93,7 @@ def getBatchMsgs(self):
return self.batchMsgs
def clearBatch(self):
- self.batchMins = 0;
+ self.batchMins = 0.0;
self.batchMsgs = [];
def writeToStderr(self, msg):
17 superlance/tests/crashsms_test.py
View
@@ -0,0 +1,17 @@
+import unittest
+import mock
+import time
+from StringIO import StringIO
+
+from crashmailbatch_test import CrashMailBatchTests
+
+class CrashSMSBatchTests(CrashMailBatchTests):
+ subject = 'None'
+ unexpectedErrorMsg = '[bar:foo](58597) exited unexpectedly'
+
+ def _getTargetClass(self):
+ from superlance.crashsms import CrashSMS
+ return CrashSMS
+
+if __name__ == '__main__':
+ unittest.main()
15 superlance/tests/process_state_monitor_test.py
View
@@ -44,6 +44,17 @@ def getTick60Event(self):
}
payload = 'when:1279665240'
return (headers, payload)
+
+ def test__get_tick_secs(self):
+ monitor = self._makeOneMocked()
+ self.assertEquals(5, monitor._get_tick_secs('TICK_5'))
+ self.assertEquals(60, monitor._get_tick_secs('TICK_60'))
+ self.assertEquals(3600, monitor._get_tick_secs('TICK_3600'))
+ self.assertRaises(ValueError, monitor._get_tick_secs, 'JUNK_60')
+
+ def test__get_tick_mins(self):
+ monitor = self._makeOneMocked()
+ self.assertEquals(5.0/60.0, monitor._get_tick_mins('TICK_5'))
def test_handleEvent_exit(self):
monitor = self._makeOneMocked()
@@ -82,9 +93,9 @@ def test_handleEvent_tick_interval_not_expired(self):
monitor = self._makeOneMocked(interval=3)
hdrs, payload = self.getTick60Event()
monitor.handleEvent(hdrs, payload)
- self.assertEquals(1, monitor.getBatchMinutes())
+ self.assertEquals(1.0, monitor.getBatchMinutes())
monitor.handleEvent(hdrs, payload)
- self.assertEquals(2, monitor.getBatchMinutes())
+ self.assertEquals(2.0, monitor.getBatchMinutes())
if __name__ == '__main__':
unittest.main()
Please sign in to comment.
Something went wrong with that request. Please try again.