Skip to content

Commit 0ce33db

Browse files
authored
Log database health check exceptions at ERROR level (#1977)
Fixes OpenConext/Monitor-bundle#27. The catch block in DoctrineConnectionHealthCheck was silently swallowing exceptions, making database failures impossible to diagnose from logs. Injects PSR-3 LoggerInterface via constructor and logs the exception with full context before returning the status-down report.
1 parent 5ddec7a commit 0ce33db

3 files changed

Lines changed: 108 additions & 1 deletion

File tree

config/services/services.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ services:
1818
class: OpenConext\EngineBlockBundle\HealthCheck\DoctrineConnectionHealthCheck
1919
arguments:
2020
- '%monitor_database_health_check_query%'
21+
- '@logger'
2122
calls:
2223
- [ setEntityManager, ['@?doctrine.orm.entity_manager']]
2324
tags:

src/OpenConext/EngineBlockBundle/HealthCheck/DoctrineConnectionHealthCheck.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use OpenConext\MonitorBundle\HealthCheck\HealthCheckInterface;
2525
use OpenConext\MonitorBundle\HealthCheck\HealthReportInterface;
2626
use OpenConext\MonitorBundle\Value\HealthReport;
27+
use Psr\Log\LoggerInterface;
2728

2829
/**
2930
* Test if there is a working database connection.
@@ -40,7 +41,7 @@ class DoctrineConnectionHealthCheck implements HealthCheckInterface
4041
*/
4142
private $query;
4243

43-
public function __construct($query)
44+
public function __construct(string $query, private readonly LoggerInterface $logger)
4445
{
4546
Assertion::nonEmptyString($query, 'health check query');
4647
$this->query = $query;
@@ -59,6 +60,10 @@ public function check(HealthReportInterface $report): HealthReportInterface
5960
try {
6061
$this->entityManager->getConnection()->executeQuery($this->query);
6162
} catch (Exception $e) {
63+
$this->logger->error(
64+
'Unable to execute a query on the database.',
65+
['exception' => $e]
66+
);
6267
return HealthReport::buildStatusDown('Unable to execute a query on the database.');
6368
}
6469
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/**
4+
* Copyright 2010 SURFnet B.V.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
namespace OpenConext\EngineBlockBundle\Tests;
20+
21+
use Doctrine\DBAL\Connection;
22+
use Doctrine\ORM\EntityManager;
23+
use Exception;
24+
use Mockery;
25+
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
26+
use OpenConext\EngineBlockBundle\HealthCheck\DoctrineConnectionHealthCheck;
27+
use OpenConext\MonitorBundle\HealthCheck\HealthReportInterface;
28+
use OpenConext\MonitorBundle\Value\HealthReport;
29+
use PHPUnit\Framework\Attributes\Group;
30+
use PHPUnit\Framework\Attributes\Test;
31+
use PHPUnit\Framework\TestCase;
32+
use Psr\Log\LoggerInterface;
33+
34+
class DoctrineConnectionHealthCheckTest extends TestCase
35+
{
36+
use MockeryPHPUnitIntegration;
37+
38+
#[Group('health-check')]
39+
#[Test]
40+
public function it_returns_the_original_report_when_no_entity_manager_is_set(): void
41+
{
42+
$logger = Mockery::mock(LoggerInterface::class);
43+
$logger->shouldNotReceive('error');
44+
45+
$healthCheck = new DoctrineConnectionHealthCheck('SELECT 1', $logger);
46+
47+
$report = HealthReport::buildStatusUp();
48+
$result = $healthCheck->check($report);
49+
50+
$this->assertSame($report, $result);
51+
}
52+
53+
#[Group('health-check')]
54+
#[Test]
55+
public function it_returns_the_original_report_when_the_query_succeeds(): void
56+
{
57+
$logger = Mockery::mock(LoggerInterface::class);
58+
$logger->shouldNotReceive('error');
59+
60+
$connection = Mockery::mock(Connection::class);
61+
$connection->shouldReceive('executeQuery')->with('SELECT 1')->once();
62+
63+
$entityManager = Mockery::mock(EntityManager::class);
64+
$entityManager->shouldReceive('getConnection')->once()->andReturn($connection);
65+
66+
$healthCheck = new DoctrineConnectionHealthCheck('SELECT 1', $logger);
67+
$healthCheck->setEntityManager($entityManager);
68+
69+
$report = HealthReport::buildStatusUp();
70+
$result = $healthCheck->check($report);
71+
72+
$this->assertSame($report, $result);
73+
}
74+
75+
#[Group('health-check')]
76+
#[Test]
77+
public function it_logs_an_error_and_returns_status_down_when_the_query_fails(): void
78+
{
79+
$exception = new Exception('Connection refused');
80+
81+
$logger = Mockery::mock(LoggerInterface::class);
82+
$logger->shouldReceive('error')
83+
->once()
84+
->with('Unable to execute a query on the database.', ['exception' => $exception]);
85+
86+
$connection = Mockery::mock(Connection::class);
87+
$connection->shouldReceive('executeQuery')->with('SELECT 1')->andThrow($exception);
88+
89+
$entityManager = Mockery::mock(EntityManager::class);
90+
$entityManager->shouldReceive('getConnection')->once()->andReturn($connection);
91+
92+
$healthCheck = new DoctrineConnectionHealthCheck('SELECT 1', $logger);
93+
$healthCheck->setEntityManager($entityManager);
94+
95+
$report = HealthReport::buildStatusUp();
96+
$result = $healthCheck->check($report);
97+
98+
$this->assertTrue($result->isDown());
99+
$this->assertSame(HealthReportInterface::STATUS_CODE_DOWN, $result->getStatusCode());
100+
}
101+
}

0 commit comments

Comments
 (0)