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

Add health check endpoint #8048

Merged
merged 6 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/8048.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a `/health` endpoint to every configured HTTP listener that can be used as a health check endpoint by load balancers.
7 changes: 7 additions & 0 deletions docs/reverse_proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,10 @@ client IP addresses are recorded correctly.
Having done so, you can then use `https://matrix.example.com` (instead
of `https://matrix.example.com:8448`) as the "Custom server" when
connecting to Synapse from a client.


## Health check endpoint

Synapse exposes a health check endpoint for use by reverse proxies.
Each configured HTTP listener has a `/health` endpoint which always returns
200 OK (and doesn't get logged).
6 changes: 5 additions & 1 deletion synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
from synapse.rest.client.v2_alpha.keys import KeyChangesServlet, KeyQueryServlet
from synapse.rest.client.v2_alpha.register import RegisterRestServlet
from synapse.rest.client.versions import VersionsRestServlet
from synapse.rest.health import HealthResource
from synapse.rest.key.v2 import KeyApiV2Resource
from synapse.server import HomeServer
from synapse.storage.databases.main.censor_events import CensorEventsStore
Expand Down Expand Up @@ -493,7 +494,10 @@ def _listen_http(self, listener_config: ListenerConfig):
site_tag = listener_config.http_options.tag
if site_tag is None:
site_tag = port
resources = {}

# We always include a health resource.
resources = {"/health": HealthResource()}

for res in listener_config.http_options.resources:
for name in res.names:
if name == "metrics":
Expand Down
5 changes: 4 additions & 1 deletion synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory
from synapse.rest import ClientRestResource
from synapse.rest.admin import AdminRestResource
from synapse.rest.health import HealthResource
from synapse.rest.key.v2 import KeyApiV2Resource
from synapse.rest.well_known import WellKnownResource
from synapse.server import HomeServer
Expand Down Expand Up @@ -98,7 +99,9 @@ def _listener_http(self, config: HomeServerConfig, listener_config: ListenerConf
if site_tag is None:
site_tag = port

resources = {}
# We always include a health resource.
resources = {"/health": HealthResource()}

for res in listener_config.http_options.resources:
for name in res.names:
if name == "openid" and "federation" in res.names:
Expand Down
9 changes: 8 additions & 1 deletion synapse/http/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ def _finished_processing(self):
# the connection dropped)
code += "!"

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

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


class XForwardedForRequest(SynapseRequest):
def __init__(self, *args, **kw):
Expand Down
31 changes: 31 additions & 0 deletions synapse/rest/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.web.resource import Resource


class HealthResource(Resource):
"""A resource that does nothing except return a 200 with a body of `OK`,
which can be used as a health check.

Note: `SynapseRequest._should_log_request` ensures that requests to
`/health` do not get logged at INFO.
"""

isLeaf = 1

def render_GET(self, request):
request.setHeader(b"Content-Type", b"text/plain")
return b"OK"
34 changes: 34 additions & 0 deletions tests/rest/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright 2020 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from synapse.rest.health import HealthResource

from tests import unittest


class HealthCheckTests(unittest.HomeserverTestCase):
def setUp(self):
super().setUp()

# replace the JsonResource with a HealthResource.
self.resource = HealthResource()

def test_health(self):
request, channel = self.make_request("GET", "/health", shorthand=False)
self.render(request)

self.assertEqual(request.code, 200)
self.assertEqual(channel.result["body"], b"OK")