Skip to content

Commit

Permalink
Initial port from Flask to aiohttp.
Browse files Browse the repository at this point in the history
  • Loading branch information
Brannon Jones committed Nov 11, 2016
1 parent 98c1046 commit d079a13
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 133 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ nosetests.xml
.pydevproject

.idea

.python-version
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
The MIT License (MIT)

Copyright (c) 2016 Brannon Jones
Copyright (c) 2013 Runscope Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand All @@ -19,4 +20,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

73 changes: 38 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
Healthcheck
----------
-----------

Healthcheck wraps a Flask app object and adds a way to write simple heathcheck
functions that can be use to monitor your application. It's useful for
asserting that your dependencies are up and running and your application can
respond to HTTP requests. The Healthcheck functions are exposed via a user
defined flask route so you can use an external monitoring application (monit,
nagios, Runscope, etc.) to check the status and uptime of your application.
Based on https://github.com/Runscope/healthcheck.

New in version 1.1: Healthcheck also gives you a simple Flask route to view
information about your application's environment. By default, this includes
data about the operating system, the Python environment, the current process,
and the application config. You can customize which sections are included, or
add your own sections to the output.
aiohttp_healthcheck provides a set of simple aiohttp request handlers that make
it easy to write simple heathcheck functions that can be use to monitor your
application. It's useful for asserting that your dependencies are up and running
and your application can respond to HTTP requests. The Healthcheck functions are
exposed via a user defined aiohttp route so you can use an external monitoring
application (monit, nagios, Runscope, etc.) to check the status and uptime of
your application.

New in version 1.1: aiohttp_healthcheck also gives you a simple aiohttp route to
view information about your application's environment. By default, this includes
data about the operating system, the Python environment, and the current
process. You can customize which sections are included, or add your own sections
to the output.

## Installing

```
pip install healthcheck
pip install aiohttp-healthcheck
```

Expand All @@ -26,14 +29,16 @@ pip install healthcheck
Here's an example of basic usage:

```python
from flask import Flask
from healthcheck import HealthCheck, EnvironmentDump
from aiohttp import web
from aiohttp_healthcheck import HealthCheck, EnvironmentDump

app = Flask(__name__)
app = web.Application()

# wrap the flask app and give a heathcheck url
health = HealthCheck(app, "/healthcheck")
envdump = EnvironmentDump(app, "/environment")
# Bind the healthcheck to the app's router
health = HealthCheck()
envdump = EnvironmentDump()
app.router.add_get("/healthcheck", health)
app.router.add_get("/environment", envdump)

# add your own check function to the healthcheck
def redis_available():
Expand All @@ -45,8 +50,8 @@ health.add_check(redis_available)

# add your own data to the environment dump
def application_data():
return {"maintainer": "Frank Stratton",
"git_repo": "https://github.com/Runscope/healthcheck"}
return {"maintainer": "Brannon Jones",
"git_repo": "https://github.com/brannon/aiohttp-healthcheck"}

envdump.add_section("application", application_data)
```
Expand Down Expand Up @@ -116,11 +121,10 @@ healthcheck overall is failed.

### Caching

In Runscope's infrastructure, the /healthcheck endpoint is hit surprisingly
often. haproxy runs on every server, and each haproxy hits every healthcheck
twice a minute. (So if we have 30 servers in our infrastructure, that's 60
healthchecks per minute to every Flask service.) Plus, monit hits every
healthcheck 6 times a minute.
In a typical infrastructure, the /healthcheck endpoint can be hit surprisingly
often. If haproxy runs on every server, and each haproxy hits every healthcheck
twice a minute, with 30 servers that would be 60 healthchecks per minute to
every aiohttp service.

To avoid putting too much strain on backend services, health check results can
be cached in process memory. By default, health checks that succeed are cached
Expand All @@ -138,15 +142,13 @@ failure responses.

### Built-in data sections

By default, EnvironmentDump data includes these 4 sections:
By default, EnvironmentDump data includes these 3 sections:

* `os`: information about your operating system.
* `python`: information about your Python executable, Python path, and
installed packages.
* `process`: information about the currently running Python process, including
the PID, command line arguments, and all environment variables.
* `config`: information about your Flask app's configuration, pulled from
`app.config`.

Some of the data is scrubbed to avoid accidentally exposing passwords or access
keys/tokens. Config keys and environment variable names are scanned for `key`,
Expand All @@ -159,9 +161,9 @@ For security reasons, you may want to disable an entire section. You can
disable sections when you instantiate the `EnvironmentDump` object, like this:

```python
envdump = EnvironmentDump(app, "/environment",
include_python=False, include_os=False,
include_process=False, include_config=False)
envdump = EnvironmentDump(include_python=False,
include_os=False,
include_process=False)
```

### Adding custom data sections
Expand All @@ -171,9 +173,10 @@ Here's an example of how this would be used:

```python
def application_data():
return {"maintainer": "Frank Stratton",
"git_repo": "https://github.com/Runscope/healthcheck"}
return {"maintainer": "Brannon Jones",
"git_repo": "https://github.com/brannon/aiohttp-healthcheck"}

envdump = EnvironmentDump(app, "/environment")
envdump = EnvironmentDump()
app.router.add_get("/environment", envdump)
envdump.add_section("application", application_data)
```
77 changes: 44 additions & 33 deletions healthcheck/__init__.py → aiohttp_healthcheck/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import asyncio
import imp
import json
import os
import six
import logging
import socket
import sys
import time
import traceback
from flask import current_app
from aiohttp import web
try:
from functools import reduce
except Exception:
Expand Down Expand Up @@ -44,12 +45,12 @@ def check_reduce(passed, result):


