makinacorpus/monitoring-bundle

简单监控工具和Symfony组件

1.0.0-alpha7 2023-09-05 12:33 UTC

This package is auto-updated.

Last update: 2024-09-08 15:51:33 UTC


README

此API提供简单易用的接口,用于构建监督探测,您可以运行它们来检查应用程序的健全性。

它提供了一个与Symfony >= 4.4兼容的组件,用于将自定义探测注册到Symfony应用程序中。

特性

  • 构建类似于Nagios或Centreon等常用监督工具的输出结果的探测。
  • 构建更高级的信息收集器,用于构建高级状态报告。
  • 提供易于使用的探测和信息收集器注册表。
  • 提供一些控制台命令以运行探测或构建状态报告。
  • 通过Symfony组件使用时,提供简单的探测注册和HTTP端点以查询探测:易于与监督工具一起使用。

安装

使用composer安装

composer req makinacorpus/monitoring-bundle

Symfony组件

安装

然后在您的 app/bundles.php 中注册该组件

<?php

return [
    // Other bundles...
    MakinaCorpus\Monitoring\Bridge\Symfony\MonitoringBundle::class => ['all' => true],
];

配置

可选地,将 src/Bridge/Symfony/Resources/config/packages/monitoring.yaml 文件复制到您的应用 config/packages/ 文件夹。

注册HTTP状态端点(推荐)

状态端点始终返回纯文本响应,与Nagios解析器兼容,这意味着几乎每个开源监督工具都会理解这些状态报告。

自定义路由加载器将为每个探测生成一个路由。为了注册这些路由,请将以下代码添加到您的 config/routes.yaml

monitoring_endpoint:
    resource: "@MonitoringBundle/Resources/config/routes/endpoint.yaml"

为了使这些路由响应,您需要为用户生成一个访问令牌

bin/console monitoring:generate-token

按照屏幕上的说明操作,它将显示新的令牌和新的探测URL:您可以将新生成的令牌复制/粘贴到您的环境变量中

MONITORING_TOKEN=your-generated-token

请注意,您可以多次运行此命令,以便能够复制/粘贴探测URL,该命令永远不会修改您的 应用程序配置

如果您的站点受到防火墙的保护,您可以在 config/packages/security.yaml 中添加以下内容

security:
    firewalls:
        # Monitoring (access is token protected within controller)
        monitoring:
            pattern: ^/monitoring/status
            security: false

注册管理员报告屏幕(可选)

提供了一个基本的HTML报告控制器和模板,您可以将它添加到您的 config/routes.yaml 配置文件中

monitoring_admin:
    resource: "@MonitoringBundle/Resources/config/routes/admin.yaml"
    prefix: /admin

请注意,它不受安全检查,您必须手动配置防火墙以保护它。

构建自己的探测和报告

构建一个简单的探测

实现 \MakinaCorpus\Monitoring\Probe 接口。

declare(strict_types=1);

namespace App\Monitoring;

use MakinaCorpus\Monitoring\Probe;
use MakinaCorpus\Monitoring\ProbeStatus;

/**
 * Collects number of items to process in queue, raise error when it is too much.
 */
final class QueueSizeProbe implements Probe
{
    /** @var ?int */
    private $warningThreshold;

    /** @var ?int */
    private $criticalThreshold;

    /** @var \My\Favourite\Database\Client */
    private $database;

    public function __construct(
        \My\Favourite\Database\Client $database,
        ?int $warningThreshold = null,
        ?int $criticalThreshold = null
    ) {
        $this->database = $database;
        $this->warningThreshold = $warningThreshold;
        $this->criticalThreshold = $criticalThreshold;
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        // Internal name.
        return 'queue_size';
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle(): string
    {
        // Human readable name, for reports.
        return "Queue size";
    }

    /**
     * {@inheritdoc}
     */
    public function getStatus(): ProbeStatus
    {
        $queueSize = $this->database->query('SELECT COUNT(*) FROM "my_queue_table" WHERE "is_consumed" is false')->fetch();

        // For those who know Nagios or the like, just set a very short status
        // message intended for display purpose in larger reports.
        $message = \sprintf("my queue size: %d items", $queueSize);

        if ($queueSize >= $this->criticalThreshold) {
            return ProbeStatus::critical([$message, "queue is failing !"]);
        }
        if ($queueSize >= $this->warningThreshold) {
            return ProbeStatus::warning($message);
        }
        return ProbeStatus::ok($message);
    }
}

构建一个简单的报告生成器

实现 \MakinaCorpus\Monitoring\InfoCollector 接口

declare(strict_types=1);

namespace App\Monitoring;

use MakinaCorpus\Monitoring\InfoCollector;
use MakinaCorpus\Monitoring\Output\CollectionBuilder;

/**
 * Collects information about all database tables.
 */
final class DataInfoCollector implements InfoCollector
{
    /** @var \My\Favourite\Database\Client */
    private $database;

