Skip to content

Commit d079a13

Browse files
author
Brannon Jones
committed
Initial port from Flask to aiohttp.
1 parent 98c1046 commit d079a13

File tree

8 files changed

+160
-133
lines changed

8 files changed

+160
-133
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ nosetests.xml
3636
.pydevproject
3737

3838
.idea
39+
40+
.python-version

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
The MIT License (MIT)
22

3+
Copyright (c) 2016 Brannon Jones
34
Copyright (c) 2013 Runscope Inc.
45

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

README.md

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
Healthcheck
2-
----------
2+
-----------
33

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

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

1720
## Installing
1821

1922
```
20-
pip install healthcheck
23+
pip install aiohttp-healthcheck
2124
2225
```
2326

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

2831
```python
29-
from flask import Flask
30-
from healthcheck import HealthCheck, EnvironmentDump
32+
from aiohttp import web
33+
from aiohttp_healthcheck import HealthCheck, EnvironmentDump
3134

32-
app = Flask(__name__)
35+
app = web.Application()
3336

34-
# wrap the flask app and give a heathcheck url
35-
health = HealthCheck(app, "/healthcheck")
36-
envdump = EnvironmentDump(app, "/environment")
37+
# Bind the healthcheck to the app's router
38+
health = HealthCheck()
39+
envdump = EnvironmentDump()
40+
app.router.add_get("/healthcheck", health)
41+
app.router.add_get("/environment", envdump)
3742

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

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

5156
envdump.add_section("application", application_data)
5257
```
@@ -116,11 +121,10 @@ healthcheck overall is failed.
116121

117122
### Caching
118123

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

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

139143
### Built-in data sections
140144

141-
By default, EnvironmentDump data includes these 4 sections:
145+
By default, EnvironmentDump data includes these 3 sections:
142146

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

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

161163
```python
162-
envdump = EnvironmentDump(app, "/environment",
163-
include_python=False, include_os=False,
164-
include_process=False, include_config=False)
164+
envdump = EnvironmentDump(include_python=False,
165+
include_os=False,
166+
include_process=False)
165167
```
166168

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

172174
```python
173175
def application_data():
174-
return {"maintainer": "Frank Stratton",
175-
"git_repo": "https://github.com/Runscope/healthcheck"}
176+
return {"maintainer": "Brannon Jones",
177+
"git_repo": "https://github.com/brannon/aiohttp-healthcheck"}
176178

177-
envdump = EnvironmentDump(app, "/environment")
179+
envdump = EnvironmentDump()
180+
app.router.add_get("/environment", envdump)
178181
envdump.add_section("application", application_data)
179182
```

healthcheck/__init__.py renamed to aiohttp_healthcheck/__init__.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import asyncio
12
import imp
23
import json
34
import os
4-
import six
5+
import logging
56
import socket
67
import sys
78
import time
89
import traceback
9-
from flask import current_app
10+
from aiohttp import web
1011
try:
1112
from functools import reduce
1213
except Exception:
@@ -44,12 +45,12 @@ def check_reduce(passed, result):
4445

4546

4647
class HealthCheck(object):
47-
def __init__(self, app=None, path=None, success_status=200,
48-
success_headers=None, success_handler=json_success_handler,
49-
success_ttl=27, failed_status=500, failed_headers=None,
48+
def __init__(self, success_status=200, success_headers=None,
49+
success_handler=json_success_handler, success_ttl=27,
50+
failed_status=500, failed_headers=None,
5051
failed_handler=json_failed_handler, failed_ttl=9,
5152
exception_handler=basic_exception_handler, checkers=None,
52-
**options):
53+
logger=None, **options):
5354
self.cache = dict()
5455

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

70-
if app:
71-
self.init_app(app, path)
71+
self.logger = logger
72+
if not self.logger:
73+
self.logger = logging.getLogger('HealthCheck')
7274

73-
def init_app(self, app, path):
74-
if path:
75-
app.add_url_rule(path, view_func=self.check, **self.options)
75+
@asyncio.coroutine
76+
def __call__(self, request):
77+
message, status, headers = yield from self.check()
78+
return web.Response(text=message, status=status, headers=headers)
7679

7780
def add_check(self, func):
81+
if not asyncio.iscoroutinefunction(func):
82+
func = asyncio.coroutine(func)
83+
7884
self.checkers.append(func)
7985

86+
@asyncio.coroutine
8087
def check(self):
8188
results = []
8289
for checker in self.checkers:
8390
if checker in self.cache and self.cache[checker].get('expires') >= time.time():
8491
result = self.cache[checker]
8592
else:
86-
result = self.run_check(checker)
93+
result = yield from self.run_check(checker)
8794
self.cache[checker] = result
8895
results.append(result)
8996

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

103110
return message, self.failed_status, self.failed_headers
104111

