2026-02-03 10:52:50 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Prometheus Metrics Controller
|
|
|
|
|
*
|
|
|
|
|
* @package Jeremias\WcLicensedProduct\Metrics
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Jeremias\WcLicensedProduct\Metrics;
|
|
|
|
|
|
|
|
|
|
use Jeremias\WcLicensedProduct\Admin\SettingsController;
|
|
|
|
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
|
|
|
|
use Jeremias\WcLicensedProduct\Product\VersionManager;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Exposes license and API metrics for Prometheus monitoring
|
|
|
|
|
*/
|
|
|
|
|
final class PrometheusController
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Option name for storing API counters
|
|
|
|
|
*/
|
|
|
|
|
private const COUNTERS_OPTION = 'wclp_prometheus_counters';
|
|
|
|
|
|
|
|
|
|
private LicenseManager $licenseManager;
|
|
|
|
|
private VersionManager $versionManager;
|
|
|
|
|
|
|
|
|
|
public function __construct(LicenseManager $licenseManager, VersionManager $versionManager)
|
|
|
|
|
{
|
|
|
|
|
$this->licenseManager = $licenseManager;
|
|
|
|
|
$this->versionManager = $versionManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register hooks for Prometheus metrics collection
|
|
|
|
|
*/
|
|
|
|
|
public function register(): void
|
|
|
|
|
{
|
|
|
|
|
// Only register if metrics are enabled
|
|
|
|
|
if (!SettingsController::isMetricsEnabled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_action('wp_prometheus_collect_metrics', [$this, 'collectMetrics']);
|
2026-02-03 11:29:14 +01:00
|
|
|
add_action('wp_prometheus_register_dashboards', [$this, 'registerDashboard']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register Grafana dashboard with wp-prometheus
|
|
|
|
|
*
|
|
|
|
|
* @param object $provider The dashboard provider object
|
|
|
|
|
*/
|
|
|
|
|
public function registerDashboard(object $provider): void
|
|
|
|
|
{
|
|
|
|
|
$dashboardFile = WC_LICENSED_PRODUCT_PLUGIN_DIR . 'docs/grafana-dashboard.json';
|
|
|
|
|
|
|
|
|
|
if (!file_exists($dashboardFile)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$provider->register_dashboard('wc-licensed-product', [
|
|
|
|
|
'title' => __('WC Licensed Product - License Metrics', 'wc-licensed-product'),
|
|
|
|
|
'description' => __('Monitor license status, downloads, API usage, and validation errors.', 'wc-licensed-product'),
|
|
|
|
|
'icon' => 'dashicons-admin-network',
|
|
|
|
|
'file' => $dashboardFile,
|
|
|
|
|
'plugin' => 'WC Licensed Product',
|
|
|
|
|
]);
|
2026-02-03 10:52:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect and register all metrics
|
|
|
|
|
*
|
|
|
|
|
* @param object $collector The Prometheus collector object
|
|
|
|
|
*/
|
|
|
|
|
public function collectMetrics(object $collector): void
|
|
|
|
|
{
|
|
|
|
|
$this->collectLicenseMetrics($collector);
|
|
|
|
|
$this->collectDownloadMetrics($collector);
|
|
|
|
|
$this->collectApiMetrics($collector);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect license-related metrics
|
|
|
|
|
*/
|
|
|
|
|
private function collectLicenseMetrics(object $collector): void
|
|
|
|
|
{
|
|
|
|
|
$stats = $this->licenseManager->getStatistics();
|
|
|
|
|
|
|
|
|
|
// License count by status (gauge)
|
|
|
|
|
$licensesByStatus = $collector->register_gauge(
|
|
|
|
|
'wclp_licenses_total',
|
|
|
|
|
'Total number of licenses by status',
|
|
|
|
|
['status']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ($stats['by_status'] as $status => $count) {
|
|
|
|
|
$licensesByStatus->set($count, [$status]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lifetime licenses (gauge)
|
|
|
|
|
$lifetimeLicenses = $collector->register_gauge(
|
|
|
|
|
'wclp_licenses_lifetime_total',
|
|
|
|
|
'Total number of lifetime licenses'
|
|
|
|
|
);
|
|
|
|
|
$lifetimeLicenses->set($stats['lifetime']);
|
|
|
|
|
|
|
|
|
|
// Expiring licenses (gauge)
|
|
|
|
|
$expiringLicenses = $collector->register_gauge(
|
|
|
|
|
'wclp_licenses_expiring_total',
|
|
|
|
|
'Total number of licenses with expiration date'
|
|
|
|
|
);
|
|
|
|
|
$expiringLicenses->set($stats['expiring']);
|
|
|
|
|
|
|
|
|
|
// Licenses expiring soon - next 30 days (gauge)
|
|
|
|
|
$expiringSoon = $collector->register_gauge(
|
|
|
|
|
'wclp_licenses_expiring_soon',
|
|
|
|
|
'Licenses expiring within 30 days'
|
|
|
|
|
);
|
|
|
|
|
$expiringSoon->set($stats['expiring_soon']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect download-related metrics
|
|
|
|
|
*/
|
|
|
|
|
private function collectDownloadMetrics(object $collector): void
|
|
|
|
|
{
|
|
|
|
|
$stats = $this->versionManager->getDownloadStatistics();
|
|
|
|
|
|
|
|
|
|
// Total downloads (gauge)
|
|
|
|
|
$totalDownloads = $collector->register_gauge(
|
|
|
|
|
'wclp_downloads_total',
|
|
|
|
|
'Total number of file downloads'
|
|
|
|
|
);
|
|
|
|
|
$totalDownloads->set($stats['total']);
|
|
|
|
|
|
|
|
|
|
// Active versions count (gauge)
|
|
|
|
|
$activeVersions = $collector->register_gauge(
|
|
|
|
|
'wclp_versions_active_total',
|
|
|
|
|
'Total number of active product versions'
|
|
|
|
|
);
|
|
|
|
|
$activeVersions->set($this->countActiveVersions());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Collect API-related metrics (counters)
|
|
|
|
|
*/
|
|
|
|
|
private function collectApiMetrics(object $collector): void
|
|
|
|
|
{
|
|
|
|
|
$counters = $this->getCounters();
|
|
|
|
|
|
|
|
|
|
// API requests by endpoint and result (counter)
|
|
|
|
|
$apiRequests = $collector->register_counter(
|
|
|
|
|
'wclp_api_requests_total',
|
|
|
|
|
'Total API requests by endpoint and result',
|
|
|
|
|
['endpoint', 'result']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ($counters['api_requests'] ?? [] as $key => $count) {
|
|
|
|
|
[$endpoint, $result] = explode(':', $key);
|
|
|
|
|
$apiRequests->incBy($count, [$endpoint, $result]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Rate limit exceeded events (counter)
|
|
|
|
|
$rateLimitExceeded = $collector->register_counter(
|
|
|
|
|
'wclp_rate_limit_exceeded_total',
|
|
|
|
|
'Total rate limit exceeded events by endpoint',
|
|
|
|
|
['endpoint']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ($counters['rate_limit'] ?? [] as $endpoint => $count) {
|
|
|
|
|
$rateLimitExceeded->incBy($count, [$endpoint]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validation errors by type (counter)
|
|
|
|
|
$validationErrors = $collector->register_counter(
|
|
|
|
|
'wclp_validation_errors_total',
|
|
|
|
|
'Total validation errors by error type',
|
|
|
|
|
['error_type']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach ($counters['validation_errors'] ?? [] as $errorType => $count) {
|
|
|
|
|
$validationErrors->incBy($count, [$errorType]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Count active product versions
|
|
|
|
|
*/
|
|
|
|
|
private function countActiveVersions(): int
|
|
|
|
|
{
|
|
|
|
|
global $wpdb;
|
|
|
|
|
|
|
|
|
|
$tableName = \Jeremias\WcLicensedProduct\Installer::getVersionsTable();
|
|
|
|
|
|
|
|
|
|
return (int) $wpdb->get_var(
|
|
|
|
|
"SELECT COUNT(*) FROM {$tableName} WHERE is_active = 1"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get stored counters
|
|
|
|
|
*/
|
|
|
|
|
private function getCounters(): array
|
|
|
|
|
{
|
|
|
|
|
$counters = get_option(self::COUNTERS_OPTION, []);
|
|
|
|
|
return is_array($counters) ? $counters : [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Increment an API request counter
|
|
|
|
|
*
|
|
|
|
|
* @param string $endpoint The API endpoint (validate, status, activate, update-check)
|
|
|
|
|
* @param string $result The result (success or error)
|
|
|
|
|
*/
|
|
|
|
|
public static function incrementApiRequest(string $endpoint, string $result): void
|
|
|
|
|
{
|
|
|
|
|
if (!SettingsController::isMetricsEnabled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counters = get_option(self::COUNTERS_OPTION, []);
|
|
|
|
|
if (!is_array($counters)) {
|
|
|
|
|
$counters = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$key = "{$endpoint}:{$result}";
|
|
|
|
|
$counters['api_requests'][$key] = ($counters['api_requests'][$key] ?? 0) + 1;
|
|
|
|
|
|
|
|
|
|
update_option(self::COUNTERS_OPTION, $counters, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Increment rate limit exceeded counter
|
|
|
|
|
*
|
|
|
|
|
* @param string $endpoint The API endpoint
|
|
|
|
|
*/
|
|
|
|
|
public static function incrementRateLimitExceeded(string $endpoint): void
|
|
|
|
|
{
|
|
|
|
|
if (!SettingsController::isMetricsEnabled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counters = get_option(self::COUNTERS_OPTION, []);
|
|
|
|
|
if (!is_array($counters)) {
|
|
|
|
|
$counters = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counters['rate_limit'][$endpoint] = ($counters['rate_limit'][$endpoint] ?? 0) + 1;
|
|
|
|
|
|
|
|
|
|
update_option(self::COUNTERS_OPTION, $counters, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Increment validation error counter
|
|
|
|
|
*
|
|
|
|
|
* @param string $errorType The error type (license_not_found, domain_mismatch, etc.)
|
|
|
|
|
*/
|
|
|
|
|
public static function incrementValidationError(string $errorType): void
|
|
|
|
|
{
|
|
|
|
|
if (!SettingsController::isMetricsEnabled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counters = get_option(self::COUNTERS_OPTION, []);
|
|
|
|
|
if (!is_array($counters)) {
|
|
|
|
|
$counters = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$counters['validation_errors'][$errorType] = ($counters['validation_errors'][$errorType] ?? 0) + 1;
|
|
|
|
|
|
|
|
|
|
update_option(self::COUNTERS_OPTION, $counters, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Reset all counters (useful for testing or maintenance)
|
|
|
|
|
*/
|
|
|
|
|
public static function resetCounters(): void
|
|
|
|
|
{
|
|
|
|
|
delete_option(self::COUNTERS_OPTION);
|
|
|
|
|
}
|
|
|
|
|
}
|