Delete Me
Delete Me

Reputation: 111

Running A Process In the Background In Flask

I am making a webapp in python and flask which will email & tweet to the webmaster when the moment his website goes down. I am thinking of running an infinite while loop which waits for 10 minutes and then send a request to the website to be checked and checks if the returned response is 200. The problem is that were in the script can I insert this loop? Any suggestions about how I can achieve this?

Upvotes: 1

Views: 2853

Answers (1)

Jan Vlcinsky
Jan Vlcinsky

Reputation: 44092

Soldier, tell me when you are dead

Trying to report a problem from dying app is not the most reliable method.

Use external process to watch the app

You shall have some external independent app, monitoring your app.

If you live in pure Python environment, you may write a script, which will check if accessing some app url succeeds and if not, it would alert someone. For alerting, you may try e.g. logbook with log records being sent by e-mail (see MailHandler or GMailHandler).

In production environment it would be best to run some monitoring app like Nagios and simply check by check_http

Sample checking script using Logbook

For better readibility, the content is split to more parts, real script is in one file called monitor_url.py

monitor_url.py: Docstring, imports and MAIL_TEMPLATE

Docstring explains usage and is finally used by command line parser docopt

Imports are mostly logbook related

"""monitor_url.py - check GET access to a url, notify by GMail about problems
Usage:
    monitor_url.py   [options] <url> <from> <pswd> <to>...
    monitor_url.py -h

Options:
  -L, --logfile <logfile>    Name of logfile to write to [default: monitor_url.log].
  -N, --archives <archives>  Number of daily logs to keep, use 0 for unlimited [default: 0]

The check is performed once a minute and does HTTP GET request to <url>.
If there is a problem, it sends an e-mail using GMail account.
There is a limit of 6 e-mails, which can be sent per hour.
"""
import time
from logbook import Logger, GMailHandler, StderrHandler, NestedSetup, TimedRotatingFileHandler, Processor
from logbook.more import JinjaFormatter
from datetime import timedelta
import requests
from requests.exceptions import ConnectionError

MAIL_TEMPL = """Subject: {{ record.level_name }} on {{ record.extra.url }}

{{ record.message }}

Url: {{ record.extra.url }}
{% if record.exc_info %}
Exception: {{ record.formatted_exception }}
{% else %}
Status: {{ record.extra.req.status_code }}
Reason: {{ record.extra.req.reason }}
{% endif %}
"""

monitor_url.py: main function performing the checks

This script is looping in while and performs once a minute a check. If a problem is detected, or status code has changed, GMailHandler is configured to send e-mail.

def main(url, e_from, pswd, e_to, logfile, archives):
    log = Logger("httpWatcher")
    def inject_req(record):
        record.extra.url = url
        record.extra.req = req
    processor = Processor(inject_req)
    gmail_handler = GMailHandler(e_from, pswd, e_to,
        level="WARNING", record_limit=6, record_delta=timedelta(hours=1), bubble=True)
    gmail_handler.formatter = JinjaFormatter(MAIL_TEMPL)

    setup = NestedSetup([StderrHandler(),
        TimedRotatingFileHandler(logfile, bubble=True),
        gmail_handler,
        processor])

    with setup.applicationbound():
        last_status = 200
        while True:
            try:
                req = requests.get(url)
                if req.status_code != last_status:
                    log.warn("url was reached, status has changed")
                if req.ok:
                    log.info("url was reached, status OK")
                else:
                    log.error("Problem to reach the url")
                last_status = req.status_code
            except ConnectionError:
                req = None
                log.exception("Unable to connect")
                last_status = 0
            time.sleep(6)

montior_url.py final if __name__ ...

This part actually parses command line parameters and calls the main

if __name__ == "__main__":
    from docopt import docopt
    args = docopt(__doc__)
    print args
    main(args["<url>"], args["<from>"], args["<pswd>"], args["<to>"], args["--logfile"],
            int(args["--archives"]))

Call it

Try to call it without parameters:

$ python monitor_url.py 

Usage: monitor_url.py [options] ... monitor_url.py -h

To get full help, use -h

$ python monitor_url.py -h
...the help is the same as script docstring...

Use it for real monitoring, here used for monitoring http://localhost:8000

$ python monitor_url.py -L mylog.log -N 2 http://localhost:8000 <yourmail>@gmail.com xxxpasswordxxx [email protected]
{'--archives': '2',
 '--logfile': 'mylog.log',
 '-h': False,
 '<from>': '[email protected]',
 '<pswd>': 'xxxxxxx',
 '<to>': ['[email protected]'],
 '<url>': 'http://localhost:8000'}
[2014-06-09 19:41] INFO: httpWatcher: url was reached, status OK
[2014-06-09 19:41] ERROR: httpWatcher: Unable to connect
Traceback (most recent call last):
....(shortened)...
    raise ConnectionError(e, request=request)
ConnectionError: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: / (Caused by <class 'socket.error'>: [Errno 111] Connection refused)
[2014-06-09 19:41] WARNING: httpWatcher: url was reached, status has changed
[2014-06-09 19:41] INFO: httpWatcher: url was reached, status OK

Check logfile created in local folder.

Check your gmail inbox (if there is nothing, you have to play with the password).

Conclusions

Twitter notification is also possible with logbook, but is not shown here.

To run the script, Python 2.7 is expected and you shall install some packages:

$ pip install logbook jinja2 requests

Managing notification from such a script is not easy. Consider this script of beta quality in this regard.

Usage of solutions like Nagios seems to be more appropriate for this purpose.

Upvotes: 3

Related Questions