django-health-check 3.23.2__py3-none-any.whl → 4.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/METADATA +9 -5
- django_health_check-4.0rc1.dist-info/RECORD +20 -0
- health_check/__init__.py +5 -14
- health_check/_version.py +3 -3
- health_check/base.py +93 -0
- health_check/checks.py +329 -0
- health_check/contrib/celery.py +70 -0
- health_check/contrib/kafka.py +69 -0
- health_check/contrib/rabbitmq.py +43 -0
- health_check/contrib/redis.py +63 -0
- health_check/contrib/rss.py +113 -0
- health_check/exceptions.py +6 -9
- health_check/management/commands/health_check.py +20 -66
- health_check/templates/health_check/index.html +61 -43
- health_check/views.py +176 -75
- django_health_check-3.23.2.dist-info/RECORD +0 -63
- health_check/backends.py +0 -101
- health_check/cache/__init__.py +0 -0
- health_check/cache/apps.py +0 -14
- health_check/cache/backends.py +0 -50
- health_check/conf.py +0 -8
- health_check/contrib/celery/__init__.py +0 -3
- health_check/contrib/celery/apps.py +0 -31
- health_check/contrib/celery/backends.py +0 -46
- health_check/contrib/celery/tasks.py +0 -6
- health_check/contrib/celery_ping/__init__.py +0 -0
- health_check/contrib/celery_ping/apps.py +0 -19
- health_check/contrib/celery_ping/backends.py +0 -74
- health_check/contrib/db_heartbeat/__init__.py +0 -0
- health_check/contrib/db_heartbeat/apps.py +0 -19
- health_check/contrib/db_heartbeat/backends.py +0 -44
- health_check/contrib/mail/__init__.py +0 -0
- health_check/contrib/mail/apps.py +0 -19
- health_check/contrib/mail/backends.py +0 -61
- health_check/contrib/migrations/__init__.py +0 -0
- health_check/contrib/migrations/apps.py +0 -19
- health_check/contrib/migrations/backends.py +0 -31
- health_check/contrib/psutil/__init__.py +0 -0
- health_check/contrib/psutil/apps.py +0 -36
- health_check/contrib/psutil/backends.py +0 -63
- health_check/contrib/rabbitmq/__init__.py +0 -3
- health_check/contrib/rabbitmq/apps.py +0 -19
- health_check/contrib/rabbitmq/backends.py +0 -57
- health_check/contrib/redis/__init__.py +0 -3
- health_check/contrib/redis/apps.py +0 -19
- health_check/contrib/redis/backends.py +0 -75
- health_check/contrib/s3boto3_storage/__init__.py +0 -0
- health_check/contrib/s3boto3_storage/apps.py +0 -19
- health_check/contrib/s3boto3_storage/backends.py +0 -32
- health_check/contrib/s3boto_storage/__init__.py +0 -0
- health_check/contrib/s3boto_storage/apps.py +0 -20
- health_check/contrib/s3boto_storage/backends.py +0 -27
- health_check/db/__init__.py +0 -0
- health_check/db/apps.py +0 -20
- health_check/db/backends.py +0 -23
- health_check/db/migrations/0001_initial.py +0 -34
- health_check/db/migrations/0002_alter_testmodel_options.py +0 -32
- health_check/db/migrations/__init__.py +0 -0
- health_check/db/models.py +0 -9
- health_check/deprecation.py +0 -35
- health_check/mixins.py +0 -86
- health_check/plugins.py +0 -25
- health_check/storage/__init__.py +0 -0
- health_check/storage/apps.py +0 -12
- health_check/storage/backends.py +0 -73
- health_check/urls.py +0 -18
- {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/WHEEL +0 -0
- {django_health_check-3.23.2.dist-info → django_health_check-4.0rc1.dist-info}/licenses/LICENSE +0 -0
health_check/views.py
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import re
|
|
2
|
-
import
|
|
3
|
-
from functools import cached_property
|
|
3
|
+
import typing
|
|
4
4
|
|
|
5
5
|
from django.db import transaction
|
|
6
6
|
from django.http import HttpResponse, JsonResponse
|
|
7
|
+
from django.utils import timezone
|
|
8
|
+
from django.utils.cache import patch_vary_headers
|
|
7
9
|
from django.utils.decorators import method_decorator
|
|
10
|
+
from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
|
|
8
11
|
from django.utils.module_loading import import_string
|
|
9
12
|
from django.views.decorators.cache import never_cache
|
|
10
13
|
from django.views.generic import TemplateView
|
|
11
14
|
|
|
12
|
-
from health_check.
|
|
13
|
-
from health_check.mixins import CheckMixin
|
|
15
|
+
from health_check.base import HealthCheck
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class MediaType:
|
|
17
19
|
"""
|
|
18
20
|
Sortable object representing HTTP's accept header.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
|
|
21
23
|
"""
|
|
22
24
|
|
|
23
25
|
pattern = re.compile(
|
|
@@ -54,20 +56,21 @@ class MediaType:
|
|
|
54
56
|
|
|
55
57
|
@classmethod
|
|
56
58
|
def from_string(cls, value):
|
|
57
|
-
"""Return single instance parsed from given
|
|
59
|
+
"""Return single instance parsed from the given Accept-header string."""
|
|
58
60
|
match = cls.pattern.search(value)
|
|
59
61
|
if match is None:
|
|
60
62
|
raise ValueError(f'"{value}" is not a valid media type')
|
|
61
|
-
|
|
62
|
-
return cls(match.group("mime_type"), float(match.group("weight") or 1))
|
|
63
|
-
except ValueError:
|
|
64
|
-
return cls(value)
|
|
63
|
+
return cls(match.group("mime_type"), float(match.group("weight") or 1))
|
|
65
64
|
|
|
66
65
|
@classmethod
|
|
67
66
|
def parse_header(cls, value="*/*"):
|
|
68
67
|
"""Parse HTTP accept header and return instances sorted by weight."""
|
|
69
68
|
yield from sorted(
|
|
70
|
-
(
|
|
69
|
+
(
|
|
70
|
+
cls.from_string(token.strip())
|
|
71
|
+
for token in value.split(",")
|
|
72
|
+
if token.strip()
|
|
73
|
+
),
|
|
71
74
|
reverse=True,
|
|
72
75
|
)
|
|
73
76
|
|
|
@@ -84,103 +87,201 @@ class MediaType:
|
|
|
84
87
|
return self.weight.__lt__(other.weight)
|
|
85
88
|
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"""Deprecated: Use HealthCheckView instead."""
|
|
90
|
+
class HealthCheckView(TemplateView):
|
|
91
|
+
"""Perform health checks and return results in various formats."""
|
|
90
92
|
|
|
91
93
|
template_name = "health_check/index.html"
|
|
94
|
+
feed_author = "Django Health Check"
|
|
95
|
+
|
|
96
|
+
checks: typing.Iterable[
|
|
97
|
+
type[HealthCheck] | str | tuple[type[HealthCheck] | str, dict[str, typing.Any]]
|
|
98
|
+
] = (
|
|
99
|
+
"health_check.checks.Cache",
|
|
100
|
+
"health_check.checks.Database",
|
|
101
|
+
"health_check.checks.Disk",
|
|
102
|
+
"health_check.checks.DNS",
|
|
103
|
+
"health_check.checks.Mail",
|
|
104
|
+
"health_check.checks.Memory",
|
|
105
|
+
"health_check.checks.Storage",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@method_decorator(transaction.non_atomic_requests)
|
|
109
|
+
async def dispatch(self, request, *args, **kwargs):
|
|
110
|
+
response = await super().dispatch(request, *args, **kwargs)
|
|
111
|
+
patch_vary_headers(response, ["Accept"])
|
|
112
|
+
return response
|
|
92
113
|
|
|
93
114
|
@method_decorator(never_cache)
|
|
94
|
-
def get(self, request, *args, **kwargs):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
async def get(self, request, *args, **kwargs):
|
|
116
|
+
self.results = await asyncio.gather(
|
|
117
|
+
*(check.get_result() for check in self.get_checks())
|
|
118
|
+
)
|
|
119
|
+
has_errors = any(result.error for result in self.results)
|
|
120
|
+
status_code = 500 if has_errors else 200
|
|
98
121
|
format_override = request.GET.get("format")
|
|
99
122
|
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
match format_override:
|
|
124
|
+
case "json":
|
|
125
|
+
return self.render_to_response_json(status_code)
|
|
126
|
+
case "text":
|
|
127
|
+
return self.render_to_response_text(status_code)
|
|
128
|
+
case "atom":
|
|
129
|
+
return self.render_to_response_atom()
|
|
130
|
+
case "rss":
|
|
131
|
+
return self.render_to_response_rss()
|
|
132
|
+
case "openmetrics":
|
|
133
|
+
return self.render_to_response_openmetrics()
|
|
102
134
|
|
|
103
135
|
accept_header = request.headers.get("accept", "*/*")
|
|
104
136
|
for media in MediaType.parse_header(accept_header):
|
|
105
|
-
|
|
106
|
-
"text/
|
|
107
|
-
|
|
108
|
-
"text/*"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
match media.mime_type:
|
|
138
|
+
case "text/plain":
|
|
139
|
+
return self.render_to_response_text(status_code)
|
|
140
|
+
case "text/html" | "application/xhtml+xml" | "text/*" | "*/*":
|
|
141
|
+
context = self.get_context_data(**kwargs)
|
|
142
|
+
return self.render_to_response(context, status=status_code)
|
|
143
|
+
case "application/json" | "application/*":
|
|
144
|
+
return self.render_to_response_json(status_code)
|
|
145
|
+
case "application/atom+xml":
|
|
146
|
+
return self.render_to_response_atom()
|
|
147
|
+
case "application/rss+xml":
|
|
148
|
+
return self.render_to_response_rss()
|
|
149
|
+
case "application/openmetrics-text":
|
|
150
|
+
return self.render_to_response_openmetrics()
|
|
115
151
|
return HttpResponse(
|
|
116
|
-
"Not Acceptable: Supported content types: text/html, application/json",
|
|
152
|
+
"Not Acceptable: Supported content types: text/plain, text/html, application/json, application/atom+xml, application/rss+xml, application/openmetrics-text",
|
|
117
153
|
status=406,
|
|
118
154
|
content_type="text/plain",
|
|
119
155
|
)
|
|
120
156
|
|
|
121
157
|
def get_context_data(self, **kwargs):
|
|
122
|
-
subset = kwargs.get("subset")
|
|
123
158
|
return {
|
|
124
159
|
**super().get_context_data(**kwargs),
|
|
125
|
-
"
|
|
126
|
-
"errors": any(
|
|
160
|
+
"results": self.results,
|
|
161
|
+
"errors": any(result.error for result in self.results),
|
|
127
162
|
}
|
|
128
163
|
|
|
129
|
-
def render_to_response_json(self,
|
|
164
|
+
def render_to_response_json(self, status):
|
|
165
|
+
"""Return JSON response with health check results."""
|
|
130
166
|
return JsonResponse(
|
|
131
|
-
{
|
|
167
|
+
{
|
|
168
|
+
repr(result.check): "OK" if not result.error else str(result.error)
|
|
169
|
+
for result in self.results
|
|
170
|
+
},
|
|
132
171
|
status=status,
|
|
133
172
|
)
|
|
134
173
|
|
|
174
|
+
def render_to_response_text(self, status):
|
|
175
|
+
"""Return plain text response with health check results."""
|
|
176
|
+
lines = (
|
|
177
|
+
f"{repr(result.check)}: {'OK' if not result.error else str(result.error)}"
|
|
178
|
+
for result in self.results
|
|
179
|
+
)
|
|
180
|
+
return HttpResponse(
|
|
181
|
+
"\n".join(lines) + "\n",
|
|
182
|
+
content_type="text/plain; charset=utf-8",
|
|
183
|
+
status=status,
|
|
184
|
+
)
|
|
135
185
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
class MainView(_MainView):
|
|
140
|
-
"""Deprecated: Use HealthCheckView instead."""
|
|
186
|
+
def render_to_response_atom(self):
|
|
187
|
+
"""Return Atom feed response with health check results."""
|
|
188
|
+
return self._render_feed(Atom1Feed)
|
|
141
189
|
|
|
142
|
-
|
|
190
|
+
def render_to_response_rss(self):
|
|
191
|
+
"""Return RSS 2.0 feed response with health check results."""
|
|
192
|
+
return self._render_feed(Rss201rev2Feed)
|
|
143
193
|
|
|
194
|
+
def _escape_openmetrics_label_value(self, value):
|
|
195
|
+
r"""
|
|
196
|
+
Escape label value according to OpenMetrics specification.
|
|
144
197
|
|
|
145
|
-
|
|
146
|
-
|
|
198
|
+
Escapes backslashes, double quotes, and newlines as required by the spec:
|
|
199
|
+
- Backslash (\) -> \\
|
|
200
|
+
- Double quote (") -> \"
|
|
201
|
+
- Line feed (\n) -> \n
|
|
202
|
+
"""
|
|
203
|
+
return value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
147
204
|
|
|
148
|
-
|
|
205
|
+
def render_to_response_openmetrics(self):
|
|
206
|
+
"""Return OpenMetrics response with health check results."""
|
|
207
|
+
lines = [
|
|
208
|
+
"# HELP django_health_check_status Health check status (1 = healthy, 0 = unhealthy)",
|
|
209
|
+
"# TYPE django_health_check_status gauge",
|
|
210
|
+
]
|
|
211
|
+
has_errors: bool = False
|
|
149
212
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
213
|
+
# Add status metrics for each check
|
|
214
|
+
for result in self.results:
|
|
215
|
+
safe_label = self._escape_openmetrics_label_value(repr(result.check))
|
|
216
|
+
has_errors |= bool(result.error)
|
|
217
|
+
lines.append(
|
|
218
|
+
f'django_health_check_status{{check="{safe_label}"}} {not result.error:d}'
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Add response time metrics
|
|
222
|
+
lines += [
|
|
223
|
+
"",
|
|
224
|
+
"# HELP django_health_check_response_time_seconds Health check response time in seconds",
|
|
225
|
+
"# TYPE django_health_check_response_time_seconds gauge",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
for result in self.results:
|
|
229
|
+
safe_label = self._escape_openmetrics_label_value(repr(result.check))
|
|
230
|
+
lines.append(
|
|
231
|
+
f'django_health_check_response_time_seconds{{check="{safe_label}"}} {result.time_taken:.6f}'
|
|
157
232
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
233
|
+
|
|
234
|
+
# Add overall health status
|
|
235
|
+
lines += [
|
|
236
|
+
"",
|
|
237
|
+
"# HELP django_health_check_overall_status Overall health check status (1 = all healthy, 0 = at least one unhealthy)",
|
|
238
|
+
"# TYPE django_health_check_overall_status gauge",
|
|
239
|
+
f"django_health_check_overall_status {not has_errors:d}",
|
|
240
|
+
"# EOF",
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
return HttpResponse(
|
|
244
|
+
"\n".join(lines) + "\n",
|
|
245
|
+
content_type="application/openmetrics-text; version=1.0.0; charset=utf-8",
|
|
246
|
+
status=200, # Prometheus expects 200 even if checks fail
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def _render_feed(self, feed_class):
|
|
250
|
+
"""Generate RSS or Atom feed with health check results."""
|
|
251
|
+
feed = feed_class(
|
|
252
|
+
title="Health Check Status",
|
|
253
|
+
link=self.request.build_absolute_uri(),
|
|
254
|
+
description="Current status of system health checks",
|
|
255
|
+
feed_url=self.request.build_absolute_uri(),
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
for result in self.results:
|
|
259
|
+
feed.add_item(
|
|
260
|
+
title=repr(result.check),
|
|
261
|
+
link=self.request.build_absolute_uri(),
|
|
262
|
+
description=f"{result.check!r}\nResponse time: {result.time_taken:.3f}s",
|
|
263
|
+
pubdate=timezone.now(),
|
|
264
|
+
updateddate=timezone.now(),
|
|
265
|
+
author_name=self.feed_author,
|
|
266
|
+
categories=["error", "unhealthy"] if result.error else ["healthy"],
|
|
163
267
|
)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
268
|
+
|
|
269
|
+
response = HttpResponse(
|
|
270
|
+
feed.writeString("utf-8"),
|
|
271
|
+
content_type=feed.content_type,
|
|
272
|
+
status=200, # Feed readers expect 200 even if checks fail
|
|
273
|
+
)
|
|
274
|
+
return response
|
|
275
|
+
|
|
276
|
+
def get_checks(
|
|
277
|
+
self,
|
|
278
|
+
) -> typing.Generator[HealthCheck, None, None]:
|
|
279
|
+
"""Yield instantiated health check callables."""
|
|
280
|
+
for check in self.checks:
|
|
175
281
|
try:
|
|
176
282
|
check, options = check
|
|
177
|
-
except ValueError:
|
|
283
|
+
except (ValueError, TypeError):
|
|
178
284
|
options = {}
|
|
179
285
|
if isinstance(check, str):
|
|
180
286
|
check = import_string(check)
|
|
181
|
-
|
|
182
|
-
yield repr(plugin_instance), plugin_instance
|
|
183
|
-
|
|
184
|
-
@cached_property
|
|
185
|
-
def plugins(self):
|
|
186
|
-
return dict(self.get_plugins())
|
|
287
|
+
yield check(**options)
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
health_check/__init__.py,sha256=vLH1k9cjhuLe_swu-qMxrz1_7fFJA179-UWNcsdA8QA,868
|
|
2
|
-
health_check/_version.py,sha256=mxLf1sW9K0QQHtOQxElIqSODHiT_z1DtMeutHlOPwAs,714
|
|
3
|
-
health_check/backends.py,sha256=Q4B8yRusBZz8mOz8jNZmqJWQ1Fu6YawYiLNFkw5sUmA,3414
|
|
4
|
-
health_check/conf.py,sha256=l3Utl8zLLT42TKUnBFNLnD_i-wYNKmxPXVVyIucGUXc,319
|
|
5
|
-
health_check/deprecation.py,sha256=uYGwPFW8lX1xQ33x0VRkoPFoyoLcrMt6DmpX3RHLxj4,1127
|
|
6
|
-
health_check/exceptions.py,sha256=L9QiCWYpuz3H05an3wDhz_coKeulHvTYotIT4gjKMXA,743
|
|
7
|
-
health_check/mixins.py,sha256=pm1JB1qwU5oBklpR8czPIdPru0JrqDDEnmsiYJGWtms,2924
|
|
8
|
-
health_check/plugins.py,sha256=Y7TaRm5ILhvLUKmoF33KrIYz6ubA0Vd5Mjf7IOmvwO8,623
|
|
9
|
-
health_check/urls.py,sha256=2AYTZzWO-BBILcsSBD2gTuRCstf9YTp0NZ3k8FfY1RM,627
|
|
10
|
-
health_check/views.py,sha256=LJPanE4wZctDiwuqSn6zMTH_JsMqYPSVbLag1yBFbVQ,6543
|
|
11
|
-
health_check/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
health_check/cache/apps.py,sha256=aJKPag3O1oLTdRDlpxqI8l4vE_c-iA8AReROsnlckio,349
|
|
13
|
-
health_check/cache/backends.py,sha256=a2nhNOE_wFmZNMBi1G_y8ByS_J1dm_R2X-8JdknB-kQ,1994
|
|
14
|
-
health_check/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
health_check/contrib/celery/__init__.py,sha256=xdK73-N2QJ6N9iom2s4Zqhl7nc4_MQrhojjHdD7p7Y8,85
|
|
16
|
-
health_check/contrib/celery/apps.py,sha256=0hV0CAUeZMnjHmu0QpmDhQnS48NI95iD1lssly4sAa0,1409
|
|
17
|
-
health_check/contrib/celery/backends.py,sha256=iGtaNQq6ZDcXC6UUB8knvNaZS3IE5LkcwPJHnG7lfoc,2221
|
|
18
|
-
health_check/contrib/celery/tasks.py,sha256=UWmgZa4oGsXuo_udRVRBoCAOVFibGHjqevw3s8rOnfQ,99
|
|
19
|
-
health_check/contrib/celery_ping/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
health_check/contrib/celery_ping/apps.py,sha256=NOg01PnKYOCBSUCqSW62VbLwmeO99h7X_Mf8zjBmkbc,754
|
|
21
|
-
health_check/contrib/celery_ping/backends.py,sha256=T8EC3EpfsuzT1HNvyk8ZFJAIMgvVlIwB7TGOsXvcCpQ,2522
|
|
22
|
-
health_check/contrib/db_heartbeat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
-
health_check/contrib/db_heartbeat/apps.py,sha256=_zc7ZYis4U6JpObmQBoFrEZHgC1UFcvO415l0c0cUXw,752
|
|
24
|
-
health_check/contrib/db_heartbeat/backends.py,sha256=vfdGzdtCW2wUWwcXS4VNFdm4LopLi4UpylMhUpApA0s,1336
|
|
25
|
-
health_check/contrib/mail/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
health_check/contrib/mail/apps.py,sha256=UGwk9BM6oIVingQmr--Joe4ujNPtVsxe6nzQ-fDu9c0,683
|
|
27
|
-
health_check/contrib/mail/backends.py,sha256=UdbRPyy-1mZSgSkjMopnvpMDZrJT1xybvSJI9Br3lS4,1940
|
|
28
|
-
health_check/contrib/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
health_check/contrib/migrations/apps.py,sha256=oSvxHRPVU6JT48gdbASSI8G-zUUclAQZZVSdJKM_2oM,707
|
|
30
|
-
health_check/contrib/migrations/backends.py,sha256=-qx9C4oxqv5ITAnoeQ-8bGxmxZYtbF7RE9y4IMoFIGM,1432
|
|
31
|
-
health_check/contrib/psutil/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
health_check/contrib/psutil/apps.py,sha256=VjR4sw6kY4y1GmWohZSRxl8Em1JrdL3ApgPcZ8hGnE0,1350
|
|
33
|
-
health_check/contrib/psutil/backends.py,sha256=-5yv3pM8UvrfQgr9PT4dyVmSRSg6j322-ZW-nm3FHho,2383
|
|
34
|
-
health_check/contrib/rabbitmq/__init__.py,sha256=PlctAhXwp9DjxPo7DGpWvD1DBTfvWf807t-mhfMoNFU,78
|
|
35
|
-
health_check/contrib/rabbitmq/apps.py,sha256=i-1Uo3W6Ofc0_StFABfrxysh_anWVP-mcNKUHqZ6_c8,721
|
|
36
|
-
health_check/contrib/rabbitmq/backends.py,sha256=22zNDsX-Ie-a5Rwk4naIpGB3184SV2OaGTXzwBzWbU0,1836
|
|
37
|
-
health_check/contrib/redis/__init__.py,sha256=TzSbuXNst7itI8mbrtsCNJXJP40ZpX-r2x6mxMNTgYE,69
|
|
38
|
-
health_check/contrib/redis/apps.py,sha256=1bhVAy5iKZer8IwG4WXZYeYUSl7AMTtNPRRhR5ugrLs,700
|
|
39
|
-
health_check/contrib/redis/backends.py,sha256=xQ73bnGlOGtvnaNiEOz4bhqxXsD-Qn2y6aVwoiQkWCg,2788
|
|
40
|
-
health_check/contrib/s3boto3_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
health_check/contrib/s3boto3_storage/apps.py,sha256=1-7X_v7d6HeKD5MBxyDvrh5vdopsvmnJNjToiFjeDx4,732
|
|
42
|
-
health_check/contrib/s3boto3_storage/backends.py,sha256=0WrNNNJiqDVn5_GHQZm6hlEBvSxhWTqWSWUtmVHkzzo,1331
|
|
43
|
-
health_check/contrib/s3boto_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
-
health_check/contrib/s3boto_storage/apps.py,sha256=5pQWKqhBgb_IYpJEJ5pej8bYRcermiGecZUq2aZeRvk,569
|
|
45
|
-
health_check/contrib/s3boto_storage/backends.py,sha256=jfvlK2GhJBGEtdqFzNnxsp9W11KPpoRlYzQVBweoncQ,1074
|
|
46
|
-
health_check/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
-
health_check/db/apps.py,sha256=Xmh9FbjROw1qYQbMutb3o4c0XG64KHxHLpaWzq4W31s,708
|
|
48
|
-
health_check/db/backends.py,sha256=c-rNcEoqq3X8r08sqJTHqdXZpxba-wloHAeKlI82Bxc,1028
|
|
49
|
-
health_check/db/models.py,sha256=kMA0uoAQBwqHLpfVt4gMG8-h3BZIGcFRSAwD9oATBWU,204
|
|
50
|
-
health_check/db/migrations/0001_initial.py,sha256=a5sN_dfDXju_Kr32uBqzPvd8B9Ysl1bV8WL5fQwsRZI,826
|
|
51
|
-
health_check/db/migrations/0002_alter_testmodel_options.py,sha256=yLcp1aAdJ_2Q-F7MfCVVf2nTojIijnztdulOK1QmpUQ,984
|
|
52
|
-
health_check/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
health_check/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
54
|
-
health_check/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
|
-
health_check/management/commands/health_check.py,sha256=EWBAhMOq9clravsDwHqCiSt4JQJIRejcg5f2SvIsSeI,3864
|
|
56
|
-
health_check/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
-
health_check/storage/apps.py,sha256=f6958Deq62DXoe8myL57rnKSkKkPQf2FLM9iJkUdx-U,293
|
|
58
|
-
health_check/storage/backends.py,sha256=pkj64HcVnvm9pvgWFhL81IDejTOH8-pLSFwJQ_90jbs,2636
|
|
59
|
-
health_check/templates/health_check/index.html,sha256=pOC7GW_r0gDQ5Qz5aJacNFI22N52kbiOzh4aZb9_V-Q,4045
|
|
60
|
-
django_health_check-3.23.2.dist-info/licenses/LICENSE,sha256=19Rs8FInCokFQuq03cab_KHwpeyF5pt-lTp7pfJX1iE,1101
|
|
61
|
-
django_health_check-3.23.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
62
|
-
django_health_check-3.23.2.dist-info/METADATA,sha256=BjOQs1iaWA_kROQ0sJeZuItjeBeWrRX3yX_xWaQn0lM,3554
|
|
63
|
-
django_health_check-3.23.2.dist-info/RECORD,,
|
health_check/backends.py
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import logging
|
|
3
|
-
import warnings
|
|
4
|
-
from timeit import default_timer as timer
|
|
5
|
-
|
|
6
|
-
from health_check.exceptions import HealthCheckException
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger("health-check")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclasses.dataclass()
|
|
12
|
-
class HealthCheck:
|
|
13
|
-
"""
|
|
14
|
-
Base class for all health check backends.
|
|
15
|
-
|
|
16
|
-
To create your own health check backend, subclass this class
|
|
17
|
-
and implement the ``check_status`` method.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
critical_service: bool = dataclasses.field(init=False, default=True, repr=False)
|
|
21
|
-
errors: list[HealthCheckException] = dataclasses.field(init=False, default_factory=list, repr=False)
|
|
22
|
-
|
|
23
|
-
def check_status(self):
|
|
24
|
-
"""
|
|
25
|
-
Execute the health check logic.
|
|
26
|
-
|
|
27
|
-
This method should be overridden by subclasses to implement
|
|
28
|
-
specific health check logic. If the check fails, it should
|
|
29
|
-
call `self.add_error` with an appropriate error message or
|
|
30
|
-
raise a `HealthCheckException`.
|
|
31
|
-
|
|
32
|
-
Raises:
|
|
33
|
-
HealthCheckException: If the health check fails.
|
|
34
|
-
ServiceWarning: If the health check encounters a warning condition.
|
|
35
|
-
|
|
36
|
-
"""
|
|
37
|
-
raise NotImplementedError
|
|
38
|
-
|
|
39
|
-
def run_check(self):
|
|
40
|
-
start = timer()
|
|
41
|
-
self.errors = []
|
|
42
|
-
try:
|
|
43
|
-
self.check_status()
|
|
44
|
-
except HealthCheckException as e:
|
|
45
|
-
self.add_error(e, e)
|
|
46
|
-
except BaseException:
|
|
47
|
-
logger.exception("Unexpected Error!")
|
|
48
|
-
raise
|
|
49
|
-
finally:
|
|
50
|
-
self.time_taken = timer() - start
|
|
51
|
-
|
|
52
|
-
def add_error(self, error, cause=None):
|
|
53
|
-
if isinstance(error, HealthCheckException):
|
|
54
|
-
pass
|
|
55
|
-
elif isinstance(error, str):
|
|
56
|
-
msg = error
|
|
57
|
-
error = HealthCheckException(msg)
|
|
58
|
-
else:
|
|
59
|
-
msg = "unknown error"
|
|
60
|
-
error = HealthCheckException(msg)
|
|
61
|
-
if isinstance(cause, BaseException):
|
|
62
|
-
logger.exception(str(error))
|
|
63
|
-
else:
|
|
64
|
-
logger.error(str(error))
|
|
65
|
-
self.errors.append(error)
|
|
66
|
-
|
|
67
|
-
def pretty_status(self):
|
|
68
|
-
if self.errors:
|
|
69
|
-
return "\n".join(str(e) for e in self.errors)
|
|
70
|
-
return "OK"
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def status(self):
|
|
74
|
-
return int(not self.errors)
|
|
75
|
-
|
|
76
|
-
def __repr__(self):
|
|
77
|
-
if hasattr(self, "identifier"):
|
|
78
|
-
warnings.warn(
|
|
79
|
-
"`identifier()` method is deprecated: implement `__repr__()` instead to return a stable identifier. Action: update your backend class to implement `__repr__` and remove `identifier()`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
|
|
80
|
-
DeprecationWarning,
|
|
81
|
-
)
|
|
82
|
-
return self.identifier()
|
|
83
|
-
|
|
84
|
-
return self.__class__.__name__
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class BaseHealthCheck(HealthCheck):
|
|
88
|
-
"""
|
|
89
|
-
Deprecated base class for health check backends.
|
|
90
|
-
|
|
91
|
-
This class is maintained for backward compatibility.
|
|
92
|
-
New health check backends should inherit from `HealthCheck` instead.
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
def __init_subclass__(cls, **kwargs):
|
|
96
|
-
warnings.warn(
|
|
97
|
-
f"{cls.__name__} inherits from deprecated `BaseHealthCheck`: inherit from `HealthCheck` instead. Action: update subclass to inherit from `health_check.backends.HealthCheck`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
|
|
98
|
-
DeprecationWarning,
|
|
99
|
-
stacklevel=2,
|
|
100
|
-
)
|
|
101
|
-
super().__init_subclass__(**kwargs)
|
health_check/cache/__init__.py
DELETED
|
File without changes
|
health_check/cache/apps.py
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from django.apps import AppConfig
|
|
2
|
-
from django.conf import settings
|
|
3
|
-
|
|
4
|
-
from health_check.plugins import plugin_dir
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class HealthCheckConfig(AppConfig):
|
|
8
|
-
name = "health_check.cache"
|
|
9
|
-
|
|
10
|
-
def ready(self):
|
|
11
|
-
from .backends import CacheBackend
|
|
12
|
-
|
|
13
|
-
for backend in settings.CACHES:
|
|
14
|
-
plugin_dir.register(CacheBackend, alias=backend)
|
health_check/cache/backends.py
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import dataclasses
|
|
2
|
-
import datetime
|
|
3
|
-
|
|
4
|
-
from django.conf import settings
|
|
5
|
-
from django.core.cache import CacheKeyWarning, caches
|
|
6
|
-
|
|
7
|
-
from health_check.backends import HealthCheck
|
|
8
|
-
from health_check.exceptions import ServiceReturnedUnexpectedResult, ServiceUnavailable
|
|
9
|
-
|
|
10
|
-
try:
|
|
11
|
-
# Exceptions thrown by Redis do not subclass builtin exceptions like ConnectionError.
|
|
12
|
-
# Additionally, not only connection errors (ConnectionError -> RedisError) can be raised,
|
|
13
|
-
# but also errors for time-outs (TimeoutError -> RedisError)
|
|
14
|
-
# and if the backend is read-only (ReadOnlyError -> ResponseError -> RedisError).
|
|
15
|
-
# Since we know what we are trying to do here, we are not picky and catch the global exception RedisError.
|
|
16
|
-
from redis.exceptions import RedisError
|
|
17
|
-
except ModuleNotFoundError:
|
|
18
|
-
# In case Redis is not installed and another cache backend is used.
|
|
19
|
-
class RedisError(Exception):
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@dataclasses.dataclass
|
|
24
|
-
class CacheBackend(HealthCheck):
|
|
25
|
-
"""
|
|
26
|
-
Check that the cache backend is able to set and get a value.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
alias: The cache alias to test against.
|
|
30
|
-
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
alias: str = dataclasses.field(default="default")
|
|
34
|
-
cache_key: str = dataclasses.field(
|
|
35
|
-
default=getattr(settings, "HEALTHCHECK_CACHE_KEY", "djangohealthcheck_test"), repr=False
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def check_status(self):
|
|
39
|
-
cache = caches[self.alias]
|
|
40
|
-
ts = datetime.datetime.now().timestamp()
|
|
41
|
-
try:
|
|
42
|
-
cache.set(self.cache_key, f"itworks-{ts}")
|
|
43
|
-
if not cache.get(self.cache_key) == f"itworks-{ts}":
|
|
44
|
-
raise ServiceUnavailable(f"Cache key {self.cache_key} does not match")
|
|
45
|
-
except CacheKeyWarning as e:
|
|
46
|
-
self.add_error(ServiceReturnedUnexpectedResult("Cache key warning"), e)
|
|
47
|
-
except ValueError as e:
|
|
48
|
-
self.add_error(ServiceReturnedUnexpectedResult("ValueError"), e)
|
|
49
|
-
except (ConnectionError, RedisError) as e:
|
|
50
|
-
self.add_error(ServiceReturnedUnexpectedResult("Connection Error"), e)
|
health_check/conf.py
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
from django.conf import settings
|
|
2
|
-
|
|
3
|
-
HEALTH_CHECK = getattr(settings, "HEALTH_CHECK", {})
|
|
4
|
-
HEALTH_CHECK.setdefault("DISK_USAGE_MAX", 90)
|
|
5
|
-
HEALTH_CHECK.setdefault("MEMORY_MIN", 100)
|
|
6
|
-
HEALTH_CHECK.setdefault("WARNINGS_AS_ERRORS", True)
|
|
7
|
-
HEALTH_CHECK.setdefault("SUBSETS", {})
|
|
8
|
-
HEALTH_CHECK.setdefault("DISABLE_THREADING", False)
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
|
|
3
|
-
from celery import current_app
|
|
4
|
-
from django.apps import AppConfig
|
|
5
|
-
from django.conf import settings
|
|
6
|
-
|
|
7
|
-
from health_check.plugins import plugin_dir
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class HealthCheckConfig(AppConfig):
|
|
11
|
-
name = "health_check.contrib.celery"
|
|
12
|
-
|
|
13
|
-
def ready(self):
|
|
14
|
-
from .backends import CeleryHealthCheck
|
|
15
|
-
|
|
16
|
-
warnings.warn(
|
|
17
|
-
"The `health_check.contrib.celery` app is deprecated: checks are now configured via `HealthCheckView`. Action: remove this sub-app from `INSTALLED_APPS` and add the Celery check(s) to your `HealthCheckView.checks`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
|
|
18
|
-
DeprecationWarning,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
if hasattr(settings, "HEALTHCHECK_CELERY_TIMEOUT"):
|
|
22
|
-
warnings.warn(
|
|
23
|
-
"`HEALTHCHECK_CELERY_TIMEOUT` setting is deprecated: it was split into separate timeouts. Action: replace it with `HEALTHCHECK_CELERY_RESULT_TIMEOUT` and `HEALTHCHECK_CELERY_QUEUE_TIMEOUT`. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md).",
|
|
24
|
-
DeprecationWarning,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
for queue in current_app.amqp.queues:
|
|
28
|
-
celery_class_name = "CeleryHealthCheck" + queue.title()
|
|
29
|
-
|
|
30
|
-
celery_class = type(celery_class_name, (CeleryHealthCheck,), {"queue": queue})
|
|
31
|
-
plugin_dir.register(celery_class)
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
from celery.exceptions import TaskRevokedError, TimeoutError
|
|
2
|
-
from django.conf import settings
|
|
3
|
-
|
|
4
|
-
from health_check.backends import HealthCheck
|
|
5
|
-
from health_check.deprecation import deprecated
|
|
6
|
-
from health_check.exceptions import ServiceReturnedUnexpectedResult, ServiceUnavailable
|
|
7
|
-
|
|
8
|
-
from .tasks import add
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@deprecated(
|
|
12
|
-
"`CeleryHealthCheck` is deprecated: use `health_check.contrib.celery.Ping` (or the new view-based Celery ping check) instead. Action: remove legacy CeleryHealthCheck subclasses and configure `HealthCheckView` with the new Celery ping check in your `checks` list. See migration guide: https://codingjoe.dev/django-health-check/migrate-to-v4/ (docs/migrate-to-v4.md)."
|
|
13
|
-
)
|
|
14
|
-
class CeleryHealthCheck(HealthCheck):
|
|
15
|
-
def check_status(self):
|
|
16
|
-
timeout = getattr(settings, "HEALTHCHECK_CELERY_TIMEOUT", 3)
|
|
17
|
-
result_timeout = getattr(settings, "HEALTHCHECK_CELERY_RESULT_TIMEOUT", timeout)
|
|
18
|
-
queue_timeout = getattr(settings, "HEALTHCHECK_CELERY_QUEUE_TIMEOUT", timeout)
|
|
19
|
-
priority = getattr(settings, "HEALTHCHECK_CELERY_PRIORITY", None)
|
|
20
|
-
|
|
21
|
-
try:
|
|
22
|
-
result = add.apply_async(args=[4, 4], expires=queue_timeout, queue=self.queue, priority=priority)
|
|
23
|
-
result.get(timeout=result_timeout)
|
|
24
|
-
if result.result != 8:
|
|
25
|
-
self.add_error(ServiceReturnedUnexpectedResult("Celery returned wrong result"))
|
|
26
|
-
except OSError as e:
|
|
27
|
-
self.add_error(ServiceUnavailable("IOError"), e)
|
|
28
|
-
except NotImplementedError as e:
|
|
29
|
-
self.add_error(
|
|
30
|
-
ServiceUnavailable("NotImplementedError: Make sure CELERY_RESULT_BACKEND is set"),
|
|
31
|
-
e,
|
|
32
|
-
)
|
|
33
|
-
except TaskRevokedError as e:
|
|
34
|
-
self.add_error(
|
|
35
|
-
ServiceUnavailable(
|
|
36
|
-
"TaskRevokedError: The task was revoked, likely because it spent too long in the queue"
|
|
37
|
-
),
|
|
38
|
-
e,
|
|
39
|
-
)
|
|
40
|
-
except TimeoutError as e:
|
|
41
|
-
self.add_error(
|
|
42
|
-
ServiceUnavailable("TimeoutError: The task took too long to return a result"),
|
|
43
|
-
e,
|
|
44
|
-
)
|
|
45
|
-
except BaseException as e:
|
|
46
|
-
self.add_error(ServiceUnavailable("Unknown error"), e)
|