Skip to content

Commit a7cad34

Browse files
authored
Log checks messages in heartbeat view (#86)
* Log checks messages in heartbeat view This implements ``monitoring-heartbeat-error`` of our https://mozilla.github.io/syseng-pod/quality-standards/ * @grahamalama review
1 parent b763d91 commit a7cad34

File tree

6 files changed

+98
-33
lines changed

6 files changed

+98
-33
lines changed

src/dockerflow/django/views.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# This Source Code Form is subject to the terms of the Mozilla Public
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, you can obtain one at http://mozilla.org/MPL/2.0/.
4+
import logging
5+
46
from django.conf import settings
57
from django.core import checks
68
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse
@@ -14,6 +16,9 @@
1416
)
1517

1618

19+
logger = logging.getLogger("dockerflow.django")
20+
21+
1722
def version(request):
1823
"""
1924
Returns the contents of version.json or a 404.
@@ -51,11 +56,18 @@ def heartbeat(request):
5156
level = 0
5257

5358
for check in all_checks:
54-
detail = heartbeat_check_detail(check)
55-
statuses[check.__name__] = detail["status"]
56-
level = max(level, detail["level"])
57-
if detail["level"] > 0:
58-
details[check.__name__] = detail
59+
check_level, check_errors = heartbeat_check_detail(check)
60+
level_text = level_to_text(check_level)
61+
statuses[check.__name__] = level_text
62+
level = max(level, check_level)
63+
if level > 0:
64+
for error in check_errors:
65+
logger.log(error.level, "%s: %s", error.id, error.msg)
66+
details[check.__name__] = {
67+
"status": level_text,
68+
"level": level,
69+
"messages": {e.id: e.msg for e in check_errors},
70+
}
5971

6072
if level < checks.messages.ERROR:
6173
status_code = 200
@@ -75,9 +87,4 @@ def heartbeat_check_detail(check):
7587
errors = check(app_configs=None)
7688
errors = list(filter(lambda e: e.id not in settings.SILENCED_SYSTEM_CHECKS, errors))
7789
level = max([0] + [e.level for e in errors])
78-
79-
return {
80-
"status": level_to_text(level),
81-
"level": level,
82-
"messages": {e.id: e.msg for e in errors},
83-
}
90+
return level, errors

src/dockerflow/flask/app.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,7 @@ def _lbheartbeat_view(self):
306306
def _heartbeat_check_detail(self, check):
307307
errors = list(filter(lambda e: e.id not in self.silenced_checks, check()))
308308
level = max([0] + [e.level for e in errors])
309-
310-
return {
311-
"status": checks.level_to_text(level),
312-
"level": level,
313-
"messages": {e.id: e.msg for e in errors},
314-
}
309+
return level, errors
315310

316311
def _heartbeat_view(self):
317312
"""
@@ -326,11 +321,18 @@ def _heartbeat_view(self):
326321
level = 0
327322

328323
for name, check in self.checks.items():
329-
detail = self._heartbeat_check_detail(check)
330-
statuses[name] = detail["status"]
331-
level = max(level, detail["level"])
332-
if detail["level"] > 0:
333-
details[name] = detail
324+
check_level, check_errors = self._heartbeat_check_detail(check)
325+
level_text = checks.level_to_text(check_level)
326+
statuses[name] = level_text
327+
level = max(level, check_level)
328+
if check_level > 0:
329+
for error in check_errors:
330+
self.logger.log(error.level, "%s: %s", error.id, error.msg)
331+
details[name] = {
332+
"status": level_text,
333+
"level": level,
334+
"messages": {e.id: e.msg for e in check_errors},
335+
}
334336