    public function __construct(\My\Favourite\Database\Client $database)
    {
        $this->database = $database;
    }

    /**
     * {@inheritdoc}
     */
    public function getName(): string
    {
        // Internal name.
        return 'data';
    }

    /**
     * {@inheritdoc}
     */
    public function getTags(): iterable
    {
        // Tags will help build specific reports.
        return ['database', 'data', 'volume'];
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle(): string
    {
        // Human readable name, for reports.
        return "Data information";
    }

    /**
     * {@inheritdoc}
     */
    public function info(CollectionBuilder $builder): void
    {
        // Yes, this should work with pgsql.
        $rows = $this->database->query(<<<SQL
SELECT 
    pg_size_pretty(pg_total_relation_size(relid))
        AS "total size",
    pg_size_pretty(pg_table_size(relid))
        AS "table size",
    pg_size_pretty(pg_indexes_size(relid))
        AS "index size",
    concat(schemaname, '.', relname),
    concat('seq_scan: ',seq_scan, E'\\nseq_tup_read: ', seq_tup_read, E'\\nidx_scan: ', idx_scan, E'\\nidx_tup_fetch: ', idx_tup_fetch)
        AS "reads",
    concat('insert: ',n_tup_ins, E'\\nupdate: ', n_tup_upd, E'\\nhot_update: ', n_tup_hot_upd, E'\\ndelete: ', n_tup_del)
        AS "writes",
    concat('live: ',n_live_tup, E'\\ndead: ', n_dead_tup, E'\\nmod_since_analyze: ', n_mod_since_analyze)
        AS "state",
    concat('last_vacuum: ',last_vacuum, 'auto: ', last_autovacuum, E'\\nlast_analyze: ', last_analyze, ' auto: ', last_autoanalyze, E'\\ncpt_vacuum: ', vacuum_count, ' auto: ', autovacuum_count, E'\\ncpt_analyze: ', analyze_count, ' auto: ', autoanalyze_count)
        AS "vacuum"
    FROM pg_stat_user_tables
    ORDER BY pg_total_relation_size(relid) DESC
SQL
        );

        $table = $builder->addTable()->setHeaders([
            'table', 'total_size', // ...
        ]);

        foreach ($rows as $row) {
            $table->addRow([
                $row['relname'],
                $row['total size'],
                // ...
            ]);
        }
    }
}

结合两者

只需实现这两个接口,它们是兼容的且不会冲突。

关于报告和标签的说明

每个 InfoCollector 实现都有一个 getTags(): iterable 方法,每个标签都是一个标签名字符串。请注意,您可能想要将关联的报告分组在一起以构建您的UI。

在Symfony中注册它们

对于任何探测或信息收集器类,将其注册到容器中并添加 monitoring_plugin 标签

services:

    # Considering that auto-wiring is enabled.
    _defaults:
        autowire: true

    App\Monitoring\:
        resource: '../src/Monitoring'
        tags: ['monitoring_plugin']

Cron脚本

如果您没有监督工具来解析状态端点,您可以使用由cron运行的简单检查命令来执行状态检查。

配置您的cron

如果您没有任何外部软件来读取您的探测,您可以将默认(非常简单)的监控守护程序版本插入到您的crontab中

# Every 10 minutes: sanity check.
# In real life, this is the job of your sysadmin to choose recurrence.
*/10 * * * * /path/to/your/symfony/app/bin/console monitoring:check

将错误反应连接起来

默认情况下,检查命令不会做任何事情,您必须手动编写处理程序来对损坏的探测做出反应。

<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use MakinaCorpus\Monitoring\Event\ProbeResultEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @codeCoverageIgnore
 */
final class MonitoringEventSubscriber implements EventSubscriberInterface
{
    /**
     * {@inheritdo}
     */
    public static function getSubscribedEvents()
    {
        return [
            ProbeResultEvent::class => 'onProbeResult',
        ];
    }

    public function onProbeResult(ProbeResultEvent $event)
    {
        if ($event->isCritical()) {
            // Do something.
        } else if ($event->isMalfunctioning()) {
            // Do something else.
        }
    }
}

使用命令行获取与Nagios兼容的结果

/path/to/your/symfony/app/bin/console monitoring:status

这将输出一个与Nagios解析器兼容的文本。