class HealthCheck(object):
def __init__(self, app=None, path=None, success_status=200,
success_headers=None, success_handler=json_success_handler,
success_ttl=27, failed_status=500, failed_headers=None,
def __init__(self, success_status=200, success_headers=None,
success_handler=json_success_handler, success_ttl=27,
failed_status=500, failed_headers=None,
failed_handler=json_failed_handler, failed_ttl=9,
exception_handler=basic_exception_handler, checkers=None,
**options):
logger=None, **options):
self.cache = dict()

self.success_status = success_status
Expand All @@ -67,23 +68,29 @@ def __init__(self, app=None, path=None, success_status=200,
self.options = options
self.checkers = checkers or []

if app:
self.init_app(app, path)
self.logger = logger
if not self.logger:
self.logger = logging.getLogger('HealthCheck')

def init_app(self, app, path):
if path:
app.add_url_rule(path, view_func=self.check, **self.options)
@asyncio.coroutine
def __call__(self, request):
message, status, headers = yield from self.check()
return web.Response(text=message, status=status, headers=headers)

def add_check(self, func):
if not asyncio.iscoroutinefunction(func):
func = asyncio.coroutine(func)

self.checkers.append(func)

@asyncio.coroutine
def check(self):
results = []
for checker in self.checkers:
if checker in self.cache and self.cache[checker].get('expires') >= time.time():
result = self.cache[checker]
else:
result = self.run_check(checker)
result = yield from self.run_check(checker)
self.cache[checker] = result
results.append(result)

Expand All @@ -102,18 +109,19 @@ def check(self):

return message, self.failed_status, self.failed_headers

@asyncio.coroutine
def run_check(self, checker):
try:
passed, output = checker()
passed, output = yield from checker()
except Exception:
traceback.print_exc()
e = sys.exc_info()[0]
current_app.logger.exception(e)
self.logger.exception(e)
passed, output = self.exception_handler(checker, e)

if not passed:
msg = 'Health check "{}" failed with output "{}"'.format(checker.__name__, output)
current_app.logger.error(msg)
self.logger.error(msg)

timestamp = time.time()
if passed:
Expand All @@ -130,46 +138,47 @@ def run_check(self, checker):


class EnvironmentDump(object):
def __init__(self, app=None, path=None,
include_os=True, include_python=True,
include_config=True, include_process=True):
def __init__(self,
include_os=True,
include_python=True,
include_process=True):
self.functions = {}
if include_os:
self.functions['os'] = self.get_os
if include_python:
self.functions['python'] = self.get_python
if include_config:
self.functions['config'] = self.get_config
if include_process:
self.functions['process'] = self.get_process

if app:
self.init_app(app, path)

def init_app(self, app, path):
if path:
app.add_url_rule(path, view_func=self.dump_environment)
@asyncio.coroutine
def __call__(self, request):
data = yield from self.dump_environment()
return web.json_response(data)

def add_section(self, name, func):
if name in self.functions:
raise Exception('The name "{}" is already taken.'.format(name))

if not asyncio.iscoroutinefunction(func):
func = asyncio.coroutine(func)

self.functions[name] = func

@asyncio.coroutine
def dump_environment(self):
data = {}
for (name, func) in six.iteritems(self.functions):
data[name] = func()
for name, func in self.functions.items():
data[name] = yield from func()

return json.dumps(data), 200, {'Content-Type': 'application/json'}
return data

@asyncio.coroutine
def get_os(self):
return {'platform': sys.platform,
'name': os.name,
'uname': os.uname()}

def get_config(self):
return self.safe_dump(current_app.config)

@asyncio.coroutine
def get_python(self):
result = {'version': sys.version,
'executable': sys.executable,
Expand All @@ -186,6 +195,7 @@ def get_python(self):

return result

@asyncio.coroutine
def get_login(self):
# Based on https://github.com/gitpython-developers/GitPython/pull/43/
# Fix for 'Inappopropirate ioctl for device' on posix systems.
Expand All @@ -198,10 +208,11 @@ def get_login(self):
username = os.getlogin()
return username

@asyncio.coroutine
def get_process(self):
return {'argv': sys.argv,
'cwd': os.getcwd(),
'user': self.get_login(),
'user': (yield from self.get_login()),
'pid': os.getpid(),
'environ': self.safe_dump(os.environ)}

Expand Down
2 changes: 0 additions & 2 deletions jenkins.conf

This file was deleted.

1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
aiohttp==1.1.1
pylint==1.2.1
pep8==1.5.7
six==1.10.0
31 changes: 18 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
#!/usr/bin/env python
import sys
from setuptools import setup

from setuptools import setup, find_packages

setup(name='healthcheck',
install_requires = ['aiohttp>=1.1.1']


setup(name='aiohttp_healthcheck',
version='1.3.1',
description='Adds healthcheck endpoints to Flask apps',
description='Adds healthcheck endpoints to aiohttp apps. Based on https://github.com/Runscope/healthcheck.',
author='Frank Stratton',
author_email='frank@runscope.com',
url='https://github.com/Runscope/healthcheck',
download_url='https://github.com/Runscope/healthcheck/tarball/1.3.1',
packages=find_packages(),
zip_safe=False,
include_package_data=True,
maintainer='Brannon Jones',
maintainer_email='brannonj@gmail.com',
url='https://github.com/brannon/aiohttp-healthcheck',
packages=['aiohttp_healthcheck'],
zip_safe=True,
license='MIT',
platforms='any',
install_requires=[],
test_requires=['flask'],
test_suite='test_healthcheck',
# python_requires='>=3.4.2',
install_requires=install_requires,
classifiers=('Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Flask',
'Programming Language :: Python'))
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5'))
Loading

0 comments on commit d079a13

Please sign in to comment.