335337
payload = {
336338
"status": checks.level_to_text(level),

src/dockerflow/sanic/app.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,7 @@ async def _heartbeat_check_detail(self, check):
214214
result = await result
215215
errors = [e for e in result if e.id not in self.silenced_checks]
216216
level = max([0] + [e.level for e in errors])
217-
218-
return {
219-
"status": checks.level_to_text(level),
220-
"level": level,
221-
"messages": {e.id: e.msg for e in errors},
222-
}
217+
return level, errors
223218

224219
async def _heartbeat_view(self, request):
225220
"""
@@ -234,11 +229,18 @@ async def _heartbeat_view(self, request):
234229
level = 0
235230

236231
for name, check in self.checks.items():
237-
detail = await self._heartbeat_check_detail(check)
238-
statuses[name] = detail["status"]
239-
level = max(level, detail["level"])
240-
if detail["level"] > 0:
241-
details[name] = detail
232+
check_level, check_errors = await self._heartbeat_check_detail(check)
233+
level_text = checks.level_to_text(check_level)
234+
statuses[name] = level_text
235+
level = max(level, check_level)
236+
if check_level > 0:
237+
for error in check_errors:
238+
self.logger.log(error.level, "%s: %s", error.id, error.msg)
239+
details[name] = {
240+
"status": level_text,
241+
"level": check_level,
242+
"messages": {e.id: e.msg for e in check_errors},
243+
}
242244

243245
payload = {
244246
"status": checks.level_to_text(level),

tests/django/test_django.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,22 @@ def test_heartbeat(dockerflow_middleware, reset_checks, rf, settings):
6868
assert response.status_code == 500
6969

7070

71+
@pytest.mark.django_db
72+
def test_heartbeat_logging(dockerflow_middleware, reset_checks, rf, settings, caplog):
73+
request = rf.get("/__heartbeat__")
74+
settings.DOCKERFLOW_CHECKS = [
75+
"tests.django.django_checks.warning",
76+
"tests.django.django_checks.error",
77+
]
78+
checks.register()
79+
80+
with caplog.at_level(logging.INFO, logger="dockerflow.django"):
81+
dockerflow_middleware.process_request(request)
82+
logged = [(record.levelname, record.message) for record in caplog.records]
83+
assert ("ERROR", "tests.checks.E001: some error") in logged
84+
assert ("WARNING", "tests.checks.W001: some warning") in logged
85+
86+
7187
@pytest.mark.django_db
7288
def test_lbheartbeat_makes_no_db_queries(dockerflow_middleware, rf):
7389
queries = CaptureQueriesContext(connection)

tests/flask/test_flask.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,25 @@ def warning_check2():
139139
assert "warning-check-two" in defaults
140140

141141

142+
def test_heartbeat_logging(app, dockerflow, caplog):
143+
dockerflow.checks.clear()
144+
145+
@dockerflow.check
146+
def error_check():
147+
return [checks.Error("some error", id="tests.checks.E001")]
148+
149+
@dockerflow.check()
150+
def warning_check():
151+
return [checks.Warning("some warning", id="tests.checks.W001")]
152+
153+
with caplog.at_level(logging.INFO, logger="dockerflow.django"):
154+
app.test_client().get("/__heartbeat__")
155+
156+
logged = [(record.levelname, record.message) for record in caplog.records]
157+
assert ("ERROR", "tests.checks.E001: some error") in logged
158+
assert ("WARNING", "tests.checks.W001: some warning") in logged
159+
160+
142161
def test_lbheartbeat_makes_no_db_queries(dockerflow, app):
143162
with app.app_context():
144163
assert len(get_debug_queries()) == 0

tests/sanic/test_sanic.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,25 @@ async def warning_check2():
162162
assert "warning-check-two" in details
163163

164164

165+
def test_heartbeat_logging(dockerflow, test_client, caplog):
166+
dockerflow.checks.clear()
167+
168+
@dockerflow.check
169+
def error_check():
170+
return [checks.Error("some error", id="tests.checks.E001")]
171+
172+
@dockerflow.check()
173+
def warning_check():
174+
return [checks.Warning("some warning", id="tests.checks.W001")]
175+
176+
with caplog.at_level(logging.INFO, logger="dockerflow.sanic"):
177+
_, response = test_client.get("/__heartbeat__")
178+
179+
logged = [(record.levelname, record.message) for record in caplog.records]
180+
assert ("ERROR", "tests.checks.E001: some error") in logged
181+
assert ("WARNING", "tests.checks.W001: some warning") in logged
182+
183+
165184
def test_redis_check(dockerflow_redis, mocker, test_client):
166185
assert "check_redis_connected" in dockerflow_redis.checks
167186
mocker.patch.object(sanic_redis.core, "from_url", fake_redis)

0 commit comments

Comments
 (0)