You've already forked wc-licensed-product
Add Prometheus metrics integration (v0.7.4)
All checks were successful
Create Release Package / build-release (push) Successful in 1m8s
All checks were successful
Create Release Package / build-release (push) Successful in 1m8s
- New Metrics settings tab with enable/disable toggle - PrometheusController for wp_prometheus_collect_metrics hook - License gauges: total by status, lifetime, expiring, expiring soon - Download gauges: total downloads, active versions - API counters: requests, rate limits, validation errors - Metric tracking in RestApiController and UpdateController Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
22
CHANGELOG.md
22
CHANGELOG.md
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.7.4] - 2026-02-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Prometheus Metrics Integration**: Expose license and API metrics for monitoring
|
||||||
|
- New "Metrics" settings tab with enable/disable toggle
|
||||||
|
- License gauges: total by status, lifetime, expiring, expiring soon
|
||||||
|
- Download gauges: total downloads, active versions count
|
||||||
|
- API counters: requests by endpoint/result, rate limit exceeded events, validation errors by type
|
||||||
|
- Requires [WP Prometheus](https://src.bundespruefstelle.ch/magdev/wp-prometheus) plugin
|
||||||
|
|
||||||
|
### New Files
|
||||||
|
|
||||||
|
- `src/Metrics/PrometheusController.php` - Prometheus metrics collection and registration
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
- Hooks into `wp_prometheus_collect_metrics` action for metric collection
|
||||||
|
- API counters stored persistently in WordPress options (`wclp_prometheus_counters`)
|
||||||
|
- Static methods for incrementing counters from API controllers
|
||||||
|
- Metrics only collected when enabled in settings
|
||||||
|
|
||||||
## [0.7.3] - 2026-02-01
|
## [0.7.3] - 2026-02-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,7 @@ final class SettingsController
|
|||||||
'auto-updates' => __('Auto-Updates', 'wc-licensed-product'),
|
'auto-updates' => __('Auto-Updates', 'wc-licensed-product'),
|
||||||
'defaults' => __('Default Settings', 'wc-licensed-product'),
|
'defaults' => __('Default Settings', 'wc-licensed-product'),
|
||||||
'notifications' => __('Notifications', 'wc-licensed-product'),
|
'notifications' => __('Notifications', 'wc-licensed-product'),
|
||||||
|
'metrics' => __('Metrics', 'wc-licensed-product'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@ final class SettingsController
|
|||||||
'auto-updates' => $this->getAutoUpdatesSettings(),
|
'auto-updates' => $this->getAutoUpdatesSettings(),
|
||||||
'defaults' => $this->getDefaultsSettings(),
|
'defaults' => $this->getDefaultsSettings(),
|
||||||
'notifications' => $this->getNotificationsSettings(),
|
'notifications' => $this->getNotificationsSettings(),
|
||||||
|
'metrics' => $this->getMetricsSettings(),
|
||||||
default => $this->getPluginLicenseSettings(),
|
default => $this->getPluginLicenseSettings(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -314,6 +316,32 @@ final class SettingsController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get metrics settings
|
||||||
|
*/
|
||||||
|
private function getMetricsSettings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'metrics_section_title' => [
|
||||||
|
'name' => __('Prometheus Metrics', 'wc-licensed-product'),
|
||||||
|
'type' => 'title',
|
||||||
|
'desc' => __('Expose license and API metrics for Prometheus monitoring. Requires the WP Prometheus plugin to be installed and active.', 'wc-licensed-product'),
|
||||||
|
'id' => 'wc_licensed_product_section_metrics',
|
||||||
|
],
|
||||||
|
'metrics_enabled' => [
|
||||||
|
'name' => __('Enable Prometheus Metrics', 'wc-licensed-product'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'desc' => __('Expose license statistics, API usage, and download metrics via Prometheus.', 'wc-licensed-product'),
|
||||||
|
'id' => 'wc_licensed_product_metrics_enabled',
|
||||||
|
'default' => 'no',
|
||||||
|
],
|
||||||
|
'metrics_section_end' => [
|
||||||
|
'type' => 'sectionend',
|
||||||
|
'id' => 'wc_licensed_product_section_metrics_end',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render settings tab content
|
* Render settings tab content
|
||||||
*/
|
*/
|
||||||
@@ -575,4 +603,12 @@ final class SettingsController
|
|||||||
wp_send_json_error(['message' => $error]);
|
wp_send_json_error(['message' => $error]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Prometheus metrics are enabled
|
||||||
|
*/
|
||||||
|
public static function isMetricsEnabled(): bool
|
||||||
|
{
|
||||||
|
return get_option('wc_licensed_product_metrics_enabled', 'no') === 'yes';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||||||
namespace Jeremias\WcLicensedProduct\Api;
|
namespace Jeremias\WcLicensedProduct\Api;
|
||||||
|
|
||||||
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
||||||
|
use Jeremias\WcLicensedProduct\Metrics\PrometheusController;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
use WP_REST_Response;
|
use WP_REST_Response;
|
||||||
use WP_REST_Server;
|
use WP_REST_Server;
|
||||||
@@ -108,6 +109,10 @@ final class RestApiController
|
|||||||
'retry_after' => $retryAfter,
|
'retry_after' => $retryAfter,
|
||||||
], 429);
|
], 429);
|
||||||
$response->header('Retry-After', (string) $retryAfter);
|
$response->header('Retry-After', (string) $retryAfter);
|
||||||
|
|
||||||
|
// Track rate limit event for metrics
|
||||||
|
PrometheusController::incrementRateLimitExceeded('api');
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +214,16 @@ final class RestApiController
|
|||||||
|
|
||||||
$statusCode = $this->getStatusCodeForResult($result);
|
$statusCode = $this->getStatusCodeForResult($result);
|
||||||
|
|
||||||
|
// Track metrics
|
||||||
|
if ($result['valid']) {
|
||||||
|
PrometheusController::incrementApiRequest('validate', 'success');
|
||||||
|
} else {
|
||||||
|
PrometheusController::incrementApiRequest('validate', 'error');
|
||||||
|
if (!empty($result['error'])) {
|
||||||
|
PrometheusController::incrementValidationError($result['error']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new WP_REST_Response($result, $statusCode);
|
return new WP_REST_Response($result, $statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,6 +262,9 @@ final class RestApiController
|
|||||||
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
||||||
|
|
||||||
if (!$license) {
|
if (!$license) {
|
||||||
|
PrometheusController::incrementApiRequest('status', 'error');
|
||||||
|
PrometheusController::incrementValidationError('license_not_found');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'valid' => false,
|
'valid' => false,
|
||||||
'error' => 'license_not_found',
|
'error' => 'license_not_found',
|
||||||
@@ -254,6 +272,8 @@ final class RestApiController
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrometheusController::incrementApiRequest('status', 'success');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'valid' => $license->isValid(),
|
'valid' => $license->isValid(),
|
||||||
'status' => $license->getStatus(),
|
'status' => $license->getStatus(),
|
||||||
@@ -280,6 +300,9 @@ final class RestApiController
|
|||||||
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
||||||
|
|
||||||
if (!$license) {
|
if (!$license) {
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'error');
|
||||||
|
PrometheusController::incrementValidationError('license_not_found');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'license_not_found',
|
'error' => 'license_not_found',
|
||||||
@@ -288,6 +311,9 @@ final class RestApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$license->isValid()) {
|
if (!$license->isValid()) {
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'error');
|
||||||
|
PrometheusController::incrementValidationError('license_invalid');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'license_invalid',
|
'error' => 'license_invalid',
|
||||||
@@ -299,6 +325,8 @@ final class RestApiController
|
|||||||
|
|
||||||
// Check if already activated on this domain
|
// Check if already activated on this domain
|
||||||
if ($license->getDomain() === $normalizedDomain) {
|
if ($license->getDomain() === $normalizedDomain) {
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'success');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => __('License is already activated for this domain.', 'wc-licensed-product'),
|
'message' => __('License is already activated for this domain.', 'wc-licensed-product'),
|
||||||
@@ -307,6 +335,9 @@ final class RestApiController
|
|||||||
|
|
||||||
// Check if can activate on another domain
|
// Check if can activate on another domain
|
||||||
if (!$license->canActivate()) {
|
if (!$license->canActivate()) {
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'error');
|
||||||
|
PrometheusController::incrementValidationError('max_activations_reached');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'max_activations_reached',
|
'error' => 'max_activations_reached',
|
||||||
@@ -318,6 +349,9 @@ final class RestApiController
|
|||||||
$success = $this->licenseManager->updateLicenseDomain($license->getId(), $domain);
|
$success = $this->licenseManager->updateLicenseDomain($license->getId(), $domain);
|
||||||
|
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'error');
|
||||||
|
PrometheusController::incrementValidationError('activation_failed');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'error' => 'activation_failed',
|
'error' => 'activation_failed',
|
||||||
@@ -325,6 +359,8 @@ final class RestApiController
|
|||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PrometheusController::incrementApiRequest('activate', 'success');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => __('License activated successfully.', 'wc-licensed-product'),
|
'message' => __('License activated successfully.', 'wc-licensed-product'),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Jeremias\WcLicensedProduct\Api;
|
namespace Jeremias\WcLicensedProduct\Api;
|
||||||
|
|
||||||
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
||||||
|
use Jeremias\WcLicensedProduct\Metrics\PrometheusController;
|
||||||
use Jeremias\WcLicensedProduct\Product\VersionManager;
|
use Jeremias\WcLicensedProduct\Product\VersionManager;
|
||||||
use Jeremias\WcLicensedProduct\Product\ProductVersion;
|
use Jeremias\WcLicensedProduct\Product\ProductVersion;
|
||||||
use WP_REST_Request;
|
use WP_REST_Request;
|
||||||
@@ -113,6 +114,10 @@ final class UpdateController
|
|||||||
'retry_after' => $retryAfter,
|
'retry_after' => $retryAfter,
|
||||||
], 429);
|
], 429);
|
||||||
$response->header('Retry-After', (string) $retryAfter);
|
$response->header('Retry-After', (string) $retryAfter);
|
||||||
|
|
||||||
|
// Track rate limit event for metrics
|
||||||
|
PrometheusController::incrementRateLimitExceeded('update-check');
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +184,14 @@ final class UpdateController
|
|||||||
$validationResult = $this->licenseManager->validateLicense($licenseKey, $domain);
|
$validationResult = $this->licenseManager->validateLicense($licenseKey, $domain);
|
||||||
|
|
||||||
if (!$validationResult['valid']) {
|
if (!$validationResult['valid']) {
|
||||||
|
$errorType = $validationResult['error'] ?? 'license_invalid';
|
||||||
|
PrometheusController::incrementApiRequest('update-check', 'error');
|
||||||
|
PrometheusController::incrementValidationError($errorType);
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'update_available' => false,
|
'update_available' => false,
|
||||||
'error' => $validationResult['error'] ?? 'license_invalid',
|
'error' => $errorType,
|
||||||
'message' => $validationResult['message'] ?? __('License validation failed.', 'wc-licensed-product'),
|
'message' => $validationResult['message'] ?? __('License validation failed.', 'wc-licensed-product'),
|
||||||
], $validationResult['error'] === 'license_not_found' ? 404 : 403);
|
], $validationResult['error'] === 'license_not_found' ? 404 : 403);
|
||||||
}
|
}
|
||||||
@@ -190,6 +199,9 @@ final class UpdateController
|
|||||||
// Get license to access product ID
|
// Get license to access product ID
|
||||||
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
$license = $this->licenseManager->getLicenseByKey($licenseKey);
|
||||||
if (!$license) {
|
if (!$license) {
|
||||||
|
PrometheusController::incrementApiRequest('update-check', 'error');
|
||||||
|
PrometheusController::incrementValidationError('license_not_found');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'update_available' => false,
|
'update_available' => false,
|
||||||
@@ -202,6 +214,9 @@ final class UpdateController
|
|||||||
$product = wc_get_product($productId);
|
$product = wc_get_product($productId);
|
||||||
|
|
||||||
if (!$product) {
|
if (!$product) {
|
||||||
|
PrometheusController::incrementApiRequest('update-check', 'error');
|
||||||
|
PrometheusController::incrementValidationError('product_not_found');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'update_available' => false,
|
'update_available' => false,
|
||||||
@@ -214,6 +229,8 @@ final class UpdateController
|
|||||||
$latestVersion = $this->getLatestVersionForLicense($license);
|
$latestVersion = $this->getLatestVersionForLicense($license);
|
||||||
|
|
||||||
if (!$latestVersion) {
|
if (!$latestVersion) {
|
||||||
|
PrometheusController::incrementApiRequest('update-check', 'success');
|
||||||
|
|
||||||
return new WP_REST_Response([
|
return new WP_REST_Response([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'update_available' => false,
|
'update_available' => false,
|
||||||
@@ -230,6 +247,8 @@ final class UpdateController
|
|||||||
// Build response
|
// Build response
|
||||||
$response = $this->buildUpdateResponse($product, $latestVersion, $license, $updateAvailable);
|
$response = $this->buildUpdateResponse($product, $latestVersion, $license, $updateAvailable);
|
||||||
|
|
||||||
|
PrometheusController::incrementApiRequest('update-check', 'success');
|
||||||
|
|
||||||
return new WP_REST_Response($response);
|
return new WP_REST_Response($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
259
src/Metrics/PrometheusController.php
Normal file
259
src/Metrics/PrometheusController.php
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
<?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']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ use Jeremias\WcLicensedProduct\Frontend\AccountController;
|
|||||||
use Jeremias\WcLicensedProduct\Frontend\DownloadController;
|
use Jeremias\WcLicensedProduct\Frontend\DownloadController;
|
||||||
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
use Jeremias\WcLicensedProduct\License\LicenseManager;
|
||||||
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
|
use Jeremias\WcLicensedProduct\License\PluginLicenseChecker;
|
||||||
|
use Jeremias\WcLicensedProduct\Metrics\PrometheusController;
|
||||||
use Jeremias\WcLicensedProduct\Product\LicensedProductType;
|
use Jeremias\WcLicensedProduct\Product\LicensedProductType;
|
||||||
use Jeremias\WcLicensedProduct\Product\VersionManager;
|
use Jeremias\WcLicensedProduct\Product\VersionManager;
|
||||||
use Jeremias\WcLicensedProduct\Update\PluginUpdateChecker;
|
use Jeremias\WcLicensedProduct\Update\PluginUpdateChecker;
|
||||||
@@ -171,6 +172,9 @@ final class Plugin
|
|||||||
if (!empty($serverUrl) && !$licenseChecker->isSelfLicensing()) {
|
if (!empty($serverUrl) && !$licenseChecker->isSelfLicensing()) {
|
||||||
PluginUpdateChecker::getInstance()->register();
|
PluginUpdateChecker::getInstance()->register();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Prometheus metrics if enabled
|
||||||
|
(new PrometheusController($this->licenseManager, $this->versionManager))->register();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Plugin Name: WooCommerce Licensed Product
|
* Plugin Name: WooCommerce Licensed Product
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-licensed-product
|
||||||
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
* Description: WooCommerce plugin to sell software products using license keys with domain-based validation.
|
||||||
* Version: 0.7.3
|
* Version: 0.7.4
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* License: GPL-2.0-or-later
|
* License: GPL-2.0-or-later
|
||||||
@@ -28,7 +28,7 @@ if (!defined('ABSPATH')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Plugin constants
|
// Plugin constants
|
||||||
define('WC_LICENSED_PRODUCT_VERSION', '0.7.3');
|
define('WC_LICENSED_PRODUCT_VERSION', '0.7.4');
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__);
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user