- Add RuntimeCollector class for tracking request lifecycle metrics - Add wordpress_http_requests_total counter (method, status, endpoint) - Add wordpress_http_request_duration_seconds histogram - Add wordpress_db_queries_total counter (endpoint) - Add wordpress_db_query_duration_seconds histogram (requires SAVEQUERIES) - Update Collector to expose stored runtime metrics - Add new settings options for enabling/disabling runtime metrics - Create translation files (.pot, .po, .mo) for internationalization - Update documentation and changelog Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 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)
- 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.
Version 0.2.0 (Planned)
- WooCommerce integration metrics
- Cron job metrics
- Transient cache metrics
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
│ └── js/
│ └── admin.js # Admin JavaScript
├── languages/ # Translation files
├── lib/
│ └── wc-licensed-product-client/ # Git submodule
├── releases/ # Release packages
├── src/
│ ├── Admin/
│ │ └── Settings.php # Settings page
│ ├── Endpoint/
│ │ └── MetricsEndpoint.php # /metrics endpoint
│ ├── License/
│ │ └── Manager.php # License management
│ ├── Metrics/
│ │ ├── Collector.php # Prometheus metrics collector
│ │ └── RuntimeCollector.php # Runtime metrics collector
│ ├── 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 - 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