Skip to content

Commit

Permalink
Add support for warnings that do not error endpoints
Browse files Browse the repository at this point in the history
Add a new concept of warnings and non-critical services. Both
will not cause the template and JSON endpoint to response with a
500 status code but with a regular 200. This change is backwards
compatible because the existing warnings are treated as errors by
default.

Close #191
  • Loading branch information
codingjoe committed May 24, 2018
1 parent 00a229d commit d609b07
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 60 deletions.
5 changes: 3 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ multi_line_output = 5
line_length = 80
combine_as_imports = true
skip = wsgi.py,docs,.tox,env,.eggs
known_first_party = health_check,health_check_cache,health_check_celery,health_check_celery3,health_check_db,health_check_storage,tests
known_third_party = django
known_first_party = health_check,tests
known_third_party = django,celery,psutil
default_section=THIRDPARTY
not_skip = __init__.py


Expand Down
115 changes: 102 additions & 13 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,104 @@
/.idea/
*.pyc
*.db
/project/
/dist
*.egg-info
.DS_Store
/build
*#
*~
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.cache/
.coverage
.eggs
/docs/_build
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# pytest
.pytest_cache/
34 changes: 0 additions & 34 deletions AUTHORS

This file was deleted.

4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ Writing a health check is quick and easy:
from health_check.backends import BaseHealthCheckBackend
class MyHealthCheckBackend(BaseHealthCheckBackend):
#: The status endpoints will respond with a 200 status code
#: even if the check errors.
critical_service = False
def check_status(self):
# The test code goes here.
# You can use `self.add_error` or
Expand Down
7 changes: 7 additions & 0 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ Settings

Settings can be configured via the ``HEALTH_CHECK`` dictionary.

.. data:: WARNINGS_AS_ERRORS

Treats :class:`ServiceWarning` as errors, meaning they will case the views
to respond with a 500 status code. Default is ``True``. If set to
``False`` warnings will be displayed in the template on in the JSON
response but the status code will remain a 200.

Security
--------

Expand Down
8 changes: 8 additions & 0 deletions health_check/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@


class BaseHealthCheckBackend:
critical_service = True
"""
Define if service is critical to the operation of the site.
If set to ``False`` service failures will cause a 500 response code on the
health check endpoint.
"""

def __init__(self):
self.errors = []

Expand Down
6 changes: 6 additions & 0 deletions health_check/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.conf import settings

HEALTH_CHECK = getattr(settings, 'HEALTH_CHECK', {})
HEALTH_CHECK.setdefault('DISK_USAGE_MAX', 90)
HEALTH_CHECK.setdefault('MEMORY_MIN', 100)
HEALTH_CHECK.setdefault('WARNINGS_AS_ERRORS', True)
12 changes: 4 additions & 8 deletions health_check/contrib/psutil/backends.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import locale
import socket

from django.conf import settings

import psutil

from health_check.backends import BaseHealthCheckBackend
from health_check.conf import HEALTH_CHECK
from health_check.exceptions import (
ServiceReturnedUnexpectedResult, ServiceWarning
)

host = socket.gethostname()

if hasattr(settings, 'HEALTH_CHECK'):
DISK_USAGE_MAX = settings.HEALTH_CHECK.get('DISK_USAGE_MAX', 90) # in %
MEMORY_MIN = settings.HEALTH_CHECK.get('MEMORY_MIN', 100) # in MB
else:
DISK_USAGE_MAX = 90 # in %
MEMORY_MIN = 100 # in MB
DISK_USAGE_MAX = HEALTH_CHECK['DISK_USAGE_MAX']
MEMORY_MIN = HEALTH_CHECK['MEMORY_MIN']


class DiskUsage(BaseHealthCheckBackend):
Expand Down
7 changes: 7 additions & 0 deletions health_check/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ def __str__(self):


class ServiceWarning(HealthCheckException):
"""
Warning of service misbehavior.
If the ``HEALTH_CHECK['WARNINGS_AS_ERRORS']`` is set to ``False``,
these exceptions will not case a 500 status response.
"""

message_type = _("warning")


Expand Down
16 changes: 13 additions & 3 deletions health_check/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import copy
from concurrent.futures import ThreadPoolExecutor

from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.cache import never_cache
from django.views.generic import TemplateView

from health_check.exceptions import ServiceWarning
from health_check.plugins import plugin_dir

WARNINGS_AS_ERRORS = getattr(settings, 'HEALTH_CHECK_WARNINGS_AS_ERRORS', True)


class MainView(TemplateView):
template_name = 'health_check/index.html'
Expand All @@ -23,14 +27,20 @@ def get(self, request, *args, **kwargs):
def _run(plugin):
plugin.run_check()
try:
return plugin.errors
return plugin.errors, plugin
finally:
from django.db import connection
connection.close()

with ThreadPoolExecutor(max_workers=len(plugins) or 1) as executor:
for ers in executor.map(_run, plugins):
errors.extend(ers)
for plugin_errors, plugin in executor.map(_run, plugins):
if plugin.critical_service:
if not WARNINGS_AS_ERRORS:
plugin_errors = (
e for e in plugin_errors
if not isinstance(e, ServiceWarning)
)
errors.extend(plugin_errors)

status_code = 500 if errors else 200

Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ keywords =
packages =
health_check

[pbr]
skip_authors = true
skip_changelog = true

[tool:pytest]
norecursedirs=venv env .eggs
DJANGO_SETTINGS_MODULE=tests.testapp.settings
Expand Down

0 comments on commit d609b07

Please sign in to comment.