112+
@asyncio.coroutine
105113
def run_check(self, checker):
106114
try:
107-
passed, output = checker()
115+
passed, output = yield from checker()
108116
except Exception:
109117
traceback.print_exc()
110118
e = sys.exc_info()[0]
111-
current_app.logger.exception(e)
119+
self.logger.exception(e)
112120
passed, output = self.exception_handler(checker, e)
113121

114122
if not passed:
115123
msg = 'Health check "{}" failed with output "{}"'.format(checker.__name__, output)
116-
current_app.logger.error(msg)
124+
self.logger.error(msg)
117125

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

131139

132140
class EnvironmentDump(object):
133-
def __init__(self, app=None, path=None,
134-
include_os=True, include_python=True,
135-
include_config=True, include_process=True):
141+
def __init__(self,
142+
include_os=True,
143+
include_python=True,
144+
include_process=True):
136145
self.functions = {}
137146
if include_os:
138147
self.functions['os'] = self.get_os
139148
if include_python:
140149
self.functions['python'] = self.get_python
141-
if include_config:
142-
self.functions['config'] = self.get_config
143150
if include_process:
144151
self.functions['process'] = self.get_process
145152

146-
if app:
147-
self.init_app(app, path)
148-
149-
def init_app(self, app, path):
150-
if path:
151-
app.add_url_rule(path, view_func=self.dump_environment)
153+
@asyncio.coroutine
154+
def __call__(self, request):
155+
data = yield from self.dump_environment()
156+
return web.json_response(data)
152157

153158
def add_section(self, name, func):
154159
if name in self.functions:
155160
raise Exception('The name "{}" is already taken.'.format(name))
161+
162+
if not asyncio.iscoroutinefunction(func):
163+
func = asyncio.coroutine(func)
164+
156165
self.functions[name] = func
157166

167+
@asyncio.coroutine
158168
def dump_environment(self):
159169
data = {}
160-
for (name, func) in six.iteritems(self.functions):
161-
data[name] = func()
170+
for name, func in self.functions.items():
171+
data[name] = yield from func()
162172

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

175+
@asyncio.coroutine
165176
def get_os(self):
166177
return {'platform': sys.platform,
167178
'name': os.name,
168179
'uname': os.uname()}
169180

170-
def get_config(self):
171-
return self.safe_dump(current_app.config)
172-
181+
@asyncio.coroutine
173182
def get_python(self):
174183
result = {'version': sys.version,
175184
'executable': sys.executable,
@@ -186,6 +195,7 @@ def get_python(self):
186195

187196
return result
188197

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

211+
@asyncio.coroutine
201212
def get_process(self):
202213
return {'argv': sys.argv,
203214
'cwd': os.getcwd(),
204-
'user': self.get_login(),
215+
'user': (yield from self.get_login()),
205216
'pid': os.getpid(),
206217
'environ': self.safe_dump(os.environ)}
207218

jenkins.conf

Lines changed: 0 additions & 2 deletions
This file was deleted.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
aiohttp==1.1.1
12
pylint==1.2.1
23
pep8==1.5.7
34
six==1.10.0

setup.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
#!/usr/bin/env python
2+
import sys
3+
from setuptools import setup
24

3-
from setuptools import setup, find_packages
45

5-
setup(name='healthcheck',
6+
install_requires = ['aiohttp>=1.1.1']
7+
8+
9+
setup(name='aiohttp_healthcheck',
610
version='1.3.1',
7-
description='Adds healthcheck endpoints to Flask apps',
11+
description='Adds healthcheck endpoints to aiohttp apps. Based on https://github.com/Runscope/healthcheck.',
812
author='Frank Stratton',
913
author_email='[email protected]',
10-
url='https://github.com/Runscope/healthcheck',
11-
download_url='https://github.com/Runscope/healthcheck/tarball/1.3.1',
12-
packages=find_packages(),
13-
zip_safe=False,
14-
include_package_data=True,
14+
maintainer='Brannon Jones',
15+
maintainer_email='brannonj@gmail.com',
16+
url='https://github.com/brannon/aiohttp-healthcheck',
17+
packages=['aiohttp_healthcheck'],
18+
zip_safe=True,
1519
license='MIT',
1620
platforms='any',
17-
install_requires=[],
18-
test_requires=['flask'],
19-
test_suite='test_healthcheck',
21+
# python_requires='>=3.4.2',
22+
install_requires=install_requires,
2023
classifiers=('Development Status :: 5 - Production/Stable',
2124
'Environment :: Web Environment',
22-
'Framework :: Flask',
23-
'Programming Language :: Python'))
25+
'Programming Language :: Python',
26+
'Programming Language :: Python :: 3',
27+
'Programming Language :: Python :: 3.4',
28+
'Programming Language :: Python :: 3.5'))

0 commit comments

Comments
 (0)