Files
wp-prometheus/CLAUDE.md
2026-02-03 19:33:02 +01:00

27 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 /metrics endpoint
  • 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_metrics action hook
  • Dashboard extension hook wp_prometheus_register_dashboards for third-party Grafana dashboards
  • 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 pending roadmap items.

Technical Stack

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 ABSPATH check
  • XSS-safe DOM construction in JavaScript (no innerHTML with 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 .pot file 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)

Translation compilation (.po → .mo) is handled automatically by CI/CD pipeline during release builds. No local compilation needed.

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.0 before building release packages
    • Run composer update --no-dev --optimize-autoloader to rebuild dependencies
  • 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 at wp-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
  • 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
  • Exclusion patterns must match the relative path structure used in zip command
  • Always verify the package structure with unzip -l before 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)
  • 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.md file
  • 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:
    1. Plugin header comment Version: x.x.x - WordPress uses THIS for admin display
    2. 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

Important Git Notes:

  • Default branch while development is dev
  • Create releases from branch main after merging branch dev
  • 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.json changes 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 (but vendor/ is included)

For AI Assistants:

When starting a new session on this project:

  1. Read this CLAUDE.md file first
  2. Semantic versioning follows the MAJOR.MINOR.BUGFIX pattern
  3. Check git log for recent changes
  4. Verify you're on the dev branch before making changes
  5. Run composer install if vendor/ is missing
  6. Test changes before committing
  7. Follow commit message format with Claude Code attribution
  8. Update this session history section with learnings
  9. Never commit backup files (*.po~, *.bak, etc.) - check git status before committing
  10. 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:

  1. 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)

  2. MD056 - Table column count: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths.

  3. MD009 - No trailing spaces: Remove trailing whitespace from lines

  4. MD012 - No multiple consecutive blank lines: Use only single blank lines between sections

  5. 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, use txt.

  6. MD032 - Lists should be surrounded by blank lines: Add a blank line before AND after list blocks, including after bold labels like **Attributes:**.

  7. MD034 - Bare URLs: Wrap URLs in angle brackets (e.g., <https://example.com>) or use markdown link syntax [text](url).

  8. 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 SecureLicenseClient or LicenseClient
  • 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-03 - Database Query Timing Documentation (v0.4.7)

  • Added database query duration distribution panel to Grafana Runtime dashboard
  • Added wordpress_db_query_duration_seconds metric to Help tab metrics reference
  • Added documentation in README explaining how to enable SAVEQUERIES for query timing
  • Updated translation files (.pot and .po) with new strings
  • Key Learning: WordPress SAVEQUERIES constant
    • Enables $wpdb->queries array with query strings, timing, and call stacks
    • Required for wordpress_db_query_duration_seconds histogram metric
    • Has performance overhead - recommended for development, use cautiously in production
    • Without it, only query counts are available (not timing data)

2026-02-03 - Dashboard Extension Hook (v0.4.6)

  • Added wp_prometheus_register_dashboards action hook for third-party plugins
  • Third-party plugins can now register their own Grafana dashboard templates
  • Implementation in DashboardProvider.php:
    • register_dashboard(slug, args) method for registrations
    • Supports file-based dashboards (absolute path to JSON) or inline JSON content
    • Security: Path traversal protection (files must be under WP_CONTENT_DIR)
    • fire_registration_hook() with output buffering and exception handling
    • Respects isolated mode setting (skips third-party hooks when enabled)
    • is_third_party() and get_plugin_name() helper methods
  • Updated admin UI in Settings.php:
    • "Extension" badge displayed on third-party dashboard cards
    • Plugin attribution shown below third-party dashboards
    • Visual distinction with blue border for third-party cards
  • Key Learning: Extension hook design pattern
    • Fire hook lazily on first get_available() call, not in constructor
    • Use $hook_fired flag to prevent double-firing
    • Wrap hook execution in try-catch to isolate failures
    • Validate registrations thoroughly before accepting them
  • Key Learning: Security for file-based registrations
    • Require absolute paths (path_is_absolute())
    • Validate files exist and are readable
    • Use realpath() to resolve symlinks and prevent traversal
    • Restrict to WP_CONTENT_DIR (not just plugin directories)

2026-02-02 - Settings Persistence Fix (v0.4.5)

  • Fixed critical bug where settings would get cleared when saving from different Metrics sub-tabs
  • Root cause: All settings were registered under single wp_prometheus_metrics_settings group
    • When saving from "Endpoint" sub-tab, only auth token was in POST data
    • WordPress Settings API would process all registered settings in the group
    • Missing fields (enabled_metrics, isolated_mode) would receive null/undefined
    • Sanitize callbacks returned empty values, overwriting existing settings
  • Solution: Split into separate settings groups per sub-tab:
    • wp_prometheus_endpoint_settings for auth token
    • wp_prometheus_selection_settings for enabled metrics
    • wp_prometheus_advanced_settings for isolated mode
  • Key Learning: WordPress Settings API and multiple forms
    • When multiple forms share the same settings group, saving one form can clear settings from another
    • Each form with settings_fields() should use a unique option group
    • register_setting() group name must match settings_fields() group name

2026-02-02 - Safe Mode & Custom Hooks Fix (v0.4.4)

  • Redesigned metrics collection to support both plugin compatibility AND custom metrics:
    • Safe Mode (default): Removes content filters early but lets WordPress load normally
    • Isolated Mode: Legacy early mode that skips custom hooks entirely
  • Implementation:
    • WP_PROMETHEUS_METRICS_REQUEST constant set for any /metrics request
    • Content filters removed via plugins_loaded hook at priority 0
    • Collector fires wp_prometheus_collect_metrics with protection (output buffering, try-catch)
    • wp_prometheus_isolated_mode option replaces wp_prometheus_disable_early_mode
    • WP_PROMETHEUS_ISOLATED_MODE environment variable for containerized deployments
  • Collector now wraps custom hooks in fire_custom_metrics_hook() method:
    • Removes content filters again before hook (in case re-added)
    • Uses output buffering to discard accidental output
    • Catches exceptions to prevent breaking metrics output
    • Logs errors when WP_DEBUG is enabled
  • Updated admin UI with mode comparison table
  • Key Learning: Hybrid approach for plugin compatibility
    • The memory issue comes from content filter recursion, not just plugin loading
    • Removing filters early (before any plugin can trigger them) prevents recursion
    • Plugins still load and can register their wp_prometheus_collect_metrics hooks
    • Hooks fire after filters are removed, in a protected context
  • Key Learning: Defense in depth for custom hooks
    • Remove filters again right before hook fires (plugins may re-add them)
    • Output buffering catches any echo/print from misbehaving plugins
    • Try-catch prevents one broken plugin from breaking metrics entirely

2026-02-02 - Sub-tabs & Early Mode Fix (v0.4.3)

  • Split Metrics tab into sub-tabs for better organization:
    • Endpoint: Authentication token configuration
    • Selection: Enable/disable individual metrics
    • Runtime: Reset runtime metrics data
    • Advanced: Early mode toggle and status
  • Fixed early mode setting not being saved (was outside form element)
  • Added CSS styling for horizontal sub-tab navigation
  • Key Learning: WordPress Settings API form structure
    • Settings must be inside <form action="options.php"> with settings_fields() call
    • Each sub-tab needs its own form wrapper for proper saving
    • Sub-tabs use URL query parameter (subtab) within the main tab
  • Key Learning: WordPress plugin versioning requires TWO updates
    • Plugin header comment Version: x.x.x (line ~6) - used by WordPress admin
    • PHP constant WP_PROMETHEUS_VERSION (line ~133) - used internally
    • CI/CD checks both must match the git tag, causing release failures if mismatched

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_mode WordPress option
    • Added WP_PROMETHEUS_DISABLE_EARLY_MODE environment variable support
    • Option check in wp_prometheus_early_metrics_check() before early interception
    • Environment variable accepts 1, true, yes, on (case-insensitive)
  • 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_metrics hook
    • Users with custom metrics need the hook, so early mode must be optional
    • Default remains enabled (safe) with explicit opt-out for advanced users

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 /metrics pattern before plugins_loaded fires
    • Defines WP_PROMETHEUS_EARLY_METRICS constant to signal early mode
    • Removes content filters (the_content, the_excerpt, get_the_excerpt, the_title)
    • Collector skips wp_prometheus_collect_metrics action in early mode
    • Changed MetricsEndpoint from template_redirect to parse_request hook
  • Key Learning: WordPress plugin loading order and hook timing
    • Plugins load alphabetically, so wp-fedistream ('f') loads before wp-prometheus ('p')
    • template_redirect fires too late - after themes and Twig initialize
    • parse_request fires 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 triggers apply_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

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 selection
    • WP_PROMETHEUS_REDIS_HOST, _PORT, _PASSWORD, _DATABASE, _PREFIX
    • WP_PROMETHEUS_APCU_PREFIX
    • Environment variables take precedence over admin settings
  • Updated Collector.php to use StorageFactory::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

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 protection
    • wordpress-overview.json - General WordPress metrics
    • wordpress-runtime.json - HTTP/DB performance metrics
    • wordpress-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.php to 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
  • Key Learning: Grafana dashboard JSON format
    • Use ${DS_PROMETHEUS} for data source variable
    • Schema version 39 for current Grafana compatibility
    • Panels use gridPos for layout positioning

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 type
    • wordpress_woocommerce_orders_total - Orders by status
    • wordpress_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 events
    • wordpress_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_orders table instead of posts and postmeta
  • 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 RuntimeCollector class that hooks into WordPress request lifecycle
  • Added new metrics:
    • wordpress_http_requests_total - Counter by method, status, endpoint
    • wordpress_http_request_duration_seconds - Histogram of request durations
    • wordpress_db_queries_total - Counter by endpoint
    • wordpress_db_query_duration_seconds - Histogram (requires SAVEQUERIES)
  • Updated Collector class 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-main constraints with minimum-stability: dev causes issues in CI

  • Path repository with symlink: false and explicit versions mapping 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_metrics action hook
  • Set up Gitea CI/CD pipeline for automated releases
  • Created documentation: README.md, PLAN.md, CHANGELOG.md