├── tests
├── commit.txt
├── trailing-newline-commit.txt
├── TestCase.php
├── Checks
│ ├── General
│ │ ├── EnvHealthCheckTest.php
│ │ ├── DebugHealthCheckTest.php
│ │ ├── MemcachedHealthCheckTest.php
│ │ └── HttpHealthCheckTest.php
│ └── Laravel
│ │ ├── DatabaseHealthCheckTest.php
│ │ └── QueueHealthCheckTest.php
├── HealthCheckTest.php
├── ResultStackTest.php
├── HealthResultTest.php
└── HealthzTest.php
├── .gitignore
├── docker-compose.yaml
├── src
├── Exceptions
│ ├── HealthFailureException.php
│ └── HealthWarningException.php
├── Support
│ ├── Stack.php
│ ├── HealthzArtisanCommand.php
│ └── HealthzServiceProvider.php
├── Checks
│ ├── General
│ │ ├── DebugHealthCheck.php
│ │ ├── EnvHealthCheck.php
│ │ ├── MemcachedHealthCheck.php
│ │ └── HttpHealthCheck.php
│ └── Laravel
│ │ ├── DatabaseHealthCheck.php
│ │ └── QueueHealthCheck.php
├── ResultStack.php
├── HealthResult.php
├── Healthz.php
└── HealthCheck.php
├── phpunit.xml
├── docker
└── installComposer.sh
├── .github
└── workflows
│ └── test.yml
├── LICENSE
├── composer.json
├── Dockerfile
├── templates
└── healthz.html
└── readme.md
/tests/commit.txt:
--------------------------------------------------------------------------------
1 | 2d39ff989b3f18bd332cfc7a5c2a0abea1308e27
--------------------------------------------------------------------------------
/tests/trailing-newline-commit.txt:
--------------------------------------------------------------------------------
1 | 2d39ff989b3f18bd332cfc7a5c2a0abea1308e27
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /tests/coverage
3 | composer.lock
4 | .idea
5 | .phpunit.result.cache
6 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 | ./tests/
13 |
14 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/docker/installComposer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
4 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
5 | ACTUAL_SIGNATURE=$(php -r "echo hash_file('sha384', 'composer-setup.php');")
6 |
7 | if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then
8 | >&2 echo 'ERROR: Invalid installer signature'
9 | rm composer-setup.php
10 | exit 1
11 | fi
12 |
13 | php composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet
14 | RESULT=$?
15 | rm composer-setup.php
16 | if [[ "$RESULT" -eq "0" ]]; then
17 | composer global require hirak/prestissimo
18 | fi;
19 | exit $RESULT
20 | Terms
21 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | pull_request:
6 | types:
7 | - opened
8 | - synchronize
9 |
10 | jobs:
11 | build:
12 | name: PHP ${{ matrix.php }}
13 |
14 | runs-on: ${{ matrix.os }}
15 |
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | php: [ "7.3", "7.4", "8.0", "8.1" ]
20 | os: [ ubuntu-latest ]
21 |
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@master
25 |
26 | - name: Setup PHP
27 | uses: shivammathur/setup-php@master
28 | with:
29 | php-version: ${{ matrix.php }}
30 |
31 | - name: Cache Composer dependencies
32 | uses: actions/cache@v2
33 | with:
34 | path: /tmp/composer-cache
35 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
36 |
37 | - name: Install Dependencies
38 | uses: php-actions/composer@master
39 | with:
40 | php_version: ${{ matrix.php }}
41 |
42 | - name: Execute tests
43 | run: vendor/bin/phpunit
44 |
--------------------------------------------------------------------------------
/src/Support/Stack.php:
--------------------------------------------------------------------------------
1 | items;
20 | }
21 |
22 | /**
23 | * @param $item
24 | *
25 | * @return $this
26 | */
27 | public function push($item): self
28 | {
29 | $this->items[] = $item;
30 |
31 | return $this;
32 | }
33 |
34 | /**
35 | * @param array $items
36 | *
37 | * @return $this
38 | */
39 | public function merge(array $items): self
40 | {
41 | $this->items = array_merge($this->items, $items);
42 |
43 | return $this;
44 | }
45 |
46 | /**
47 | * @param array $items
48 | *
49 | * @return $this
50 | */
51 | public function replace(array $items): self
52 | {
53 | $this->items = $items;
54 |
55 | return $this;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Generation Tux
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/tests/Checks/General/EnvHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | env = new EnvHealthCheck('CUSTOM_ENV');
17 | }
18 |
19 | /** @test */
20 | public function instance_of_health_check()
21 | {
22 | $this->assertInstanceOf(HealthCheck::class, $this->env);
23 | }
24 |
25 | /** @test */
26 | public function sets_the_status_to_the_current_environment()
27 | {
28 | putenv('CUSTOM_ENV=staging');
29 | $this->env->run();
30 | $this->assertSame('staging', $this->env->status());
31 | }
32 |
33 | /**
34 | * @test
35 | */
36 | public function unknown_environment_emits_a_warning()
37 | {
38 | $this->expectException(\Gentux\Healthz\Exceptions\HealthWarningException::class);
39 | putenv('CUSTOM_ENV=');
40 | $this->env->run();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Checks/General/DebugHealthCheck.php:
--------------------------------------------------------------------------------
1 | env = $env;
27 | }
28 |
29 | /**
30 | * Check if the app is in debug mode
31 | *
32 | * @return void
33 | *
34 | * @throws HealthWarningException
35 | */
36 | public function run(): void
37 | {
38 | $debug = getenv($this->env) == 'true';
39 |
40 | if ($debug) {
41 | throw new HealthWarningException('on');
42 | }
43 |
44 | $this->setStatus('off');
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Checks/General/EnvHealthCheck.php:
--------------------------------------------------------------------------------
1 | env = $env;
28 | }
29 |
30 | /**
31 | * Run the health check
32 | *
33 | * @throws HealthWarningException
34 | */
35 | public function run(): void
36 | {
37 | $env = getenv($this->env) ?: 'UNKNOWN';
38 | if ($env == 'UNKNOWN') {
39 | throw new HealthWarningException($env);
40 | }
41 |
42 | $this->setStatus($env);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Checks/General/DebugHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | debug = new DebugHealthCheck();
13 | }
14 |
15 | /** @test */
16 | public function instance_of_health_check()
17 | {
18 | $this->assertInstanceOf(HealthCheck::class, $this->debug);
19 | }
20 |
21 | /** @test */
22 | public function run_sets_the_description_to_off()
23 | {
24 | putenv('APP_DEBUG=false');
25 |
26 | $this->debug->run();
27 | $this->assertSame('off', $this->debug->status());
28 | }
29 |
30 | /**
31 | * @test
32 | */
33 | public function run_throws_warning_exception_if_debug_is_on()
34 | {
35 | $this->expectException(\Gentux\Healthz\Exceptions\HealthWarningException::class);
36 | $this->debug = new DebugHealthCheck('DEBUG_CUSTOM');
37 | putenv('DEBUG_CUSTOM=true');
38 |
39 | $this->debug->run();
40 | $this->assertSame('on', $this->debug->status());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/HealthCheckTest.php:
--------------------------------------------------------------------------------
1 | check = new MockCheck();
20 | $this->checkWithTitle = new MockCheckTitle();
21 | }
22 |
23 | /** @test */
24 | public function title_defaults_to_the_class_name()
25 | {
26 | $result = $this->check->title();
27 | $this->assertSame('MockCheck', $result);
28 |
29 | $result = $this->checkWithTitle->title();
30 | $this->assertSame('Custom Title', $result);
31 | }
32 | }
33 |
34 | /**
35 | * ----------------------------------------------------------------------
36 | * Mock Health Checks that extends base abstract class
37 | * ----------------------------------------------------------------------
38 | */
39 |
40 | class MockCheck extends HealthCheck
41 | {
42 | public function run() { return 'all good'; }
43 | }
44 |
45 | class MockCheckTitle extends HealthCheck
46 | {
47 | protected $title = 'Custom Title';
48 |
49 | public function run() { return 'all good'; }
50 | }
51 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generationtux/healthz",
3 | "description": "Health checks for PHP apps.",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Kyle Ferguson",
8 | "email": "kyle.ferguson@generationtux.com"
9 | },
10 | {
11 | "name": "Damien Russell",
12 | "email": "christopher.russell@generationtux.com"
13 | },
14 | {
15 | "name": "Thomas Manley",
16 | "email": "thomas.manley@generationtux.com"
17 | }
18 | ],
19 | "minimum-stability": "dev",
20 | "prefer-stable": true,
21 | "autoload": {
22 | "psr-4": {
23 | "Gentux\\Healthz\\": "src/"
24 | },
25 | "classmap": [
26 | "tests/TestCase.php"
27 | ]
28 | },
29 | "require": {
30 | "php": ">=7.2.5|^8.0",
31 | "guzzlehttp/guzzle": "^6.2|^7.0.1|^7.2",
32 | "aws/aws-sdk-php": "~3.0",
33 | "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0",
34 | "illuminate/database": "^6.0|^7.0|^8.0|^9.0",
35 | "illuminate/queue": "^6.0|7.0|^8.0|^9.0",
36 | "illuminate/console": "^6.0|^7.0|^8.0|^9.0",
37 | "twig/twig": "^3.0"
38 | },
39 | "require-dev": {
40 | "phpunit/phpunit": "^8.4|^9.3.3",
41 | "mockery/mockery": "~1.3.3|^1.4.2"
42 | },
43 | "extra": {
44 | "laravel": {
45 | "providers": [
46 | "Gentux\\Healthz\\Support\\HealthzServiceProvider"
47 | ]
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:8.1-fpm-alpine
2 |
3 | # The following labels need to be set as part of the docker build process.
4 | # org.opencontainers.image.created
5 | # org.opencontainers.image.revision
6 | LABEL org.opencontainers.image.url="https://laravel.com" \
7 | org.opencontainers.image.documentation="https://github.com/generationtux/php-healthz/blob/master/README.md" \
8 | org.opencontainers.image.source="https://github.com/generationtux/php-healthz/Dockerfile" \
9 | org.opencontainers.image.vendor="Generation Tux " \
10 | org.opencontainers.image.title="Laravel 6.x" \
11 | org.opencontainers.image.description="PHP built for use with the Laravel/Lumen framework" \
12 | com.generationtux.php.backend="fpm"
13 |
14 | USER root
15 |
16 | COPY ./docker/installComposer.sh /tmp/installComposer.sh
17 |
18 | RUN apk --no-cache --update add bash ca-certificates libpq postgresql-dev curl git curl git mysql-client unzip wget zip postgresql-client \
19 | && apk add --no-cache --virtual build-dependencies autoconf build-base g++ make \
20 | && pecl install redis xdebug-3.1.4 \
21 | && docker-php-ext-install bcmath opcache pdo_mysql pdo_pgsql pcntl \
22 | && docker-php-ext-enable bcmath opcache redis xdebug \
23 | && chmod +x /tmp/installComposer.sh \
24 | && /tmp/installComposer.sh \
25 | && chown www-data:www-data /usr/local/bin/composer \
26 | && apk del --purge autoconf build-dependencies g++ make \
27 | && chown -R www-data:www-data /var/www
28 |
29 | WORKDIR /var/www
30 |
31 | USER www-data:www-data
32 |
--------------------------------------------------------------------------------
/src/ResultStack.php:
--------------------------------------------------------------------------------
1 | items = $results;
26 | }
27 |
28 | /**
29 | * @param HealthResult $result
30 | *
31 | * @return Stack
32 | */
33 | public function push(HealthResult $result): ResultStack
34 | {
35 | return $this->stackPush($result);
36 | }
37 |
38 | /**
39 | * Determine if any results in the stack have failed
40 | *
41 | * @return bool
42 | */
43 | public function hasFailures(): bool
44 | {
45 | $hasFailure = false;
46 | foreach ($this->all() as $result) {
47 | if ($result->failed()) {
48 | $hasFailure = true;
49 | break;
50 | }
51 | }
52 |
53 | return $hasFailure;
54 | }
55 |
56 | /**
57 | * Determine if any results in the stack have warnings
58 | *
59 | * @return bool
60 | */
61 | public function hasWarnings(): bool
62 | {
63 | $hasWarning = false;
64 | foreach($this->all() as $result) {
65 | if ($result->warned()) {
66 | $hasWarning = true;
67 | break;
68 | }
69 | }
70 |
71 | return $hasWarning;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/ResultStackTest.php:
--------------------------------------------------------------------------------
1 | checkPassed = Mockery::mock(HealthResult::class);
24 | $this->checkPassed->shouldReceive('failed')->andReturn(false);
25 |
26 | $this->checkFailed = Mockery::mock(HealthResult::class);
27 | $this->checkFailed->shouldReceive('failed')->andReturn(true);
28 |
29 | $this->checkWarned = Mockery::mock(HealthResult::class);
30 | $this->checkWarned->shouldReceive('warned')->andReturn(true);
31 | $this->checkWarned->shouldReceive('failed')->andReturn(false);
32 |
33 | $this->stack = new ResultStack();
34 | parent::setUp();
35 | }
36 |
37 | /** @test */
38 | public function push_and_check_for_failures()
39 | {
40 | $this->stack->push($this->checkPassed);
41 | $this->assertSame([$this->checkPassed], $this->stack->all());
42 | $this->assertFalse($this->stack->hasFailures());
43 |
44 | $this->stack->merge([$this->checkFailed]);
45 | $this->assertSame([$this->checkPassed, $this->checkFailed], $this->stack->all());
46 | $this->assertTrue($this->stack->hasFailures());
47 | }
48 |
49 | /** @test */
50 | public function push_and_check_for_warnings()
51 | {
52 | $this->stack->replace([$this->checkWarned]);
53 | $this->assertFalse($this->stack->hasFailures());
54 | $this->assertTrue($this->stack->hasWarnings());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/HealthResultTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('title')->andReturn('Title');
23 | $check->shouldReceive('description')->andReturn('Description');
24 | $check->shouldReceive('status')->andReturn('Status');
25 |
26 | $this->resultSuccess = new HealthResult(HealthResult::RESULT_SUCCESS, $check);
27 | $this->resultWarning = new HealthResult(HealthResult::RESULT_WARNING, $check);
28 | $this->resultFailure = new HealthResult(HealthResult::RESULT_FAILURE, $check);
29 | }
30 |
31 | /** @test */
32 | public function result_helpers()
33 | {
34 | $this->assertTrue($this->resultSuccess->passed());
35 | $this->assertFalse($this->resultSuccess->warned());
36 | $this->assertFalse($this->resultSuccess->failed());
37 |
38 | $this->assertTrue($this->resultWarning->warned());
39 | $this->assertFalse($this->resultWarning->passed());
40 | $this->assertFalse($this->resultWarning->failed());
41 |
42 | $this->assertTrue($this->resultFailure->failed());
43 | $this->assertFalse($this->resultFailure->passed());
44 | $this->assertFalse($this->resultFailure->warned());
45 | }
46 |
47 | /** @test */
48 | public function information_about_health_check()
49 | {
50 | $this->assertSame('Title', $this->resultSuccess->title());
51 | $this->assertSame('Description', $this->resultSuccess->description());
52 | $this->assertSame('Status', $this->resultSuccess->status());
53 | $this->assertSame(HealthResult::RESULT_SUCCESS, $this->resultSuccess->result());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/templates/healthz.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Healthz
4 |
5 |
47 |
48 |
49 |
50 |
51 |
52 |
Health Check
53 |
54 |
55 | {% for result in results %}
56 | -
57 |
58 | {{ result.title }} {{ result.description }}
59 |
60 |
61 |
{{ result.status }}
62 |
63 | {% endfor %}
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/Support/HealthzArtisanCommand.php:
--------------------------------------------------------------------------------
1 | checks = $checks;
24 | }
25 |
26 | /**
27 | * Execute the console command
28 | *
29 | * @return int
30 | */
31 | public function handle(): int
32 | {
33 | if (count($this->checks->all()) == 0) {
34 | $this->comment("No health checks registered. Be sure to register Gentux\Healthz\Healthz in a service provider. See github.com/generationtux/php-healthz for more info.");
35 | return 0;
36 | }
37 |
38 | $results = $this->checks->run();
39 | foreach ($results->all() as $result) {
40 | $this->outputCheckResult($result);
41 | }
42 |
43 | if ($results->hasFailures()) {
44 | return 1;
45 | }
46 |
47 | return 0;
48 | }
49 |
50 | /**
51 | * Output message about health check result
52 | *
53 | * @param HealthResult $result
54 | *
55 | * @return void
56 | */
57 | protected function outputCheckResult(HealthResult $result): void
58 | {
59 | $message = $result->title() . ": " . $result->status();
60 |
61 | switch ($result->result()) {
62 | case HealthResult::RESULT_SUCCESS:
63 | $this->info($message);
64 | break;
65 | case HealthResult::RESULT_WARNING:
66 | $this->warn($message);
67 | break;
68 | case HealthResult::RESULT_FAILURE:
69 | $this->error($message);
70 | break;
71 | default:
72 | $this->comment($message);
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/HealthResult.php:
--------------------------------------------------------------------------------
1 | result = $result;
30 | $this->check = $check;
31 | }
32 |
33 | /**
34 | * Determine if the result is a failure
35 | *
36 | * @return bool
37 | */
38 | public function failed(): bool
39 | {
40 | return $this->result() === self::RESULT_FAILURE;
41 | }
42 |
43 | /**
44 | * Determine if the result is a success
45 | *
46 | * @return bool
47 | */
48 | public function passed(): bool
49 | {
50 | return $this->result() === self::RESULT_SUCCESS;
51 | }
52 |
53 | /**
54 | * Determine if the result is a warning
55 | *
56 | * @return bool
57 | */
58 | public function warned(): bool
59 | {
60 | return $this->result() === self::RESULT_WARNING;
61 | }
62 |
63 | /** Getters: information about the health check */
64 |
65 | /**
66 | * @return string
67 | */
68 | public function title(): string
69 | {
70 | return $this->check->title();
71 | }
72 |
73 | /**
74 | * @return null|string
75 | */
76 | public function description(): ?string
77 | {
78 | return $this->check->description();
79 | }
80 |
81 | /**
82 | * @return string|null
83 | */
84 | public function status(): ?string
85 | {
86 | return $this->check->status();
87 | }
88 |
89 | /**
90 | * @return int
91 | */
92 | public function result(): int
93 | {
94 | return $this->result;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Support/HealthzServiceProvider.php:
--------------------------------------------------------------------------------
1 | app, 'post')) {
23 | $this->app->get('/healthz', $this->healthzHandler());
24 | $this->app->get('/healthz/ui', $this->healthzUIHandler());
25 | } else {
26 | \Route::get('/healthz', $this->healthzHandler());
27 | \Route::get('/healthz/ui', $this->healthzUIHandler());
28 | }
29 |
30 | if ($this->app->runningInConsole()) {
31 | $this->commands([HealthzArtisanCommand::class]);
32 | }
33 | }
34 |
35 | protected function healthzHandler()
36 | {
37 | return function() {
38 | $healthz = app(Healthz::class);
39 | $results = $healthz->run();
40 | if ($results->hasFailures()) {
41 | return response('fail', 500);
42 | }
43 |
44 | return response('ok', 200);
45 | };
46 | }
47 |
48 | protected function healthzUIHandler()
49 | {
50 | return function() {
51 | $username = getenv('HEALTHZ_USERNAME');
52 | $password = getenv('HEALTHZ_PASSWORD');
53 | if ($username != "") {
54 | if (
55 | Arr::get($_SERVER, 'PHP_AUTH_USER') !== $username ||
56 | Arr::get($_SERVER, 'PHP_AUTH_PW') !== $password
57 | ) {
58 | return response('Invalid credentials', 401, ['WWW-Authenticate' => 'Basic']);
59 | }
60 | }
61 |
62 | $healthz = app(Healthz::class);
63 | $results = $healthz->run();
64 | $html = $healthz->html($results);
65 |
66 | $status = 200;
67 | if ($results->hasFailures()) {
68 | $status = 500;
69 | }
70 |
71 | return response($html, $status)->header('Content-Type', 'text/html');
72 | };
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/HealthzTest.php:
--------------------------------------------------------------------------------
1 | check1 = Mockery::mock(HealthCheck::class);
27 | $this->check2 = Mockery::mock(HealthCheck::class);
28 | $this->check3 = Mockery::mock(HealthCheck::class);
29 |
30 | $this->healthz = new Healthz([$this->check1, $this->check2]);
31 | }
32 |
33 | /** @test */
34 | public function get_set_of_health_checks()
35 | {
36 | $result = $this->healthz->all();
37 | $this->assertCount(2, $result);
38 | $this->assertSame($this->check1, $result[0]);
39 | $this->assertSame($this->check2, $result[1]);
40 | }
41 |
42 | /** @test */
43 | public function push_new_health_checks_onto_the_stack()
44 | {
45 | $result = $this->healthz->push($this->check3);
46 | $this->assertSame($this->healthz, $result);
47 |
48 | $this->assertCount(3, $this->healthz->all());
49 | }
50 |
51 | /** @test */
52 | public function run_health_checks_and_return_result_stack()
53 | {
54 | $this->healthz->push($this->check3);
55 |
56 | # success
57 | $this->check1->shouldReceive('run');
58 |
59 | # warning
60 | $this->check2->shouldReceive('run')->andThrow(new HealthWarningException('warning'));
61 | $this->check2->shouldReceive('setStatus')->with('warning')->once();
62 |
63 | # failure
64 | $this->check3->shouldReceive('run')->andThrow(new Exception('failure'));
65 | $this->check3->shouldReceive('setStatus')->with('failure')->once();
66 |
67 | $result = $this->healthz->run();
68 | $this->assertInstanceOf(ResultStack::class, $result);
69 | $this->assertTrue($result->all()[0]->passed());
70 | $this->assertTrue($result->all()[1]->warned());
71 | $this->assertTrue($result->all()[2]->failed());
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Healthz.php:
--------------------------------------------------------------------------------
1 | items = $healthChecks;
28 | }
29 |
30 | /**
31 | * Push new health check onto the stack
32 | *
33 | * @param HealthCheck $healthCheck
34 | *
35 | * @return $this
36 | */
37 | public function push(HealthCheck $healthCheck): self
38 | {
39 | return $this->stackPush($healthCheck);
40 | }
41 |
42 | /**
43 | * Run the health checks in the stack
44 | *
45 | * @return ResultStack
46 | */
47 | public function run(): ResultStack
48 | {
49 | $results = [];
50 |
51 | foreach($this->all() as $check) {
52 | $resultCode = HealthResult::RESULT_SUCCESS;
53 |
54 | try {
55 | $check->run();
56 | } catch (Exception $e) {
57 | $check->setStatus($e->getMessage());
58 | $resultCode = $e instanceof HealthWarningException ? HealthResult::RESULT_WARNING : HealthResult::RESULT_FAILURE;
59 | }
60 |
61 | $results[] = new HealthResult($resultCode, $check);
62 | }
63 |
64 | return new ResultStack($results);
65 | }
66 |
67 | /**
68 | * Generate the HTML view for the health checks
69 | *
70 | * NOTE: this will run the health checks if a result stack is not passed in
71 | *
72 | * @param ResultStack $results
73 | *
74 | * @return string
75 | */
76 | public function html(ResultStack $results = null): string
77 | {
78 | if ($results === null) {
79 | $results = $this->run();
80 | }
81 |
82 | $loader = new TwigArrayLoader([
83 | 'healthz' => file_get_contents(__DIR__ . '/../templates/healthz.html'),
84 | ]);
85 | $twig = new TwigEnvironment($loader);
86 |
87 | return $twig->render('healthz', ['results' => $results->all()]);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/tests/Checks/Laravel/DatabaseHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | manager = Mockery::mock(DatabaseManager::class);
24 | $this->db = new DatabaseHealthCheck($this->manager);
25 | }
26 |
27 | /** @test */
28 | public function instance_of_health_check()
29 | {
30 | $this->assertInstanceOf(HealthCheck::class, $this->db);
31 | }
32 |
33 | /** @test */
34 | public function sets_connection_name()
35 | {
36 | $this->assertNull($this->db->connection());
37 |
38 | $this->db->setConnection('custom');
39 | $this->assertSame('custom', $this->db->connection());
40 | }
41 |
42 | /** @test */
43 | public function if_no_connection_is_set_use_the_description()
44 | {
45 | $description = $this->db->description();
46 | $this->assertSame('Check the database connection.', $description); # if connection is also null
47 |
48 | $this->db->setConnection('mysql');
49 | $description = $this->db->description();
50 | $this->assertSame('mysql', $description);
51 | }
52 |
53 | /** @test */
54 | public function uses_the_connection_name_set_to_resolve_a_laravel_db_connection()
55 | {
56 | $this->db->setConnection('custom');
57 |
58 | $conn = Mockery::mock(Connection::class)->makePartial();
59 | $this->manager->shouldReceive('connection')->with('custom')->once()
60 | ->andReturn($conn);
61 |
62 | $pdo = Mockery::mock(PDO::class);
63 | $conn->shouldReceive('getPdo')->once()
64 | ->andReturn($pdo);
65 |
66 | $this->db->run();
67 | $status = $this->db->status();
68 | $this->assertSame('connected', $status);
69 | }
70 |
71 | /**
72 | * @test
73 | */
74 | public function throws_health_failure_when_laravel_runs_into_trouble()
75 | {
76 | $this->expectException(\Gentux\Healthz\Exceptions\HealthFailureException::class);
77 | $this->manager->shouldReceive('connection')->andThrow(new \Exception());
78 | $this->db->run();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Checks/Laravel/DatabaseHealthCheck.php:
--------------------------------------------------------------------------------
1 | db = $db;
37 |
38 | if (!$this->db) {
39 | try { $this->db = app('db'); } catch (Exception $e) {
40 | throw new HealthFailureException('Cannot create instance of Laravel database manager.');
41 | }
42 | }
43 | }
44 |
45 | /**
46 | * Check database connection
47 | *
48 | * @return void
49 | *
50 | * @throws HealthFailureException
51 | */
52 | public function run(): void
53 | {
54 | try {
55 | $name = $this->connection();
56 | $this->db->connection($name)->getPdo();
57 | } catch (Exception $e) {
58 | throw new HealthFailureException($e->getMessage());
59 | }
60 |
61 | $this->setStatus('connected');
62 | }
63 |
64 | /**
65 | * Get the connection name
66 | *
67 | * @return null|string
68 | */
69 | public function connection(): ?string
70 | {
71 | return $this->connection;
72 | }
73 |
74 | /**
75 | * Set the connection name
76 | *
77 | * @param string $connection
78 | *
79 | * @return void
80 | */
81 | public function setConnection($connection): void
82 | {
83 | $this->connection = $connection;
84 | }
85 |
86 | /**
87 | * If no description property is defined, use the connection
88 | * name instead ('default' if connection is also null).
89 | *
90 | * @return string
91 | */
92 | public function description(): string
93 | {
94 | return $this->connection() ?: $this->description;;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/HealthCheck.php:
--------------------------------------------------------------------------------
1 | title;
45 |
46 | if (!$title) {
47 | $classTitle = explode('\\', get_class($this));
48 | $title = array_pop($classTitle);
49 | }
50 |
51 | return $title;
52 | }
53 |
54 | /**
55 | * Set the title for the health check
56 | *
57 | * @param string $title
58 | *
59 | * @return $this
60 | */
61 | public function setTitle($title): self
62 | {
63 | $this->title = $title;
64 |
65 | return $this;
66 | }
67 |
68 | /**
69 | * Get description for the health check.
70 | *
71 | * @return null|string
72 | */
73 | public function description(): ?string
74 | {
75 | return $this->description;
76 | }
77 |
78 | /**
79 | * Set the description for the health check
80 | *
81 | * @param string $description
82 | *
83 | * @return $this
84 | */
85 | public function setDescription($description): self
86 | {
87 | $this->description = $description;
88 |
89 | return $this;
90 | }
91 |
92 | /**
93 | * Get the status of the health check
94 | *
95 | * NOTE: If an exception is thrown, the status message for a health
96 | * check will be replaced with the exceptions message.
97 | *
98 | * @return null|string
99 | */
100 | public function status(): ?string
101 | {
102 | return $this->status;
103 | }
104 |
105 | /**
106 | * Set the status of the health check
107 | *
108 | * @param string $status
109 | *
110 | * @return $this
111 | */
112 | public function setStatus($status): self
113 | {
114 | $this->status = $status;
115 |
116 | return $this;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/Checks/General/MemcachedHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | memcached = Mockery::mock(Memcached::class);
29 | $this->health = new MemcachedHealthCheck($this->memcached);
30 | }
31 |
32 | /** @test */
33 | public function instance_of_health_check()
34 | {
35 | $this->assertInstanceOf(HealthCheck::class, $this->health);
36 | }
37 |
38 | /** @test */
39 | public function add_servers()
40 | {
41 | $this->health->addServer('123.com');
42 | $this->health->addServer('456.com', 2222, 1);
43 |
44 | $expect = [
45 | ['123.com', 11211, 0],
46 | ['456.com', 2222, 1],
47 | ];
48 | $this->assertSame($expect, $this->health->servers());
49 | }
50 |
51 | /** @test */
52 | public function set_options()
53 | {
54 | $this->health->setOptions([1 => 'foo']);
55 | $this->assertSame([1 => 'foo'], $this->health->options());
56 | }
57 |
58 | /** @test */
59 | public function username_and_password()
60 | {
61 | $this->health->setAuth('user', 'secret');
62 |
63 | $this->assertSame('user', $this->health->username());
64 | $this->assertSame('secret', $this->health->password());
65 | }
66 |
67 | /** @test */
68 | public function run_builds_memcached_instance_and_tests_connection()
69 | {
70 | $this->health->addServer('123.com');
71 | $this->health->addServer('456.com', 2222, 1);
72 | $this->health->setAuth('user', 'secret');
73 | $this->health->setOptions(['foo' => 'bar']);
74 |
75 | # spy on memcached instance
76 | $servers = [ ['123.com', 11211, 0], ['456.com', 2222, 1] ];
77 | $this->memcached->shouldReceive('addServers')->with($servers)->once();
78 | $this->memcached->shouldReceive('setOptions')->with(['foo' => 'bar'])->once();
79 | $this->memcached->shouldReceive('setSaslAuthData')->with('user', 'secret')->once();
80 | $this->memcached->shouldReceive('set')->with('test.connection', 'success', 1)->once()->andReturn(true);
81 |
82 | $this->health->run();
83 | $this->assertTrue(true);
84 | }
85 |
86 | /**
87 | * @test
88 | */
89 | public function run_throws_failure_exception_if_memcached_cant_set_test_value()
90 | {
91 | $this->expectException(\Gentux\Healthz\Exceptions\HealthFailureException::class);
92 | $this->memcached->shouldReceive('set')->with('test.connection', 'success', 1)->once()->andReturn(false);
93 | $this->health->run();
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/tests/Checks/Laravel/QueueHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | manager = Mockery::mock(QueueManager::class);
26 | $this->queue = new QueueHealthCheck($this->manager);
27 | }
28 |
29 | /** @test */
30 | public function instance_of_health_check()
31 | {
32 | $this->assertInstanceOf(HealthCheck::class, $this->queue);
33 | }
34 |
35 | /** @test */
36 | public function sets_queue_name()
37 | {
38 | $this->assertNull($this->queue->name());
39 |
40 | $this->queue->setName('custom');
41 | $this->assertSame('custom', $this->queue->name());
42 | }
43 |
44 | /** @test */
45 | public function if_no_connection_is_set_use_the_description()
46 | {
47 | $description = $this->queue->description();
48 | $this->assertSame('Check the queue connection.', $description);
49 |
50 | $this->queue->setName('sqs');
51 | $description = $this->queue->description();
52 | $this->assertSame('sqs', $description);
53 | }
54 |
55 | /** @test */
56 | public function checks_connection_status_of_sqs_queue()
57 | {
58 | $this->queue->setName('custom');
59 |
60 | # laravel sqs queue service
61 | $sqsQueue = Mockery::mock(SqsQueue::class);
62 | $this->manager->shouldReceive('connection')->with('custom')->once()->andReturn($sqsQueue);
63 |
64 | # need url of queue to check attributes on SQS
65 | $sqsQueue->shouldReceive('getQueue')->andReturn('some-queue-url.com');;
66 |
67 | # sqs service to check connection
68 | $sqs = Mockery::mock(SqsClient::class);
69 | $sqsQueue->shouldReceive('getSqs')->andReturn($sqs);
70 |
71 | # make sure a call is made using sqs client to get queue attributes
72 | $sqs->shouldReceive('getQueueAttributes')->with(['QueueUrl' => 'some-queue-url.com'])->once();
73 |
74 | $this->queue->run();
75 | $status = $this->queue->status();
76 | $this->assertSame('connected to SQS', $status);
77 | }
78 |
79 | /** @test */
80 | public function checks_status_of_sync_queue()
81 | {
82 | $sync = Mockery::mock(SyncQueue::class);
83 | $this->manager->shouldReceive('connection')->andReturn($sync);
84 |
85 | $this->queue->run();
86 |
87 | $status = $this->queue->status();
88 | $this->assertSame('connected to Sync queue', $status);
89 | }
90 |
91 | /**
92 | * @test
93 | */
94 | public function throws_warning_if_queue_driver_is_not_supported()
95 | {
96 | $this->expectException(\Gentux\Healthz\Exceptions\HealthWarningException::class);
97 | $redis = Mockery::mock(RedisQueue::class);
98 | $this->manager->shouldReceive('connection')->andReturn($redis);
99 |
100 | $this->queue->run();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Checks/General/MemcachedHealthCheck.php:
--------------------------------------------------------------------------------
1 | memcached = $memcached ?: new Memcached();
29 | }
30 |
31 | /**
32 | * Check for connection to memcached servers
33 | *
34 | * @return void
35 | *
36 | * @throws HealthFailureException
37 | */
38 | public function run(): void
39 | {
40 | if (count($this->servers())) {
41 | $this->memcached->addServers($this->servers());
42 | }
43 |
44 | if (count($this->options())) {
45 | $this->memcached->setOptions($this->options());
46 | }
47 |
48 | if (!is_null($this->username())) {
49 | $this->memcached->setSaslAuthData($this->username(), $this->password());
50 | }
51 |
52 | $result = $this->memcached->set('test.connection', 'success', 1);
53 | if (!$result) {
54 | throw new HealthFailureException('Unable to set test value in memcache');
55 | }
56 |
57 | $this->setStatus('able to set test value in memcache');
58 | }
59 |
60 | /**
61 | * Add server to check
62 | *
63 | * @param string $server
64 | * @param int $port
65 | * @param int $weight
66 | *
67 | * @return self
68 | */
69 | public function addServer($server, $port = 11211, $weight = 0): HealthCheck
70 | {
71 | $this->servers[] = [$server, $port, $weight];
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Get servers
78 | *
79 | * @return array
80 | */
81 | public function servers(): array
82 | {
83 | return $this->servers;
84 | }
85 |
86 | /**
87 | * Set memcached options
88 | *
89 | * @param array $options
90 | *
91 | * @return self
92 | */
93 | public function setOptions(array $options): HealthCheck
94 | {
95 | $this->options = $options;
96 |
97 | return $this;
98 | }
99 |
100 | /**
101 | * Get options
102 | *
103 | * @return array
104 | */
105 | public function options(): array
106 | {
107 | return $this->options;
108 | }
109 |
110 | /**
111 | * Set username and password for servers
112 | *
113 | * @param string $username
114 | * @param string $password
115 | *
116 | * @return self
117 | */
118 | public function setAuth($username, $password): HealthCheck
119 | {
120 | $this->username = $username;
121 | $this->password = $password;
122 |
123 | return $this;
124 | }
125 |
126 | /**
127 | * Get username
128 | *
129 | * @return string|null
130 | */
131 | public function username(): ?string
132 | {
133 | return $this->username;
134 | }
135 |
136 | /**
137 | * Get password
138 | *
139 | * @return string|null
140 | */
141 | public function password(): ?string
142 | {
143 | return $this->password;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/Checks/Laravel/QueueHealthCheck.php:
--------------------------------------------------------------------------------
1 | queue = $queue;
40 |
41 | if (!$this->queue) {
42 | try { $this->queue = app('queue'); } catch (Exception $e) {
43 | throw new HealthFailureException('Cannot create instance of Laravels queue manager.');
44 | }
45 | }
46 | }
47 |
48 | /**
49 | * Check database connection
50 | *
51 | * @return void
52 | *
53 | * @throws HealthFailureException
54 | * @throws HealthWarningException
55 | */
56 | public function run(): void
57 | {
58 | $name = $this->name();
59 | $queue = $this->queue->connection($name);
60 |
61 | if ($queue instanceof SqsQueue) {
62 | $this->runSqsCheck($queue);
63 | } elseif ($queue instanceof SyncQueue) {
64 | $this->runSyncCheck($queue);
65 | } else {
66 | throw new HealthWarningException('Only SQS and Sync queue drivers supported at this time.');
67 | }
68 | }
69 |
70 | /**
71 | * Run the health check against an sqs queue
72 | *
73 | * @param SqsQueue $queue
74 | *
75 | * @return void
76 | */
77 | protected function runSqsCheck(SqsQueue $queue): void
78 | {
79 | $url = $queue->getQueue(null);
80 | $queue->getSqs()->getQueueAttributes(['QueueUrl' => $url]);
81 |
82 | $this->setStatus('connected to SQS');
83 | }
84 |
85 | /**
86 | * Nothing to really check with a sync queue, will
87 | * just set the status.
88 | *
89 | * @param SyncQueue $queue
90 | *
91 | * @return void
92 | */
93 | protected function runSyncCheck(SyncQueue $queue): void
94 | {
95 | $this->setStatus('connected to Sync queue');
96 | }
97 |
98 | /**
99 | * Get the queue name
100 | *
101 | * @return null|string
102 | */
103 | public function name(): ?string
104 | {
105 | return $this->name;
106 | }
107 |
108 | /**
109 | * Set the queue name
110 | *
111 | * @param string $name
112 | *
113 | * @return void
114 | */
115 | public function setName($name): void
116 | {
117 | $this->name = $name;
118 | }
119 |
120 | /**
121 | * If no description property is defined, use the queue name instead.
122 | *
123 | * @return string|null
124 | */
125 | public function description(): ?string
126 | {
127 | return $this->name() ?: $this->description;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/tests/Checks/General/HttpHealthCheckTest.php:
--------------------------------------------------------------------------------
1 | request = Mockery::mock(Request::class);
29 | $this->guzzle = Mockery::mock(Client::class);
30 | $this->http = new HttpHealthCheck($this->request, 200, [], $this->guzzle);
31 | }
32 |
33 | /** @test */
34 | public function instance_of_health_check()
35 | {
36 | $this->assertInstanceOf(HealthCheck::class, $this->http);
37 | }
38 |
39 | /** @test */
40 | public function get_request_to_be_made()
41 | {
42 | $result = $this->http->request();
43 | $this->assertSame($this->request, $result);
44 |
45 | $newRequest = Mockery::mock(Request::class);
46 | $this->http->setRequest($newRequest);
47 | $this->assertSame($newRequest, $this->http->request());
48 | }
49 |
50 | /** @test */
51 | public function gets_expected_status_code_for_request()
52 | {
53 | $result = $this->http->expectedStatusCode();
54 | $this->assertSame(200, $result);
55 |
56 | $this->http->setExpectedStatusCode(404);
57 | $result = $this->http->expectedStatusCode();
58 | $this->assertSame(404, $result);
59 | }
60 |
61 | /** @test */
62 | public function gets_guzzle_options()
63 | {
64 | $result = $this->http->guzzleOptions();
65 | $this->assertSame([], $result);
66 |
67 | $this->http->setGuzzleOptions(['foo' => 'bar']);
68 | $result = $this->http->guzzleOptions();
69 | $this->assertSame(['foo' => 'bar'], $result);
70 | }
71 |
72 | /** @test */
73 | public function run_sends_the_request()
74 | {
75 | $response = Mockery::mock(Response::class);
76 | $response->shouldReceive('getStatusCode')->andReturn(200);
77 |
78 | $this->guzzle->shouldReceive('send')->with($this->request, [])->once()->andReturn($response);
79 |
80 | $this->http->run();
81 | $this->assertTrue(true);
82 | }
83 |
84 | /**
85 | * @test
86 | */
87 | public function run_throws_an_exception_if_the_expected_response_code_doesnt_match()
88 | {
89 | $this->expectException(\Gentux\Healthz\Exceptions\HealthFailureException::class);
90 | $response = Mockery::mock(Response::class);
91 | $response->shouldReceive('getStatusCode')->andReturn(201);
92 |
93 | $this->guzzle->shouldReceive('send')->andReturn($response);
94 |
95 | $this->http->run();
96 | }
97 |
98 | /** @test */
99 | public function run_catches_guzzle_exceptions_to_compare_status_code()
100 | {
101 | $response = Mockery::mock(Response::class);
102 | $response->shouldReceive('getStatusCode')->andReturn(404);
103 |
104 | $e = Mockery::mock(RequestException::class);
105 | $e->shouldReceive('getResponse')->andReturn($response);
106 |
107 | $this->guzzle->shouldReceive('send')->andThrow($e);
108 |
109 | $this->http->setExpectedStatusCode(404);
110 | $this->http->run(); // no exceptions, should pass
111 | $this->assertTrue(true);
112 | }
113 |
114 | /** @test */
115 | public function if_no_description_is_set_the_request_uri_is_used()
116 | {
117 | $this->request->shouldReceive('getUri')->andReturn(new Uri('/somewhere'));
118 | $description = $this->http->description();
119 |
120 | $this->assertSame('/somewhere', $description);
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Checks/General/HttpHealthCheck.php:
--------------------------------------------------------------------------------
1 | send */
29 | protected $guzzleOptions;
30 |
31 | /** @var Client */
32 | protected $guzzle;
33 |
34 | public function __construct(Request $request, $expectedStatusCode = 200, array $guzzleOptions = [], Client $guzzle = null)
35 | {
36 | $this->request = $request;
37 | $this->expectedStatusCode = $expectedStatusCode;
38 | $this->guzzleOptions = $guzzleOptions;
39 | $this->guzzle = $guzzle ?: new Client($this->guzzleOptions);
40 | }
41 |
42 | /**
43 | * Send the request
44 | *
45 | * @return ResponseInterface
46 | *
47 | * @throws HealthFailureException|RequestException
48 | */
49 | public function run(): ResponseInterface
50 | {
51 | try {
52 | $response = $this->guzzle()->send(
53 | $this->request(),
54 | $this->guzzleOptions()
55 | );
56 | } catch (RequestException $e) {
57 | if (!$response = $e->getResponse()) {
58 | throw $e;
59 | }
60 | }
61 |
62 | if ($response->getStatusCode() !== $this->expectedStatusCode()) {
63 | $message = "Status code {$response->getStatusCode()} does not match expected {$this->expectedStatusCode()}";
64 | throw new HealthFailureException($message);
65 | }
66 |
67 | return $response;
68 | }
69 |
70 | /**
71 | * Get request object to send
72 | *
73 | * @return Request
74 | */
75 | public function request()
76 | {
77 | return $this->request;
78 | }
79 |
80 | /**
81 | * Set request object to send
82 | *
83 | * @param Request $request
84 | *
85 | * @return $this
86 | */
87 | public function setRequest(Request $request)
88 | {
89 | $this->request = $request;
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * Get the expected status code for the request
96 | *
97 | * @return int
98 | */
99 | public function expectedStatusCode()
100 | {
101 | return $this->expectedStatusCode;
102 | }
103 |
104 | /**
105 | * Set the expected status code from the request
106 | *
107 | * @param $statusCode
108 | *
109 | * @return $this
110 | */
111 | public function setExpectedStatusCode($statusCode)
112 | {
113 | $this->expectedStatusCode = $statusCode;
114 |
115 | return $this;
116 | }
117 |
118 | /**
119 | * Get Guzzle client options
120 | *
121 | * @return array
122 | */
123 | public function guzzleOptions()
124 | {
125 | return $this->guzzleOptions;
126 | }
127 |
128 | /**
129 | * Set options for Guzzle client
130 | *
131 | * These will be passed as the request options on client->send
132 | * @see http://docs.guzzlephp.org/en/latest/request-options.html
133 | *
134 | * @param array $guzzleOptions
135 | *
136 | * @return $this
137 | */
138 | public function setGuzzleOptions(array $guzzleOptions)
139 | {
140 | $this->guzzleOptions = $guzzleOptions;
141 |
142 | return $this;
143 | }
144 |
145 | /**
146 | * Get Guzzle client instance
147 | *
148 | * @return \GuzzleHttp\Client
149 | */
150 | public function guzzle()
151 | {
152 | return $this->guzzle;
153 | }
154 |
155 | /**
156 | * If no description is set, we will use the request URL
157 | *
158 | * @return string
159 | */
160 | public function description(): ?string
161 | {
162 | $description = $this->description;
163 |
164 | if (!$description) {
165 | $description = (string) $this->request()->getUri();
166 | }
167 |
168 | return $description;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://codeclimate.com/github/generationtux/php-healthz)
3 | [](https://codeclimate.com/github/generationtux/php-healthz/coverage)
4 |
5 | # PHP Healthz
6 |
7 | Health checking for PHP apps with built-in support for Laravel.
8 |
9 |
10 |
11 | Get an easy overview of the health of your app! Implement a health check endpoint for load balancers, or your own sanity :) Comes with an optional UI and set of pre-configured checks you can use, and is extensible
12 | to add custom health checks to the stack as well.
13 |
14 | - [Setup and usage](#setup)
15 | - [Laravel](#laravel)
16 | - [General PHP](#general-php)
17 | - [Available checks and config](#check-configuration)
18 | - [HTTP](#http-check)
19 | - [Memcached](#memcached-check)
20 | - [Debug](#debug-check)
21 | - [Env)](#env-check)
22 | - [Database (Laravel)](#laravel-database)
23 | - [Queue (Laravel)](#laravel-queue)
24 | - [Creating custom checks](#custom-checks)
25 |
26 | ---
27 |
28 | ## Setup
29 |
30 | ```sh
31 | $ composer require generationtux/healthz
32 | ```
33 |
34 | ### Laravel < 5.4
35 |
36 | (the following should work with Lumen as well, with minor differences)
37 |
38 | **Add the service provider that will register the default health checks and routes**
39 |
40 | ```php
41 | // config/app.php
42 | 'providers' => [
43 | Illuminate...,
44 | Gentux\Healthz\Support\HealthzServiceProvider::class,
45 | ]
46 | ```
47 |
48 | You should be able to visit `/healthz/ui` to see the default Laravel health checks, or run `php artisan healthz` to get a CLI view.
49 |
50 | To add basic auth to the UI page, set the `HEALTHZ_USERNAME` and `HEALTHZ_PASSWORD` environment variables.
51 | Even if the UI has basic auth, the simplified `/healthz` endpoint will always be available to respond with a simple `ok` or `fail` for load balancers and other automated checks to hit.
52 |
53 | **In order to customize the health checks, simply register `Gentux\Healthz\Healthz` in your app service provider (probably `app/Providers/AppServiceProvider.php`) to build a custom Healthz instance.**
54 |
55 | ```php
56 | use Gentux\Healthz\Healthz;
57 | use Illuminate\Support\ServiceProvider;
58 | use Gentux\Healthz\Checks\General\EnvHealthCheck;
59 | use Gentux\Healthz\Checks\Laravel\DatabaseHealthCheck;
60 |
61 | class AppServiceProvider extends ServiceProvider
62 | {
63 | public function register()
64 | {
65 | $this->app->bind(Healthz::class, function () {
66 | $env = new EnvHealthCheck();
67 | $db = new DatabaseHealthCheck();
68 | $db->setConnection("non-default");
69 |
70 | return new Healthz([$env, $db]);
71 | });
72 | }
73 | }
74 | ```
75 |
76 | [See more about configuring available checks](#check-configuration)
77 |
78 | ---
79 |
80 | ### General PHP
81 |
82 | **Build an instance of the health check**
83 |
84 | ```php
85 | addServer("127.0.0.1");
90 | $healthz = new Healthz([$memcached]);
91 | ```
92 |
93 | **Run the checks and review results**
94 |
95 | ```php
96 | // @var $results Gentux\Healthz\ResultStack
97 | $results = $healthz->run();
98 |
99 | if ($results->hasFailures()) {
100 | // oh no
101 | }
102 |
103 | if ($results->hasWarnings()) {
104 | // hmm
105 | }
106 |
107 | foreach ($results->all() as $result) {
108 | // @var $result Gentux\Healthz\HealthResult
109 | if ($result->passed() || $result->warned() || $result->failed()) {
110 | echo "it did one of those things at least";
111 | }
112 |
113 | echo "{$result->title()}: {$result->status()} ({$result->description()})";
114 | }
115 | ```
116 |
117 | **Get the UI view**
118 |
119 | ```php
120 | $html = $healthz->html();
121 | ```
122 |
123 | ---
124 |
125 | ## Check configuration
126 |
127 | - [HTTP](#http-check)
128 | - [Memcached](#memcached-check)
129 | - [Debug](#debug-check)
130 | - [Env](#env-check)
131 | - [Database (Laravel)](#laravel-database)
132 | - [Queue (Laravel)](#laravel-queue)
133 |
134 | #### HTTP
135 |
136 |
137 | Create a new [Guzzle Request](http://docs.guzzlephp.org/en/latest/psr7.html) to pass to the constuctor of the HTTP health check.
138 |
139 | ```php
140 | use GuzzleHTTP\PSR7\Request;
141 | use Gentux\Healthz\Checks\General\HttpHealthCheck;
142 |
143 | $request = new Request("GET", "http://example.com");
144 | $httpCheck = new HttpHealthCheck($request);
145 | ```
146 |
147 | You can optionally pass the expected response status code (defaults to `200`), as well as Guzzle client options.
148 |
149 | ```php
150 | $httpCheck = new HttpHealthCheck($request, 204, [
151 | "base_url" => "http://example.com",
152 | ]);
153 | ```
154 |
155 | #### Memcached
156 |
157 |
158 | Create a new Memcached health check and use the methods `addServer` and `setOptions`.
159 |
160 | ```php
161 | use Gentux\Healthz\Checks\General\MemcachedHealthCheck;
162 |
163 | $memcachedCheck = new MemcachedHealthCheck();
164 | $memcachedCheck->addServer($server, $port = 11211, $weight = 0);
165 | $memcachedCheck->setOptions([]);
166 | ```
167 |
168 | _See [Memcached setOptions](http://php.net/manual/en/memcached.setoptions.php) for option information._
169 |
170 | #### Debug
171 |
172 |
173 | Set the environment variable to check if the app is running in debug. If this check fails, it emits a warning.
174 |
175 | ```php
176 | use Gentux\Healthz\Checks\General\DebugHealthCheck;
177 |
178 | $debugCheck = new DebugHealthCheck("APP_DEBUG");
179 | ```
180 |
181 | In this case, if `APP_DEBUG` == `'true'` then this check will emit a warning.
182 |
183 | #### Env
184 |
185 |
186 | Provide an environment variable name to check for the apps environment. If the provided env name is found the check will always be successful and simply output the name. If no environment variable is set the check will emit a warning.
187 |
188 | ```php
189 | use Gentux\Healthz\Checks\General\EnvHealthCheck;
190 |
191 | $envCheck = new EnvHealthCheck("APP_ENV");
192 | ```
193 |
194 | #### Database (Laravel)
195 |
196 |
197 | This will use Laravel's built in database service to verify connectivity. You may optionally set a connection name to use (will use the default if not provided).
198 |
199 | ```php
200 | use Gentux\Healthz\Checks\Laravel\DatabaseHealthCheck;
201 |
202 | $dbCheck = new DatabaseHealthCheck();
203 | $dbCheck->setConnection("my_conn"); // optional
204 | ```
205 |
206 | #### Queue (Laravel)
207 |
208 |
209 | The queue health check currently supports `sync` and `sqs` queues. You may optionally set the queue name to use (will use the default if not specified).
210 |
211 | ```php
212 | use Gentux\Healthz\Checks\Laravel\QueueHealthCheck;
213 |
214 | $queueCheck = new QueueHealthCheck();
215 | $queueCheck->setName("my_queue"); // optional
216 | ```
217 |
218 | ---
219 |
220 | ## Custom checks
221 |
222 | _Note: Checks may have one of 3 statuses (`success`, `warning`, or `failure`). Any combination of success and warning and the stack as a whole will be considered to be successful.
223 | Any single failure, however, will consider the stack to be failed._
224 |
225 | To create a custom health check, you should extend `Gentux\Healthz\HealthCheck` and implement the one abstract method `run()`.
226 |
227 | ```php
228 |