Skip to content

Commit c9e04a4

Browse files
authored
Merge pull request #70 from nick322/patch-1
(#22) Add health-check:health command
2 parents 673d979 + 15b2053 commit c9e04a4

File tree

6 files changed

+236
-25
lines changed

6 files changed

+236
-25
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ If you'd like to tweak the config file (helpful for configuring the `EnvHealthCh
7171
php artisan vendor:publish --provider="UKFast\HealthCheck\HealthCheckServiceProvider" --tag="config"
7272
```
7373

74+
##### Console command
75+
76+
Check all: `php artisan health-check:status`
77+
78+
Only specific checks: `php artisan health-check:status --only=log,cache`
79+
80+
Except specific checks: `php artisan health-check:status --except=cache`
7481

7582
##### Middleware
7683

src/Commands/StatusCommand.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace UKFast\HealthCheck\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use UKFast\HealthCheck\Facade\HealthCheck;
7+
8+
class StatusCommand extends Command
9+
{
10+
protected $signature = '
11+
health-check:status
12+
{--only= : comma separated checks names to run}
13+
{--except= : comma separated checks names to skip}
14+
';
15+
16+
protected $description = 'Check health status';
17+
18+
public function handle()
19+
{
20+
$only = (string)$this->option('only');
21+
$except = (string)$this->option('except');
22+
23+
if ($only && $except) {
24+
$this->error('Pass --only OR --except, but not both!');
25+
26+
return 1;
27+
}
28+
29+
$onlyChecks = array_map('trim', explode(',', $only));
30+
$exceptChecks = array_map('trim', explode(',', $except));
31+
32+
$problems = [];
33+
/** @var \UKFast\HealthCheck\HealthCheck $check */
34+
foreach (HealthCheck::all() as $check) {
35+
if ($only && !in_array($check->name(), $onlyChecks)) {
36+
continue;
37+
} elseif ($except && in_array($check->name(), $exceptChecks)) {
38+
continue;
39+
}
40+
41+
$status = $check->status();
42+
43+
if ($status->isProblem()) {
44+
$problems[] = [$check->name(), $status->name(), $status->message()];
45+
}
46+
}
47+
48+
if (!$isOkay = empty($problems)) {
49+
$this->table(['name', 'status', 'message'], $problems);
50+
}
51+
52+
$this->info('All checks passed successfully');
53+
54+
return $isOkay ? 0 : 1;
55+
}
56+
}

src/Controllers/HealthCheckController.php

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,45 @@
22

33
namespace UKFast\HealthCheck\Controllers;
44

5-
use Illuminate\Contracts\Container\Container;
6-
use Illuminate\Http\Response;
7-
use Illuminate\Support\Collection;
5+
use Illuminate\Support\Arr;
86
use UKFast\HealthCheck\Status;
7+
use Illuminate\Contracts\Container\Container;
98

109
class HealthCheckController
1110
{
1211
public function __invoke(Container $container)
1312
{
14-
$checks = new Collection;
15-
foreach (config('healthcheck.checks') as $check) {
16-
$checks->push($container->make($check));
17-
}
18-
19-
$statuses = $checks->map(function ($check) {
20-
return $check->status();
21-
});
22-
23-
$isProblem = $statuses->contains(function ($status) {
24-
return $status->isProblem();
25-
});
13+
Arr::set($body, 'status', Status::OKAY);
2614

27-
$isDegraded = $statuses->contains(function ($status) {
28-
return $status->isDegraded();
29-
});
15+
$hasProblem = false;
3016

31-
$body = ['status' => ($isProblem ? Status::PROBLEM : ($isDegraded ? Status::DEGRADED : Status::OKAY))];
32-
foreach ($statuses as $status) {
33-
$body[$status->name()] = [];
34-
$body[$status->name()]['status'] = $status->getStatus();
17+
foreach (config('healthcheck.checks') as $check) {
18+
$status = $container->make($check)->status();
3519

20+
Arr::set($body, $status->name() . '.status', $status->getStatus());
3621
if (!$status->isOkay()) {
37-
$body[$status->name()]['message'] = $status->message();
22+
Arr::set($body, $status->name() . '.message', $status->message());
3823
}
3924

4025
if (!empty($status->context())) {
41-
$body[$status->name()]['context'] = $status->context();
26+
Arr::set($body, $status->name() . '.context', $status->context());
27+
}
28+
29+
if ($status->getStatus() == Status::PROBLEM && $hasProblem == false) {
30+
$hasProblem = true;
31+
Arr::set($body, 'status', Status::PROBLEM);
32+
}
33+
34+
if ($status->getStatus() == Status::DEGRADED && $hasProblem == false) {
35+
Arr::set($body, 'status', Status::DEGRADED);
4236
}
4337
}
4438

45-
return new Response($body, $isProblem ? config('healthcheck.default-problem-http-code', 500) : 200);
39+
return response()
40+
->json(
41+
$body,
42+
in_array(Arr::get($body, 'status'), [Status::DEGRADED, Status::OKAY]) ? 200
43+
: config('healthcheck.default-problem-http-code', 500)
44+
);
4645
}
4746
}

src/HealthCheckServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace UKFast\HealthCheck;
44

55
use Illuminate\Support\ServiceProvider;
6+
use UKFast\HealthCheck\Commands\StatusCommand;
67
use UKFast\HealthCheck\Commands\CacheSchedulerRunning;
78
use UKFast\HealthCheck\Commands\HealthCheckMakeCommand;
89
use UKFast\HealthCheck\Controllers\HealthCheckController;
@@ -33,6 +34,7 @@ public function boot()
3334
$this->commands([
3435
CacheSchedulerRunning::class,
3536
HealthCheckMakeCommand::class,
37+
StatusCommand::class,
3638
]);
3739
}
3840

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
3+
namespace Tests\Commands;
4+
5+
use Illuminate\Testing\PendingCommand;
6+
use Mockery;
7+
use Mockery\MockInterface;
8+
use Tests\TestCase;
9+
use UKFast\HealthCheck\Checks\DatabaseHealthCheck;
10+
use UKFast\HealthCheck\Checks\LogHealthCheck;
11+
use UKFast\HealthCheck\HealthCheckServiceProvider;
12+
use UKFast\HealthCheck\Status;
13+
14+
class StatusCommandTest extends TestCase
15+
{
16+
/**
17+
* @test
18+
*/
19+
public function running_command_status()
20+
{
21+
$this->app->register(HealthCheckServiceProvider::class);
22+
config(['healthcheck.checks' => [LogHealthCheck::class]]);
23+
24+
$status = new Status();
25+
$status->okay();
26+
$this->mockLogHealthCheck($status);
27+
28+
$result = $this->artisan('health-check:status');
29+
30+
if ($result instanceof PendingCommand) {
31+
$result->assertExitCode(0);
32+
} else {
33+
$this->assertTrue(true);
34+
}
35+
}
36+
37+
/**
38+
* @test
39+
*/
40+
public function running_command_status_with_only_option()
41+
{
42+
$this->app->register(HealthCheckServiceProvider::class);
43+
44+
$status = new Status();
45+
$status->okay();
46+
$this->mockLogHealthCheck($status);
47+
48+
$result = $this->artisan('health-check:status', ['--only' => 'log']);
49+
50+
if ($result instanceof PendingCommand) {
51+
$result->assertExitCode(0);
52+
} else {
53+
$this->assertTrue(true);
54+
}
55+
}
56+
57+
/**
58+
* @test
59+
*/
60+
public function running_command_status_with_except_option()
61+
{
62+
$this->app->register(HealthCheckServiceProvider::class);
63+
config(['healthcheck.checks' => [LogHealthCheck::class, DatabaseHealthCheck::class]]);
64+
65+
$status = new Status();
66+
$status->okay();
67+
$this->mockLogHealthCheck($status);
68+
69+
$result = $this->artisan('health-check:status', ['--except' => 'database']);
70+
71+
if ($result instanceof PendingCommand) {
72+
$result->assertExitCode(0);
73+
} else {
74+
$this->assertTrue(true);
75+
}
76+
}
77+
78+
/**
79+
* @test
80+
*/
81+
public function running_command_status_with_only_and_except_option()
82+
{
83+
$this->app->register(HealthCheckServiceProvider::class);
84+
85+
$result = $this->artisan('health-check:status', ['--only' => 'log', '--except' => 'log']);
86+
87+
if ($result instanceof PendingCommand) {
88+
$result
89+
->assertExitCode(1)
90+
->expectsOutput('Pass --only OR --except, but not both!');
91+
} else {
92+
$this->assertTrue(true);
93+
}
94+
}
95+
96+
/**
97+
* @test
98+
*/
99+
public function running_command_status_with_failure_condition()
100+
{
101+
$this->app->register(HealthCheckServiceProvider::class);
102+
config(['healthcheck.checks' => [LogHealthCheck::class]]);
103+
$status = new Status();
104+
$status->withName('statusName')->problem('statusMessage');
105+
$this->mockLogHealthCheck($status);
106+
107+
$result = $this->artisan('health-check:status');
108+
109+
if ($result instanceof PendingCommand) {
110+
$result->assertExitCode(1);
111+
//for laravel 5.*
112+
if (method_exists($result, 'expectsTable')) {
113+
$result->expectsTable(['name', 'status', 'message'], [['log', 'statusName', 'statusMessage']]);
114+
}
115+
}
116+
}
117+
118+
private function mockLogHealthCheck(Status $status)
119+
{
120+
$this->instance(
121+
LogHealthCheck::class,
122+
Mockery::mock(LogHealthCheck::class, function (MockInterface $mock) use ($status) {
123+
$mock->shouldReceive('name')->andReturn('log');
124+
$mock->shouldReceive('status')->andReturn($status);
125+
})
126+
);
127+
}
128+
}

tests/Controllers/HealthCheckControllerTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,25 @@ public function returns_status_of_problem_when_both_degraded_and_problem_statuse
202202
'context' => ['debug' => 'info'],
203203
],
204204
], json_decode($response->getContent(), true));
205+
206+
$this->setChecks([AlwaysUpCheck::class, AlwaysDownCheck::class, AlwaysDegradedCheck::class,]);
207+
$response = (new HealthCheckController)->__invoke($this->app);
208+
209+
$this->assertSame([
210+
'status' => 'PROBLEM',
211+
'always-up' => ['status' => 'OK'],
212+
'always-down' => [
213+
'status' => 'PROBLEM',
214+
'message' => 'Something went wrong',
215+
'context' => ['debug' => 'info'],
216+
],
217+
'always-degraded' => [
218+
'status' => 'DEGRADED',
219+
'message' => 'Something went wrong',
220+
'context' => ['debug' => 'info'],
221+
],
222+
], json_decode($response->getContent(), true));
223+
205224
}
206225

207226
protected function setChecks($checks)

0 commit comments

Comments
 (0)