Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 7620912

Browse files
authored
Add health check endpoint (#8048)
1 parent 4dd27e6 commit 7620912

File tree

7 files changed

+90
-3
lines changed

7 files changed

+90
-3
lines changed

changelog.d/8048.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a `/health` endpoint to every configured HTTP listener that can be used as a health check endpoint by load balancers.

docs/reverse_proxy.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,10 @@ client IP addresses are recorded correctly.
139139
Having done so, you can then use `https://matrix.example.com` (instead
140140
of `https://matrix.example.com:8448`) as the "Custom server" when
141141
connecting to Synapse from a client.
142+
143+
144+
## Health check endpoint
145+
146+
Synapse exposes a health check endpoint for use by reverse proxies.
147+
Each configured HTTP listener has a `/health` endpoint which always returns
148+
200 OK (and doesn't get logged).

synapse/app/generic_worker.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
from synapse.rest.client.v2_alpha.keys import KeyChangesServlet, KeyQueryServlet
124124
from synapse.rest.client.v2_alpha.register import RegisterRestServlet
125125
from synapse.rest.client.versions import VersionsRestServlet
126+
from synapse.rest.health import HealthResource
126127
from synapse.rest.key.v2 import KeyApiV2Resource
127128
from synapse.server import HomeServer
128129
from synapse.storage.databases.main.censor_events import CensorEventsStore
@@ -493,7 +494,10 @@ def _listen_http(self, listener_config: ListenerConfig):
493494
site_tag = listener_config.http_options.tag
494495
if site_tag is None:
495496
site_tag = port
496-
resources = {}
497+
498+
# We always include a health resource.
499+
resources = {"/health": HealthResource()}
500+
497501
for res in listener_config.http_options.resources:
498502
for name in res.names:
499503
if name == "metrics":

synapse/app/homeserver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory
6969
from synapse.rest import ClientRestResource
7070
from synapse.rest.admin import AdminRestResource
71+
from synapse.rest.health import HealthResource
7172
from synapse.rest.key.v2 import KeyApiV2Resource
7273
from synapse.rest.well_known import WellKnownResource
7374
from synapse.server import HomeServer
@@ -98,7 +99,9 @@ def _listener_http(self, config: HomeServerConfig, listener_config: ListenerConf
9899
if site_tag is None:
99100
site_tag = port
100101

101-
resources = {}
102+
# We always include a health resource.
103+
resources = {"/health": HealthResource()}
104+
102105
for res in listener_config.http_options.resources:
103106
for name in res.names:
104107
if name == "openid" and "federation" in res.names:

synapse/http/site.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,9 @@ def _finished_processing(self):
286286
# the connection dropped)
287287
code += "!"
288288

289-
self.site.access_logger.info(
289+
log_level = logging.INFO if self._should_log_request() else logging.DEBUG
290+
self.site.access_logger.log(
291+
log_level,
290292
"%s - %s - {%s}"
291293
" Processed request: %.3fsec/%.3fsec (%.3fsec, %.3fsec) (%.3fsec/%.3fsec/%d)"
292294
' %sB %s "%s %s %s" "%s" [%d dbevts]',
@@ -314,6 +316,11 @@ def _finished_processing(self):
314316
except Exception as e:
315317
logger.warning("Failed to stop metrics: %r", e)
316318

319+
def _should_log_request(self) -> bool:
320+
"""Whether we should log at INFO that we processed the request.
321+
"""
322+
return self.path != b"/health"
323+
317324

318325
class XForwardedForRequest(SynapseRequest):
319326
def __init__(self, *args, **kw):

synapse/rest/health.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2020 The Matrix.org Foundation C.I.C.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from twisted.web.resource import Resource
17+
18+
19+
class HealthResource(Resource):
20+
"""A resource that does nothing except return a 200 with a body of `OK`,
21+
which can be used as a health check.
22+
23+
Note: `SynapseRequest._should_log_request` ensures that requests to
24+
`/health` do not get logged at INFO.
25+
"""
26+
27+
isLeaf = 1
28+
29+
def render_GET(self, request):
30+
request.setHeader(b"Content-Type", b"text/plain")
31+
return b"OK"

tests/rest/test_health.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2020 The Matrix.org Foundation C.I.C.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
from synapse.rest.health import HealthResource
18+
19+
from tests import unittest
20+
21+
22+
class HealthCheckTests(unittest.HomeserverTestCase):
23+
def setUp(self):
24+
super().setUp()
25+
26+
# replace the JsonResource with a HealthResource.
27+
self.resource = HealthResource()
28+
29+
def test_health(self):
30+
request, channel = self.make_request("GET", "/health", shorthand=False)
31+
self.render(request)
32+
33+
self.assertEqual(request.code, 200)
34+
self.assertEqual(channel.result["body"], b"OK")

0 commit comments

Comments
 (0)