Skip to content

Commit

Permalink
Improve log conformity and formatting and add names to health checks (#…
Browse files Browse the repository at this point in the history
…66)

* Quote names in logs uniformly

* Add name attribute to health checks

* Update tests for service logging behavior

* Show check name in service log instead of repr

* Log interval as string
  • Loading branch information
SRv6d authored Mar 27, 2024
1 parent 1a9f8f1 commit 3cb28ca
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 12 deletions.
10 changes: 6 additions & 4 deletions src/anycastd/core/_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def __post_init__(self) -> None:
self._log = logger.bind(
service_name=self.name,
service_prefixes=[str(prefix.prefix) for prefix in self.prefixes],
service_health_checks=list(self.health_checks),
service_health_checks=[check.name for check in self.health_checks],
)

@property
Expand All @@ -91,7 +91,9 @@ async def run(self) -> None:
passing, and denounce them otherwise. If the returned coroutine is cancelled,
the service will be terminated, denouncing all prefixes in the process.
"""
self._log.info("Starting service %s.", self.name, service_healthy=self.healthy)
self._log.info(
'Starting service "%s".', self.name, service_healthy=self.healthy
)
try:
while not self._terminate:
checks_currently_healthy: bool = await self.all_checks_healthy()
Expand All @@ -107,7 +109,7 @@ async def run(self) -> None:

except asyncio.CancelledError:
self._log.debug(
"Coroutine for service %s was cancelled.",
'Coroutine for service "%s" was cancelled.',
self.name,
service_healthy=self.healthy,
)
Expand Down Expand Up @@ -157,4 +159,4 @@ async def terminate(self) -> None:
"""Terminate the service and denounce its prefixes."""
self._terminate = True
await self.denounce_all_prefixes()
logger.info("Service %s terminated.", self.name, service=self.name)
logger.info('Service "%s" terminated.', self.name, service=self.name)
8 changes: 4 additions & 4 deletions src/anycastd/healthcheck/_cabourotte/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ def __post_init__(self) -> None:

async def _get_status(self) -> bool:
"""Get the current status of the check as reported by cabourotte."""
log = logger.bind(name=self.name, url=self.url, interval=self.interval)
log = logger.bind(name=self.name, url=self.url, interval=str(self.interval))

log.debug("Cabourotte health check %s awaiting check result.", self.name)
log.debug('Cabourotte health check "%s" awaiting check result.', self.name)
try:
result = await get_result(self.name, url=self.url)
except CabourotteCheckNotFoundError as exc:
log.error(
"Cabourotte health check %s does not exist, "
'Cabourotte health check "%s" does not exist, '
"returning an unhealthy status.",
self.name,
exc_info=exc,
)
return False
log.debug(
"Cabourotte health check %s received check result.",
'Cabourotte health check "%s" received check result.',
self.name,
result=result,
)
Expand Down
2 changes: 2 additions & 0 deletions src/anycastd/healthcheck/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
class Healthcheck(Protocol):
"""A health check representing the status of a component."""

name: str

async def is_healthy(self) -> bool:
"""Whether the health checked component is healthy or not."""
...
4 changes: 4 additions & 0 deletions tests/dummy.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from dataclasses import dataclass
from ipaddress import IPv4Network, IPv6Network

from anycastd.prefix import Prefix


@dataclass
class DummyHealthcheck:
"""A dummy healthcheck to test the abstract base class."""

name: str

async def is_healthy(self) -> bool:
"""Always healthy."""
return True
Expand Down
4 changes: 2 additions & 2 deletions tests/healthcheck/cabourotte/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ async def test_get_status_logs_error_if_check_does_not_exist(mocker: MockerFixtu
await healthcheck._get_status()

assert (
logs[1]["event"]
== "Cabourotte health check test does not exist, returning an unhealthy status."
logs[1]["event"] == 'Cabourotte health check "test" does not exist, '
"returning an unhealthy status."
)
assert logs[1]["log_level"] == "error"
assert logs[1]["exc_info"] == exc
Expand Down
29 changes: 27 additions & 2 deletions tests/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def example_service(ipv4_example_network, ipv6_example_network):
return Service(
name="Example Service",
prefixes=(DummyPrefix(ipv4_example_network), DummyPrefix(ipv6_example_network)),
health_checks=(DummyHealthcheck(), DummyHealthcheck()),
health_checks=(DummyHealthcheck(name="dummy1"), DummyHealthcheck("dummy2")),
)


Expand Down Expand Up @@ -138,6 +138,25 @@ async def test_run_updates_health_state_when_changed(
assert example_service_w_mock_prefixes.healthy is True


async def test_run_logs_info_event(example_service):
"""When run, an info event is logged."""
example_service._terminate = True

with capture_logs() as logs:
await example_service.run()

assert logs[0]["event"] == f'Starting service "{example_service.name}".'
assert logs[0]["log_level"] == "info"
assert logs[0]["service_name"] == example_service.name
assert logs[0]["service_healthy"] == example_service.healthy
assert logs[0]["service_health_checks"] == [
check.name for check in example_service.health_checks
]
assert logs[0]["service_prefixes"] == [
str(prefix.prefix) for prefix in example_service.prefixes
]


async def test_all_checks_healthy_true_when_all_checks_healthy(
example_service_w_mock_checks,
):
Expand Down Expand Up @@ -240,6 +259,12 @@ def test_change_of_service_health_is_logged(example_service, new_health_status:
assert logs[0]["log_level"] == "info"
assert logs[0]["service_name"] == example_service.name
assert logs[0]["service_healthy"] == new_health_status
assert logs[0]["service_health_checks"] == [
check.name for check in example_service.health_checks
]
assert logs[0]["service_prefixes"] == [
str(prefix.prefix) for prefix in example_service.prefixes
]


@pytest.mark.parametrize("current_health_status", [True, False])
Expand Down Expand Up @@ -305,7 +330,7 @@ async def test_run_coro_cancellation_logs_termination(example_service, mocker):

assert (
logs[0]["event"]
== f"Coroutine for service {example_service.name} was cancelled."
== f'Coroutine for service "{example_service.name}" was cancelled.'
)
assert logs[0]["log_level"] == "debug"
assert logs[0]["service_name"] == example_service.name
Expand Down

0 comments on commit 3cb28ca

Please sign in to comment.