- Add wp_prometheus_disable_early_mode option in admin settings - Support WP_PROMETHEUS_DISABLE_EARLY_MODE environment variable - Add Early Mode section in Metrics tab with status indicator - Allow users to enable wp_prometheus_collect_metrics hook Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
21 KiB
WP Prometheus
Author: Marco Graetsch Author URL: https://src.bundespruefstelle.ch/magdev Author Email: magdev3.0@gmail.com Repository URL: https://src.bundespruefstelle.ch/magdev/wp-prometheus Issues URL: https://src.bundespruefstelle.ch/magdev/wp-prometheus/issues
Project Overview
This plugin provides a Prometheus /metrics endpoint and an extensible way to add your own metrics in third-party plugins using hooks. It adds some default metrics like number of active accounts, number of articles, comments, and plugin status. The default metrics can be activated/deactivated in the plugin settings.
Features
- Prometheus compatible authenticated
/metricsendpoint - Optional default metrics (users, posts, comments, plugins)
- Runtime metrics (HTTP requests, request duration, database queries)
- Cron job metrics (scheduled events, overdue, next run)
- Transient cache metrics (total, expiring, expired)
- WooCommerce integration (products, orders, revenue, customers)
- Custom metric builder with admin UI (gauges with static or option-based values)
- Metric export/import for backup and site migration
- Grafana dashboard templates for easy visualization
- Dedicated plugin settings under 'Settings/Metrics' menu
- Extensible by other plugins using
wp_prometheus_collect_metricsaction hook - License management integration
Key Fact: 100% AI-Generated
This project is proudly "vibe-coded" using Claude.AI - the entire codebase was created through AI assistance.
Temporary Roadmap
Note for AI Assistants: Clean this section after the specific features are done or new releases are made. Effective changes are tracked in CHANGELOG.md. Do not add completed versions here - document them in the Session History section at the end of this file.
No planned features at this time.
Technical Stack
- Language: PHP 8.3.x
- PHP-Standards: PSR-4
- Framework: Latest WordPress Plugin API
- Styling: Custom CSS
- Dependency Management: Composer
- Internationalization: WordPress i18n (.pot/.po/.mo files)
- Canonical Plugin Name:
wp-prometheus - License Client:
magdev/wc-licensed-product-clientHave a look at https://src.bundespruefstelle.ch/magdev/wp-fedistream for a working admin integration - Prometheus Client:
promphp/prometheus_client_phphttps://github.com/PromPHP/prometheus_client_php
Security Best Practices
- All user inputs are sanitized (integers for quantities/prices)
- Nonce verification on form submissions
- Output escaping in templates (
esc_attr,esc_html,esc_js) - Direct file access prevention via
ABSPATHcheck - XSS-safe DOM construction in JavaScript (no
innerHTMLwith user data) - SQL injection prevention using
$wpdb->prepare()throughout
Translation Ready
All user-facing strings use:
__('Text to translate', 'wp-prometheus')
_e('Text to translate', 'wp-prometheus')
Text domain: wp-prometheus
Translation Template
- Base
.potfile created:languages/wp-prometheus.pot - Ready for translation to any locale
- All translatable strings properly marked with text domain
Available Translations
en_US- English (United States) [base language - .pot template]de_CH- German (Switzerland, formal)
To compile translations to .mo files for production:
for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
Create releases
- The
vendor/directory MUST be included in releases (Dependencies required for runtime) - CRITICAL: Build
vendor/for the MINIMUM supported PHP version, not the development version- Use
composer config platform.php 8.3.0before building release packages - Run
composer update --no-dev --optimize-autoloaderto rebuild dependencies
- Use
- CRITICAL: WordPress requires plugins in a subdirectory structure
- Run zip from the
plugins/parent directory, NOT from within the plugin directory - Package must extract to
wp-prometheus/subdirectory with main file atwp-prometheus/wp-prometheus.php - Correct command:
cd /wp-content/plugins/ && zip -r wp-prometheus/releases/wp-prometheus-x.x.x.zip wp-prometheus ... - Wrong: Running zip from inside the plugin directory creates files at root level
- Run zip from the
- CRITICAL: Exclude symlinks explicitly - zip follows symlinks by default
- Always use
-x "wp-prometheus/wp-core" -x "wp-prometheus/wp-core/*" -x "wp-prometheus/wp-plugins" -x "wp-prometheus/wp-plugins/*"to exclude development symlinks - Otherwise the entire linked directory contents will be included in the package
- Always use
- Exclusion patterns must match the relative path structure used in zip command
- Always verify the package structure with
unzip -lbefore distribution- Check all files are prefixed with
wp-prometheus/ - Verify main file is at
wp-prometheus/wp-prometheus.php - Check for duplicate entries (indicates multiple builds in same archive)
- Check all files are prefixed with
- Test installation on the minimum supported PHP version before final deployment
- Releases are stored in
releases/including checksums - Track release changes in a single
CHANGELOG.mdfile - Bump the version number to either bugfix release versions or on new features minor release versions
- CRITICAL: WordPress reads version from TWO places - BOTH must be updated:
- Plugin header comment
Version: x.x.x- WordPress uses THIS for admin display - PHP constant
WP_PROMETHEUS_VERSION(line ~28) - Used internally by the plugin
- If only the constant is updated, WordPress will show the old version in Plugins list
- Plugin header comment
Important Git Notes:
- Default branch while development is
dev - Create releases from branch
mainafter merging branchdev - Tags should use format
vX.X.X(e.g.,v1.1.22), start with v0.0.1 - Use annotated tags (
-a) not lightweight tags - Commit messages should follow the established format with Claude Code attribution
.claude/settings.local.jsonchanges are typically local-only (stash before rebasing)
CRITICAL - Release Workflow:
On every new version, ALWAYS execute this complete workflow:
# 1. Commit changes to dev branch
git add <files>
git commit -m "Description of changes (vX.X.X)"
# 2. Merge dev to main
git checkout main
git merge dev --no-edit
# 3. Create annotated tag
git tag -a vX.X.X -m "Version X.X.X - Brief description"
# 4. Push everything to origin
git push origin dev main vX.X.X
# 5. Switch back to dev for continued development
git checkout dev
Never skip any of these steps. The release is not complete until all branches and the tag are pushed to origin.
What Gets Released
- All plugin source files
- Compiled vendor dependencies
- Translation files (.mo compiled from .po)
- Assets (CSS, JS)
- Documentation (README, CHANGELOG, etc.)
What's Excluded
- Git metadata (
.git/) - Development files (
.vscode/,.claude/,CLAUDE.md,wp-core,wp-plugins) - Logs and cache files
- Previous releases
composer.lock(butvendor/is included)
For AI Assistants:
When starting a new session on this project:
- Read this CLAUDE.md file first
- Semantic versioning follows the
MAJOR.MINOR.BUGFIXpattern - Check git log for recent changes
- Verify you're on the
devbranch before making changes - Run
composer installif vendor/ is missing - Test changes before committing
- Follow commit message format with Claude Code attribution
- Update this session history section with learnings
- Never commit backup files (
*.po~,*.bak, etc.) - checkgit statusbefore committing - Follow markdown linting rules (see below)
Always refer to this document when starting work on this project.
Markdown Linting Rules
When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors:
-
MD031 - Blank lines around fenced code blocks: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format:
-
Item label:
(blank line here) ```php code example ``` (blank line here)
-
-
MD056 - Table column count: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths.
-
MD009 - No trailing spaces: Remove trailing whitespace from lines
-
MD012 - No multiple consecutive blank lines: Use only single blank lines between sections
-
MD040 - Fenced code blocks should have a language specified: Always add a language identifier to code blocks (e.g.,
txt,bash,php). For shortcode examples, usetxt. -
MD032 - Lists should be surrounded by blank lines: Add a blank line before AND after list blocks, including after bold labels like
**Attributes:**. -
MD034 - Bare URLs: Wrap URLs in angle brackets (e.g.,
<https://example.com>) or use markdown link syntax[text](url). -
Author section formatting: Use a heading (
### Name) instead of bold (**Name**) for the author name to maintain consistent document structure.
Project Architecture
Directory Structure
wp-prometheus/
├── .gitea/workflows/
│ └── release.yml # CI/CD pipeline
├── assets/
│ ├── css/ # Admin/Frontend styles
│ ├── dashboards/ # Grafana dashboard templates
│ │ ├── wordpress-overview.json
│ │ ├── wordpress-runtime.json
│ │ └── wordpress-woocommerce.json
│ └── js/
│ └── admin.js # Admin JavaScript
├── languages/ # Translation files
├── lib/
│ └── wc-licensed-product-client/ # Git submodule
├── releases/ # Release packages
├── src/
│ ├── Admin/
│ │ ├── DashboardProvider.php # Grafana dashboard provider
│ │ └── Settings.php # Settings page
│ ├── Endpoint/
│ │ └── MetricsEndpoint.php # /metrics endpoint
│ ├── License/
│ │ └── Manager.php # License management
│ ├── Metrics/
│ │ ├── Collector.php # Prometheus metrics collector
│ │ ├── CustomMetricBuilder.php # Custom metric CRUD
│ │ ├── RuntimeCollector.php # Runtime metrics collector
│ │ └── StorageFactory.php # Storage adapter factory
│ ├── Installer.php # Activation/Deactivation
│ ├── Plugin.php # Main plugin class
│ └── index.php
├── CHANGELOG.md
├── CLAUDE.md
├── composer.json
├── index.php
├── PLAN.md
├── README.md
├── uninstall.php
└── wp-prometheus.php # Plugin bootstrap
Implementation Details
License Manager (src/License/Manager.php)
- Integration with
SecureLicenseClientorLicenseClient - Option storage for license key, server URL, server secret
- License validation with domain binding
- License activation with domain
- Status caching (24-hour transient)
- AJAX handlers for admin operations
- Exception handling for all license states
Metrics Endpoint Restriction Logic
// In Plugin::init_components()
if ( LicenseManager::is_license_valid() ) {
$this->collector = new Collector();
new MetricsEndpoint( $this->collector );
}
Admin settings always work; metrics endpoint requires valid license.
Custom Metrics Extension
// Third-party plugins can add custom metrics
add_action( 'wp_prometheus_collect_metrics', function( $collector ) {
$gauge = $collector->register_gauge(
'my_custom_metric',
'Description of my metric',
array( 'label1', 'label2' )
);
$gauge->set( 42, array( 'value1', 'value2' ) );
} );
Session History
2026-02-02 - Early Mode Toggle (v0.4.2)
- Added option to disable early mode for users who need extensibility
- Implementation:
- Added
wp_prometheus_disable_early_modeWordPress option - Added
WP_PROMETHEUS_DISABLE_EARLY_MODEenvironment variable support - Option check in
wp_prometheus_early_metrics_check()before early interception - Environment variable accepts
1,true,yes,on(case-insensitive)
- Added
- Admin UI in Metrics tab:
- "Early Mode" section with description of functionality
- Checkbox to disable early metrics interception
- Environment override notice when env var is set
- Current status indicator showing early mode state
- Key Learning: Balancing compatibility vs extensibility
- Early mode fixes memory issues but disables
wp_prometheus_collect_metricshook - Users with custom metrics need the hook, so early mode must be optional
- Default remains enabled (safe) with explicit opt-out for advanced users
- Early mode fixes memory issues but disables
2026-02-02 - Plugin Compatibility Fix (v0.4.1)
- Fixed memory exhaustion (1GB limit) when wp-fedistream (Twig-based) plugin is active
- Root cause: Infinite recursion through WordPress hook system when content filters trigger Twig rendering
- Solution: Early metrics endpoint interception before full WordPress initialization
- Implementation changes:
- Added
wp_prometheus_early_metrics_check()in bootstrap file (wp-prometheus.php) - Checks REQUEST_URI for
/metricspattern beforeplugins_loadedfires - Defines
WP_PROMETHEUS_EARLY_METRICSconstant to signal early mode - Removes content filters (
the_content,the_excerpt,get_the_excerpt,the_title) - Collector skips
wp_prometheus_collect_metricsaction in early mode - Changed MetricsEndpoint from
template_redirecttoparse_requesthook
- Added
- Key Learning: WordPress plugin loading order and hook timing
- Plugins load alphabetically, so wp-fedistream ('f') loads before wp-prometheus ('p')
template_redirectfires too late - after themes and Twig initializeparse_requestfires earlier but still after plugin files load- Earliest interception point: top-level code in plugin bootstrap file
- Key Learning: Content filter recursion in WordPress
get_the_excerpt()internally triggersapply_filters('the_content', ...)- This creates unexpected recursion vectors when Twig templates process content
- Solution: Remove all content-related filters before metrics collection
- Key Learning: Isolating metrics collection from WordPress template system
- Use
remove_all_filters()to clear problematic filter chains - Skip extensibility hooks (
do_action) when in isolated early mode - Exit immediately after output to prevent further WordPress processing
- Use
2026-02-02 - Persistent Storage (v0.4.0)
- Added persistent storage support for metrics:
StorageFactory.php- Factory class for storage adapter instantiation- Redis storage adapter for shared metrics across multiple instances
- APCu storage adapter for single-server high-performance caching
- Automatic fallback to In-Memory if configured adapter fails
- Added new "Storage" tab in admin settings:
- Storage adapter selection (In-Memory, Redis, APCu)
- Redis configuration (host, port, password, database, key prefix)
- APCu configuration (key prefix)
- Connection test button with detailed error messages
- Added environment variable support for Docker deployments:
WP_PROMETHEUS_STORAGE_ADAPTER- Adapter selectionWP_PROMETHEUS_REDIS_HOST,_PORT,_PASSWORD,_DATABASE,_PREFIXWP_PROMETHEUS_APCU_PREFIX- Environment variables take precedence over admin settings
- Updated
Collector.phpto useStorageFactory::get_adapter() - Updated Help tab with storage backends documentation
- Updated translation files with all new strings
- Key Learning: promphp/prometheus_client_php storage adapters
- Redis adapter requires options array with host, port, password, timeout
- APCu adapter just needs a prefix string
- Use
Redis::setPrefix()before instantiation for custom key prefixes
- Key Learning: Docker environment variable configuration
- Use
getenv()with explicit false check (false !== getenv()) - Environment variables should override WordPress options for containerized deployments
- Use
2026-02-02 - Custom Metrics & Dashboards (v0.3.0)
- Added Custom Metric Builder with full admin UI:
CustomMetricBuilder.php- CRUD operations, validation, export/import- Support for static values and WordPress option-based values
- Label support (max 5 labels, 50 value combinations)
- Prometheus naming convention validation (
[a-zA-Z_:][a-zA-Z0-9_:]*)
- Added Grafana Dashboard Templates:
DashboardProvider.php- Dashboard file provider with path traversal protectionwordpress-overview.json- General WordPress metricswordpress-runtime.json- HTTP/DB performance metricswordpress-woocommerce.json- WooCommerce store metrics
- Added export/import functionality:
- JSON-based configuration export
- Three import modes: skip, overwrite, rename duplicates
- Version tracking in export format
- Updated Settings page with new tabs:
- "Custom Metrics" tab with metric form and table
- "Dashboards" tab with download buttons
- "Reset Runtime Metrics" button in Metrics tab
- Updated
Collector.phpto integrate custom metrics - Updated translation files with all new strings
- Key Learning: Dynamic form handling in WordPress admin
- Use
wp_create_nonce()with unique nonce names per AJAX action - Localize script with
wp_localize_script()for nonces and AJAX URL - Always verify
current_user_can('manage_options')in AJAX handlers
- Use
- Key Learning: Grafana dashboard JSON format
- Use
${DS_PROMETHEUS}for data source variable - Schema version 39 for current Grafana compatibility
- Panels use
gridPosfor layout positioning
- Use
2026-02-02 - Extended Metrics (v0.2.0)
- Added WooCommerce integration metrics (only when WooCommerce is active):
wordpress_woocommerce_products_total- Products by status and typewordpress_woocommerce_orders_total- Orders by statuswordpress_woocommerce_revenue_total- Revenue (all time, today, month)wordpress_woocommerce_customers_total- Customers (registered, guest)
- Added cron job metrics:
wordpress_cron_events_total- Scheduled cron events by hook (top 20)wordpress_cron_overdue_total- Number of overdue cron eventswordpress_cron_next_run_timestamp- Unix timestamp of next scheduled cron
- Added transient cache metrics:
wordpress_transients_total- Transients by type (total, with_expiration, persistent, expired)
- Updated Settings page with new metric categories
- Updated Help tab with new metrics reference
- Key Learning: WooCommerce HPOS (High-Performance Order Storage) requires different queries
- Check
OrderUtil::custom_orders_table_usage_is_enabled()to determine storage type - HPOS uses
wc_orderstable instead ofpostsandpostmeta
- Check
- Key Learning: Cron event labeling requires cardinality control
- Limit to top 20 hooks to prevent label explosion
- Use
arsort()to get most frequent hooks first
2026-02-02 - Runtime Metrics (v0.1.0)
- Implemented runtime metrics collection for HTTP requests and database queries
- Created
RuntimeCollectorclass that hooks into WordPress request lifecycle - Added new metrics:
wordpress_http_requests_total- Counter by method, status, endpointwordpress_http_request_duration_seconds- Histogram of request durationswordpress_db_queries_total- Counter by endpointwordpress_db_query_duration_seconds- Histogram (requires SAVEQUERIES)
- Updated
Collectorclass to expose stored runtime metrics - Added new settings options in admin for enabling/disabling runtime metrics
- Created translation files (.pot, .po, .mo) for internationalization
- Key Learning: With InMemory Prometheus storage, counters/histograms reset per request
- Solution: Store aggregated data in WordPress options, read during metrics collection
- Histograms exposed as gauge metrics following Prometheus naming conventions (
_bucket,_sum,_count)
- Key Learning: Endpoint normalization is important for cardinality control
- Group requests into categories (admin, ajax, cron, rest-api, frontend, etc.)
- Avoid high-cardinality labels like full URL paths
2026-02-01 - CI/CD Fixes (v0.0.2)
-
Fixed composer.json dependency configuration for CI compatibility
-
Key Learning: Git submodules with path repositories need explicit version aliases for CI:
"repositories": [ { "type": "path", "url": "lib/wc-licensed-product-client", "options": { "symlink": false, "versions": { "magdev/wc-licensed-product-client": "0.2.2" } } } ] -
Using
dev-mainconstraints withminimum-stability: devcauses issues in CI -
Path repository with
symlink: falseand explicitversionsmapping works reliably -
Key Learning: Gitea API returns "Release has no Tag" error when re-releasing existing tags
-
Fixed release.yml to check for and delete existing releases before creating new ones
-
Changed minimum-stability back to stable
2026-02-01 - Initial Setup (v0.0.1)
- Created initial plugin structure based on wp-fedistream blueprint
- Set up composer.json with promphp/prometheus_client_php and wc-licensed-product-client
- Implemented core classes: Plugin, Installer, License/Manager, Metrics/Collector, Endpoint/MetricsEndpoint, Admin/Settings
- Created authenticated /metrics endpoint with Bearer token support
- Added default metrics: wordpress_info, users_total, posts_total, comments_total, plugins_total
- Created extensibility via
wp_prometheus_collect_metricsaction hook - Set up Gitea CI/CD pipeline for automated releases
- Created documentation: README.md, PLAN.md, CHANGELOG.md