Skip to content

Generic monitoring solution for web applications

License

Notifications You must be signed in to change notification settings

CPS-IT/monitoring

Repository files navigation

Monitoring

Coverage Maintainability CGL Tests Supported PHP Versions

This package provides a generic monitoring solution for web applications. It can be used to monitor various parts of your application by implementing a set of monitoring providers and exposing their health state by using a dedicated monitoring route. The package is highly customizable in terms of implementing custom monitoring providers and authorization solutions.

🔥 Installation

Packagist Packagist Downloads

composer require cpsit/monitoring

⚡ Usage

Monitoring service

The package ships a Monitoring class which can be used to check health of various services.

Services can be defined using the MonitoringProvider interface. Each provider can reveal the health state of the underlain service using the isHealthy() method.

namespace My\Vendor\Monitoring\Provider;

use CPSIT\Monitoring\Provider\MonitoringProvider;
use My\Vendor\Service\ApiService;

final class ApiMonitoringProvider implements MonitoringProvider
{
    public function __construct(
        private readonly ApiService $apiService,
    ) {}

    public function getName(): string
    {
        return 'api';
    }

    public function isHealthy(): bool
    {
        try {
            $response = $this->apiService->request('/health', 'HEAD');
            return $response->getStatusCode() < 400;
        } catch (\Exception) {
            return false;
        }
    }
}

Report errors

In addition to the normal health state, providers might also be able to report errors which occurred during health check. For this, an ExceptionAwareMonitoringProvider interface exists. It allows to fetch errors using the getLastException() method.

 namespace My\Vendor\Monitoring\Provider;

-use CPSIT\Monitoring\Provider\MonitoringProvider;
+use CPSIT\Monitoring\Provider\ExceptionAwareMonitoringProvider;
 use My\Vendor\Service\ApiService;

-final class ApiMonitoringProvider implements MonitoringProvider
+final class ApiMonitoringProvider implements ExceptionAwareMonitoringProvider
 {
+    private ?\Throwable $lastException = null;
+
     public function __construct(
         private readonly ApiService $apiService,
     ) {}

     public function getName(): string
     {
         return 'api';
     }

     public function isHealthy(): bool
     {
         try {
             $response = $this->apiService->request('/health', 'HEAD');
             return $response->getStatusCode() < 400;
-        } catch (\Exception) {
+        } catch (\Exception $exception) {
+            $this->lastException = $exception;
             return false;
         }
     }
+
+    public function getLastException(): ?\Throwable
+    {
+        return $this->lastException;
+    }
 }

Provide individual status information

Each provider can be extended to return individual status information. This is possible once the StatusInformationAwareMonitoringProvider interface is implemented within a concrete provider.

 namespace My\Vendor\Monitoring\Provider;

-use CPSIT\Monitoring\Provider\MonitoringProvider;
+use CPSIT\Monitoring\Provider\StatusInformationAwareMonitoringProvider;
 use My\Vendor\Service\ApiService;

-final class ApiMonitoringProvider implements MonitoringProvider
+final class ApiMonitoringProvider implements StatusInformationAwareMonitoringProvider
 {
     public function __construct(
         private readonly ApiService $apiService,
     ) {}

     public function getName(): string
     {
         return 'api';
     }

     public function isHealthy(): bool
     {
         try {
             $response = $this->apiService->request('/health', 'HEAD');
             return $response->getStatusCode() < 400;
         } catch (\Exception) {
             return false;
         }
     }
+
+    /**
+     * @return array<string, string>
+     */
+    public function getStatusInformation(): array
+    {
+        $lastProcessDate = $this->apiService->getLastProcessDate();
+
+        return [
+            'last_process_date' => $lastProcessDate->format(\DateTimeInterface::RFC2822),
+        ];
+    }
 }

Middleware

Additionally, a middleware MonitoringMiddleware exists. It can be used to make health checks available using the middleware stack of a web application. A set of monitoring providers needs to be provided when constructing the middleware. This can be best achieved with a PSR-11 service container, e.g. within a Symfony or Symfony-based application.

In case the monitoring result is healthy, a 200 OK response will be returned, otherwise the response is 424 Failed Dependency. All responses are in JSON format and contain the serialized monitoring result. If an internal error occurs (such as failed JSON encoding), the response is 500 Internal Server Error and the response body contains the error message in JSON format.

Validators

The middleware acts only on valid requests. The decision as to whether a request is valid is in the hands of so called validators. Each validator must implement the Validator interface.

The package already provides the RouteValidator. It can be configured to allow only requests to a given route, which is /monitor/health by default. In case this route is matched, the request is valid and therefore the monitoring process will be triggered.

Authorizers

Requests handled by the provided middleware can be secured using a list of authorizers. Authorizers are classes which implement the Authorizer interface. Each authorizer must implement the isAuthorized() method. In case any authorizer returns true, the request will be processed as is. If no authorizer is able to give the appropriate authorization, a 401 Unauthorized response will be returned.

Authorizers can be prioritized by defining an explicit priority using the getPriority() method. Authorizers with higher priority will be executed first.

Dependency injection

The package already provides a ready-made container configuration for dependency injection based on Symfony dependency injection. This is particularly helpful if several monitoring providers are to be automatically configured on the middleware.

For this purpose, it is necessary to install the following packages via Composer:

composer require symfony/config symfony/dependency-injection symfony/yaml

Note

The dependency injection configuration is an optional component of this package. Therefore, required Composer packages are not explicitly required, but only suggested. You must install them by your own.

The next step is to load the configuration into the container. For this, the package provides a helper class ServiceConfigurator:

use CPSIT\Monitoring\DependencyInjection\ServiceConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;

return static function (ContainerBuilder $container): void {
    ServiceConfigurator::configure($container);
};

All classes that are configured in the service container and implement the MonitoringProvider interface are now automatically tagged with monitoring.provider. The MonitoringProviderCompilerPass then takes care of the autoconfiguration of all tagged monitoring providers at the MonitoringMiddleware class.

🧑‍💻 Contributing

Please have a look at CONTRIBUTING.md.

⭐ License

This project is licensed under GNU General Public License 3.0 (or later).