diff --git a/CHANGELOG.md b/CHANGELOG.md index be82ef9..b51ab35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.5] - 2026-02-03 + +### Added + +- **Grafana Dashboard**: Example dashboard for license metrics monitoring + - 24 panels organized into 4 sections: License Overview, Downloads & Versions, API Metrics, Errors & Rate Limiting + - Template variables for data source and instance filtering + - Includes example Prometheus alerting rules +- **WP Prometheus Dashboard Integration**: Dashboard automatically registered with wp-prometheus + - Appears in Settings > WP Prometheus > Dashboards when metrics are enabled + - Uses `wp_prometheus_register_dashboards` hook for seamless integration +- Documentation for Grafana dashboard installation and PromQL query examples + +### New Files + +- `docs/grafana-dashboard.json` - Complete Grafana dashboard with 24 panels +- `docs/grafana-dashboard.md` - Installation and usage documentation + +### Changed + +- Updated README with "Monitoring with Prometheus & Grafana" section + ## [0.7.4] - 2026-02-03 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index fb220be..6b5578e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2031,3 +2031,39 @@ Added Prometheus metrics integration to expose license and API metrics for monit - Static methods allow increment from API controllers without dependency injection - Counter values persist across requests via `wclp_prometheus_counters` option - Gauges query database on each metric collection (uses existing statistics methods) + +### 2026-02-03 - Grafana Dashboard & WP Prometheus Integration + +**Overview:** + +Added example Grafana dashboard JSON and integrated with wp-prometheus dashboard registration system. + +**New files:** + +- `docs/grafana-dashboard.json` - Complete Grafana dashboard with 24 panels +- `docs/grafana-dashboard.md` - Installation and usage documentation + +**Dashboard panels:** + +- License Overview: Total, Active, Lifetime, Expiring Soon, Expired, Revoked stats + pie chart + time series +- Downloads & Versions: Total downloads, active versions, download trends +- API Metrics: Request rates by endpoint/result, pie chart breakdown, top requests table +- Errors & Rate Limiting: Rate limit events, validation errors by type over time + +**WP Prometheus integration:** + +- Dashboard automatically registered via `wp_prometheus_register_dashboards` hook +- Appears in Settings > WP Prometheus > Dashboards tab when metrics are enabled +- Uses file-based registration with metadata (title, description, icon, plugin attribution) + +**Modified files:** + +- `src/Metrics/PrometheusController.php` - Added `registerDashboard()` method and hook registration +- `docs/grafana-dashboard.md` - Added wp-prometheus installation option as recommended method +- `README.md` - Added "Monitoring with Prometheus & Grafana" section linking to dashboard docs + +**Technical notes:** + +- Dashboard registration only occurs when metrics are enabled (same condition as metric collection) +- Uses `dashicons-admin-network` icon for dashboard list +- File path uses `WC_LICENSED_PRODUCT_PLUGIN_DIR` constant for reliable path resolution diff --git a/README.md b/README.md index 7ab75a4..ead235f 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,38 @@ The plugin sends automatic email notifications (configurable via WooCommerce > S - **Expiration Warning (1 day)**: Urgent reminder sent 1 day before expiration - **License Expired**: Notification when a license auto-expires +## Monitoring with Prometheus & Grafana + +The plugin integrates with [wp-prometheus](https://src.bundespruefstelle.ch/magdev/wp-prometheus) to expose metrics for monitoring and alerting. + +### Enable Metrics + +1. Install and configure the wp-prometheus plugin +2. Go to WooCommerce > Settings > Licensed Products > Metrics +3. Enable "Prometheus Metrics" + +### Available Metrics + +**Gauges:** + +- `wclp_licenses_total{status}` - License counts by status +- `wclp_licenses_lifetime_total` - Lifetime licenses +- `wclp_licenses_expiring_soon` - Licenses expiring within 30 days +- `wclp_downloads_total` - Total file downloads +- `wclp_versions_active_total` - Active product versions + +**Counters:** + +- `wclp_api_requests_total{endpoint,result}` - API requests by endpoint and result +- `wclp_rate_limit_exceeded_total{endpoint}` - Rate limit events +- `wclp_validation_errors_total{error_type}` - Validation errors by type + +### Grafana Dashboard + +An example Grafana dashboard is included at [docs/grafana-dashboard.json](docs/grafana-dashboard.json). + +See [docs/grafana-dashboard.md](docs/grafana-dashboard.md) for installation instructions, panel descriptions, and alerting examples. + ## Changelog See [CHANGELOG.md](CHANGELOG.md) for version history and changes. diff --git a/docs/grafana-dashboard.json b/docs/grafana-dashboard.json new file mode 100644 index 0000000..3663d6a --- /dev/null +++ b/docs/grafana-dashboard.json @@ -0,0 +1,1748 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "panels": [], + "title": "License Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(wclp_licenses_total{instance=~\"$instance\"})", + "legendFormat": "Total", + "refId": "A" + } + ], + "title": "Total Licenses", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_total{instance=~\"$instance\", status=\"active\"}", + "legendFormat": "Active", + "refId": "A" + } + ], + "title": "Active Licenses", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_lifetime_total{instance=~\"$instance\"}", + "legendFormat": "Lifetime", + "refId": "A" + } + ], + "title": "Lifetime Licenses", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "yellow", + "value": null + }, + { + "color": "orange", + "value": 5 + }, + { + "color": "red", + "value": 10 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_expiring_soon{instance=~\"$instance\"}", + "legendFormat": "Expiring Soon", + "refId": "A" + } + ], + "title": "Expiring Soon (30d)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_total{instance=~\"$instance\", status=\"expired\"}", + "legendFormat": "Expired", + "refId": "A" + } + ], + "title": "Expired Licenses", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_total{instance=~\"$instance\", status=\"revoked\"}", + "legendFormat": "Revoked", + "refId": "A" + } + ], + "title": "Revoked Licenses", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "active" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "expired" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "revoked" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "inactive" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "gray", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 5 + }, + "id": 8, + "options": { + "displayLabels": ["name", "percent"], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true, + "values": ["value"] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_total{instance=~\"$instance\"}", + "legendFormat": "{{status}}", + "refId": "A" + } + ], + "title": "Licenses by Status", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 16, + "x": 8, + "y": 5 + }, + "id": 9, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_licenses_total{instance=~\"$instance\"}", + "legendFormat": "{{status}}", + "refId": "A" + } + ], + "title": "License Status Over Time", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 10, + "panels": [], + "title": "Downloads & Versions", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 14 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_downloads_total{instance=~\"$instance\"}", + "legendFormat": "Downloads", + "refId": "A" + } + ], + "title": "Total Downloads", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "purple", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 14 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_versions_active_total{instance=~\"$instance\"}", + "legendFormat": "Active Versions", + "refId": "A" + } + ], + "title": "Active Product Versions", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 13, + "options": { + "legend": { + "calcs": ["last", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_downloads_total{instance=~\"$instance\"}", + "legendFormat": "Total Downloads", + "refId": "A" + } + ], + "title": "Downloads Over Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "increase(wclp_downloads_total{instance=~\"$instance\"}[$__range])", + "legendFormat": "Downloads", + "refId": "A" + } + ], + "title": "Downloads (Selected Range)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 15, + "panels": [], + "title": "API Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".*success.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": ".*error.*" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 23 + }, + "id": 16, + "options": { + "legend": { + "calcs": ["sum"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "increase(wclp_api_requests_total{instance=~\"$instance\"}[5m])", + "legendFormat": "{{endpoint}} ({{result}})", + "refId": "A" + } + ], + "title": "API Requests (5m intervals)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "validate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "status" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "activate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "update-check" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 23 + }, + "id": 17, + "options": { + "displayLabels": ["name", "percent"], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": ["value"] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum by (endpoint) (wclp_api_requests_total{instance=~\"$instance\"})", + "legendFormat": "{{endpoint}}", + "refId": "A" + } + ], + "title": "Requests by Endpoint", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "mode": "gradient", + "type": "gauge" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 23 + }, + "id": 18, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "topk(10, sum by (endpoint, result) (wclp_api_requests_total{instance=~\"$instance\"}))", + "format": "table", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Top API Requests", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "Value": "Count", + "endpoint": "Endpoint", + "result": "Result" + } + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 19, + "panels": [], + "title": "Errors & Rate Limiting", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 32 + }, + "id": 20, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(wclp_rate_limit_exceeded_total{instance=~\"$instance\"})", + "legendFormat": "Rate Limited", + "refId": "A" + } + ], + "title": "Rate Limit Events (Total)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "red", + "value": 50 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 32 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "sum(wclp_validation_errors_total{instance=~\"$instance\"})", + "legendFormat": "Validation Errors", + "refId": "A" + } + ], + "title": "Validation Errors (Total)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 22, + "options": { + "legend": { + "calcs": ["sum"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "increase(wclp_validation_errors_total{instance=~\"$instance\"}[5m])", + "legendFormat": "{{error_type}}", + "refId": "A" + } + ], + "title": "Validation Errors Over Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "license_not_found" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "domain_mismatch" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "license_expired" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "license_revoked" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 36 + }, + "id": 23, + "options": { + "displayLabels": ["name", "value"], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "values": [] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "wclp_validation_errors_total{instance=~\"$instance\"}", + "legendFormat": "{{error_type}}", + "refId": "A" + } + ], + "title": "Validation Errors by Type", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 36 + }, + "id": 24, + "options": { + "legend": { + "calcs": ["sum"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "expr": "increase(wclp_rate_limit_exceeded_total{instance=~\"$instance\"}[5m])", + "legendFormat": "{{endpoint}}", + "refId": "A" + } + ], + "title": "Rate Limit Events by Endpoint", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 38, + "tags": ["woocommerce", "licenses", "wc-licensed-product"], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "prometheus" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": ".*", + "current": { + "selected": false, + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "definition": "label_values(wclp_licenses_total, instance)", + "hide": 0, + "includeAll": true, + "label": "Instance", + "multi": false, + "name": "instance", + "options": [], + "query": { + "query": "label_values(wclp_licenses_total, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "WC Licensed Product - License Metrics", + "uid": "wc-licensed-product", + "version": 1, + "weekStart": "" +} diff --git a/docs/grafana-dashboard.md b/docs/grafana-dashboard.md new file mode 100644 index 0000000..f9a75f3 --- /dev/null +++ b/docs/grafana-dashboard.md @@ -0,0 +1,219 @@ +# Grafana Dashboard for WC Licensed Product + +This dashboard provides comprehensive monitoring for the WC Licensed Product plugin using Prometheus metrics exposed via the [wp-prometheus](https://src.bundespruefstelle.ch/magdev/wp-prometheus) plugin. + +## Prerequisites + +1. **WP Prometheus Plugin** - Install and configure [wp-prometheus](https://src.bundespruefstelle.ch/magdev/wp-prometheus) on your WordPress site +2. **Prometheus** - Configure Prometheus to scrape your WordPress metrics endpoint +3. **Grafana** - Grafana 9.0+ with Prometheus data source configured +4. **Enable Metrics** - In WordPress admin: WooCommerce > Settings > Licensed Products > Metrics > Enable Prometheus Metrics + +## Installation + +### Option 1: Via WP Prometheus Settings (Recommended) + +When metrics are enabled, the dashboard is automatically registered with wp-prometheus: + +1. Go to **Settings > WP Prometheus** in WordPress admin +2. Navigate to the **Dashboards** tab +3. Find "WC Licensed Product - License Metrics" in the list +4. Click **Download JSON** to get the dashboard file +5. Import the downloaded file into Grafana + +### Option 2: Manual Import + +1. Open Grafana and navigate to **Dashboards > Import** +2. Upload the `grafana-dashboard.json` file or paste its contents +3. Select your Prometheus data source +4. Click **Import** + +### Configure Variables + +The dashboard includes two template variables: + +- **datasource** - Select your Prometheus data source +- **instance** - Filter by WordPress instance (useful for multi-site monitoring) + +## Dashboard Panels + +### License Overview + +| Panel | Description | +| --- | --- | +| Total Licenses | Total count of all licenses | +| Active Licenses | Licenses with `status=active` | +| Lifetime Licenses | Licenses without expiration date | +| Expiring Soon (30d) | Licenses expiring within 30 days | +| Expired Licenses | Licenses with `status=expired` | +| Revoked Licenses | Licenses with `status=revoked` | +| Licenses by Status | Pie chart showing distribution | +| License Status Over Time | Time series of license counts | + +### Downloads & Versions + +| Panel | Description | +| --- | --- | +| Total Downloads | Cumulative download count | +| Active Product Versions | Number of active versions | +| Downloads Over Time | Download trend graph | +| Downloads (Selected Range) | Downloads in selected time range | + +### API Metrics + +| Panel | Description | +| --- | --- | +| API Requests (5m intervals) | Stacked bar chart by endpoint/result | +| Requests by Endpoint | Donut chart of endpoint distribution | +| Top API Requests | Table of most frequent requests | + +### Errors & Rate Limiting + +| Panel | Description | +| --- | --- | +| Rate Limit Events (Total) | Total HTTP 429 responses | +| Validation Errors (Total) | Total validation failures | +| Validation Errors Over Time | Error trend by type | +| Validation Errors by Type | Pie chart breakdown | +| Rate Limit Events by Endpoint | Rate limits per endpoint | + +## Metrics Reference + +### Gauges (current values) + +```promql +# Licenses by status +wclp_licenses_total{status="active|expired|revoked|inactive"} + +# Lifetime licenses (no expiration) +wclp_licenses_lifetime_total + +# Licenses with expiration date +wclp_licenses_expiring_total + +# Licenses expiring within 30 days +wclp_licenses_expiring_soon + +# Total downloads +wclp_downloads_total + +# Active product versions +wclp_versions_active_total +``` + +### Counters (cumulative) + +```promql +# API requests by endpoint and result +wclp_api_requests_total{endpoint="validate|status|activate|update-check", result="success|error"} + +# Rate limit exceeded events +wclp_rate_limit_exceeded_total{endpoint="validate|status|activate|update-check"} + +# Validation errors by type +wclp_validation_errors_total{error_type="license_not_found|domain_mismatch|license_expired|license_revoked|..."} +``` + +## Example Prometheus Queries + +### Success Rate + +```promql +sum(rate(wclp_api_requests_total{result="success"}[5m])) / +sum(rate(wclp_api_requests_total[5m])) * 100 +``` + +### Error Rate by Endpoint + +```promql +sum by (endpoint) (rate(wclp_api_requests_total{result="error"}[5m])) +``` + +### License Churn (new activations) + +```promql +increase(wclp_licenses_total{status="active"}[1d]) +``` + +### Top Validation Errors + +```promql +topk(5, sum by (error_type) (wclp_validation_errors_total)) +``` + +## Alerting Examples + +Add these alerts to your Prometheus alerting rules: + +```yaml +groups: + - name: wc-licensed-product + rules: + # High rate limit events + - alert: HighRateLimitEvents + expr: increase(wclp_rate_limit_exceeded_total[5m]) > 10 + for: 5m + labels: + severity: warning + annotations: + summary: "High rate limiting on {{ $labels.endpoint }}" + + # Many expiring licenses + - alert: LicensesExpiringSoon + expr: wclp_licenses_expiring_soon > 20 + for: 1h + labels: + severity: info + annotations: + summary: "{{ $value }} licenses expiring within 30 days" + + # API error rate + - alert: HighAPIErrorRate + expr: | + sum(rate(wclp_api_requests_total{result="error"}[5m])) / + sum(rate(wclp_api_requests_total[5m])) > 0.1 + for: 10m + labels: + severity: warning + annotations: + summary: "API error rate above 10%" +``` + +## Prometheus Configuration + +Add to your `prometheus.yml`: + +```yaml +scrape_configs: + - job_name: 'wordpress' + metrics_path: '/metrics' + scheme: https + bearer_token: 'YOUR_WP_PROMETHEUS_TOKEN' + static_configs: + - targets: ['your-wordpress-site.com'] +``` + +## Troubleshooting + +### No data showing + +1. Verify wp-prometheus is installed and configured +2. Check that metrics are enabled in WC Licensed Product settings +3. Confirm Prometheus can reach your WordPress metrics endpoint +4. Check the data source selection in Grafana + +### Missing metrics + +Some metrics only appear after relevant actions occur: + +- `wclp_api_requests_total` - After API requests +- `wclp_rate_limit_exceeded_total` - After rate limit events +- `wclp_validation_errors_total` - After validation errors + +### Counter resets + +Counters persist in WordPress options and survive restarts. To reset: + +```php +\Jeremias\WcLicensedProduct\Metrics\PrometheusController::resetCounters(); +``` diff --git a/src/Metrics/PrometheusController.php b/src/Metrics/PrometheusController.php index 98b08dc..40bc55f 100644 --- a/src/Metrics/PrometheusController.php +++ b/src/Metrics/PrometheusController.php @@ -43,6 +43,29 @@ final class PrometheusController } add_action('wp_prometheus_collect_metrics', [$this, 'collectMetrics']); + 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', + ]); } /** diff --git a/wc-licensed-product.php b/wc-licensed-product.php index 0cddb90..876cf8c 100644 --- a/wc-licensed-product.php +++ b/wc-licensed-product.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce 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. - * Version: 0.7.4 + * Version: 0.7.5 * Author: Marco Graetsch * Author URI: https://src.bundespruefstelle.ch/magdev * License: GPL-2.0-or-later @@ -28,7 +28,7 @@ if (!defined('ABSPATH')) { } // Plugin constants -define('WC_LICENSED_PRODUCT_VERSION', '0.7.4'); +define('WC_LICENSED_PRODUCT_VERSION', '0.7.5'); define('WC_LICENSED_PRODUCT_PLUGIN_FILE', __FILE__); define('WC_LICENSED_PRODUCT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('WC_LICENSED_PRODUCT_PLUGIN_URL', plugin_dir_url(__FILE__));