diff --git a/.changeset/nasty-dodos-know.md b/.changeset/nasty-dodos-know.md new file mode 100644 index 00000000..20c988f6 --- /dev/null +++ b/.changeset/nasty-dodos-know.md @@ -0,0 +1,6 @@ +--- +"@wpengine/wpgraphql-logging-wordpress-plugin": minor +--- + +chore: Fixing some snags and updating docs for BETA release. + diff --git a/docs/how-to/install-toolkit-plugins/index.md b/docs/how-to/install-toolkit-plugins/index.md index 5ee2c251..ea0e08e2 100644 --- a/docs/how-to/install-toolkit-plugins/index.md +++ b/docs/how-to/install-toolkit-plugins/index.md @@ -10,6 +10,13 @@ You can also install them manually from our [Releases](https://github.com/wpengi - WordPress 6.0+ - PHP 7.4+ +### Available Plugins + +- [wpengine/hwp-previews](https://github.com/wpengine/hwptoolkit/tree/main/plugins/hwp-previews#readme) +- [wpengine/wpgraphql-webhooks](https://github.com/wpengine/hwptoolkit/tree/main/plugins/wpgraphql-webhooks#readme) +- [wpengine/wpgraphql-debug-extensions](https://github.com/wpengine/hwptoolkit/tree/main/plugins/wpgraphql-debug-extensions#readme) +- [wpengine/wpgraphql-logging](https://github.com/wpengine/hwptoolkit/tree/main/plugins/wpgraphql-logging#readme) + ## Quick Start: Example `composer.json` Copy and use this example as your `composer.json` for a typical WordPress project using HWP Toolkit plugins: @@ -26,7 +33,9 @@ Copy and use this example as your `composer.json` for a typical WordPress projec ], "require": { "wpengine/hwp-previews": "*", - "wpengine/wpgraphql-webhooks": "*" + "wpengine/wpgraphql-webhooks": "*", + "wpengine/wpgraphql-debug-extensions": "*", + "wpengine/wpgraphql-logging": "*" }, "config": { "allow-plugins": { @@ -57,7 +66,7 @@ Copy and use this example as your `composer.json` for a typical WordPress projec To update to the latest version: ```bash -composer update wpengine/hwp-previews wpengine/wpgraphql-webhooks +composer update wpengine/hwp-previews wpengine/wpgraphql-webhooks wpengine/wpgraphql-debug-extensions wpengine/wpgraphql-logging ``` --- diff --git a/plugins/wpgraphql-logging/README.md b/plugins/wpgraphql-logging/README.md index 4060c0c5..e915514b 100644 --- a/plugins/wpgraphql-logging/README.md +++ b/plugins/wpgraphql-logging/README.md @@ -2,30 +2,18 @@ A WPGraphQL logging plugin that provides visibility into request lifecycle to help quickly identify and resolve bottlenecks in your headless WordPress application. -* [Join the Headless WordPress community on Discord.](https://discord.gg/headless-wordpress-836253505944813629) -* [Documentation](#getting-started) - > [!CAUTION] -> This plugin is currently in alpha state and is not production ready but please feel free to test. - ------ - -@TODO +> This plugin is currently in beta state and is not production ready but please feel free to test. ----- - - -## Table of Contents - -- [Overview](#overview) -- [Getting Started](#getting-started) -- [Features](#features) -- [Usage](#usage) -- [Configuration](#configuration) -- [Admin & Settings](#admin--settings) -- [Extending the Functionality](#extending-the-functionality) -- [Testing](#testing) +[![Version](https://img.shields.io/github/v/release/wpengine/hwptoolkit?include_prereleases&label=wpgraphql-logging&filter=%40wpengine%2Fwpgraphql-logging-wordpress-plugin-*)](https://github.com/wpengine/hwptoolkit/releases) +[![License](https://img.shields.io/badge/license-GPLv2%2B-green)](https://www.gnu.org/licenses/gpl-2.0.html) +![GitHub forks](https://img.shields.io/github/forks/wpengine/hwptoolkit?style=social) +![GitHub stars](https://img.shields.io/github/stars/wpengine/hwptoolkit?style=social) +[![Testing Integration](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=wpgraphql-logging%20codeception%20tests&label=Automated%20Tests)](https://github.com/wpengine/hwptoolkit/actions) +[![Code Coverage](https://img.shields.io/badge/coverage-%3E90%25-brightgreen?label=Code%20Coverage)](https://github.com/wpengine/hwptoolkit/actions) +[![Code Quality](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=wpgraphql-logging%20php%20code%20quality%20checks&label=Code%20Quality%20Checks)](https://github.com/wpengine/hwptoolkit/actions) ----- @@ -33,268 +21,45 @@ A WPGraphQL logging plugin that provides visibility into request lifecycle to he ## Overview -WPGraphQL Logging is a plugin that integrates directly with the WPGraphQL Query Lifecycle, capturing detailed information about each GraphQL request. By leveraging the powerful [Monolog](https://github.com/Seldaek/monolog) logging library, it records events and metrics that help you quickly identify performance bottlenecks and other issues in your headless WordPress application. - -Designed with extensibility in mind, developers can easily customize and extend the logging functionality to suit their specific needs, making it a valuable tool for debugging and optimizing WPGraphQL-powered sites. - ---- - - -## Getting Started - -To install, you need to follow our guide here to install the plugin via composer - [https://github.com/wpengine/hwptoolkit/blob/main/docs/how-to/install-toolkit-plugins/index.md] - -Once you have the composer repository setup, please run `composer req wpengine/wpgraphql-logging:*` to install the plugin. - -Plugin should start logging data, once activated. - ---- - -## Uninstallation and Data Cleanup - -By default, WPGraphQL Logging preserves all logged data when the plugin is deactivated to prevent accidental data loss. If you want to completely remove all plugin data (including database tables) when deactivating the plugin, you must explicitly enable this behavior. - -### Enabling Database Cleanup on Deactivation +WPGraphQL Logging is a plugin that integrates directly with the WPGraphQL Query Lifecycle, capturing detailed information about each GraphQL request. -To enable automatic database cleanup when the plugin is deactivated, add the following constant to your `wp-config.php` file or in a must-use plugin: +### Key Features +* **Granular Control**: Choose which events in the request lifecycle to log, giving you precise control over the data you capture. +* **Developer-Friendly Extensibility**: Built with developers in mind, it features a pub/sub system that allows you to hook into the logging pipeline, transform event data, and trigger custom actions. +* **Flexible Log Handling**: Leverages the powerful Monolog logging library, enabling developers to add custom processors and handlers to route logs to various destinations like Slack, files, or external services. -```php -define( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN', true ); -``` - -> [!WARNING] -> **Data Loss Warning**: When `WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN` is defined as `true`, deactivating the plugin will permanently delete all logged data and drop the plugin's database tables. This action is irreversible. - -### Manual Data Cleanup - -If you prefer to manually clean up data without defining the constant, you can: - -1. Use the plugin's admin interface to clear logs (when available) -2. Manually drop the database table: `{$wpdb->prefix}wpgraphql_logging` -3. Remove plugin options from the WordPress options table +>[!IMPORTANT] +>For detailed developer guides and examples, see our [How-To Guides](docs/index.md#how-to-guides). --- -@TODO add more info once we have configuration setup. - -@TODO add more info once we have configuration setup. - - ---- - -## Project Structure - -```text -wpgraphql-logging/ -├── docs/ # Docs for extending the plugin. -├── src/ # Main plugin source code -│ ├── Admin/ # Admin settings, menu, and settings page logic -│ ├── Settings/ # Admin settings functionality for displaying and saving data. -│ ├── Events/ # Event logging, pub/sub event manager for extending the logging. -│ ├── Logger/ # Logging logic, logger service, Monolog handlers & processors -│ ├── Rules/ # Rule Management on whether we log a query -│ ├── Scheduler/ # Automated data cleanup and maintenance tasks -│ ├── Plugin.php # Main plugin class (entry point) -│ └── Autoload.php # PSR-4 autoloader -├── tests/ # All test suites -│ ├── wpunit/ # WPBrowser/Codeception unit tests -├── [wpgraphql-logging.php] -├── [activation.php] -├── [composer.json] -├── [deactivation.php] -├── [TESTING.md] -├── [README.md] -``` - -## Features - -- **Query event lifecycle logging** - - **Pre Request** (`do_graphql_request`): captures `query`, `variables`, `operation_name`. - - **Before Execution** (`graphql_before_execute`): includes a snapshot of request `params`. - - **Before Response Returned** (`graphql_return_response`): inspects `response` and automatically upgrades level to Error when GraphQL `errors` are present (adds `errors` to context when found). - -- **Built-in pub/sub event bus** - - In-memory event manager with priorities: `subscribe(event, listener, priority)` and `publish(event, payload)`. - - Transform pipeline: `transform(event, payload)` lets you mutate `context` and `level` before logging/publishing. - - WordPress bridges: actions `wpgraphql_logging_event_{event}` and filters `wpgraphql_logging_filter_{event}` to integrate with standard hooks. - -- **Monolog-powered logging pipeline** - - Default handler: stores logs in a WordPress table (`{$wpdb->prefix}wpgraphql_logging`). - -- **Automated data management** - - **Daily cleanup scheduler**: Automatically removes old logs based on retention settings. - - **Configurable retention period**: Set how many days to keep log data (default: 30 days). - - **Manual cleanup**: Admin interface to trigger immediate cleanup of old logs. - - **Data sanitization**: Remove sensitive fields from logged data for privacy compliance. - -- **Simple developer API** - - `Plugin::on()` to subscribe, `Plugin::emit()` to publish, `Plugin::transform()` to modify payloads. - -- **Safe-by-default listeners/transforms** - - Exceptions in listeners/transforms are caught and logged without breaking the pipeline. - ---- - -## Data Sanitization - -WPGraphQL Logging includes robust data sanitization capabilities to help you protect sensitive information while maintaining useful logs for debugging and monitoring. The sanitization system allows you to automatically clean, anonymize, or remove sensitive fields from log records before they are stored. +## Requirements -### Why Data Sanitization Matters +- WordPress 6.5+ +- WPGraphQL 2.3+ +- PHP 8.1.2+ -When logging GraphQL requests, context data often contains sensitive information such as: -- User authentication tokens -- Personal identification information (PII) -- Password fields -- Session data -- Internal system information -Data sanitization ensures compliance with privacy regulations (GDPR, CCPA) and security best practices while preserving the debugging value of your logs. +## Installation -### Sanitization Methods +### Option 1: Plugin Zip -The plugin offers two sanitization approaches: +You can get the latest release from . -#### 1. Recommended Rules (Default) -Pre-configured rules that automatically sanitize common WordPress and WPGraphQL sensitive fields: -- `request.app_context.viewer.data` - User data object -- `request.app_context.viewer.allcaps` - User capabilities -- `request.app_context.viewer.cap_key` - Capability keys -- `request.app_context.viewer.caps` - User capability array +### Option 2: Composer -#### 2. Custom Rules -Define your own sanitization rules using dot notation to target specific fields: +To install, you need to follow our guide here to install the plugin via composer - -**Field Path Examples:** -``` -variables.password -request.headers.authorization -user.email -variables.input.creditCard -``` - -### Sanitization Actions - -For each field, you can choose from three sanitization actions: - -| Action | Description | Example | -|--------|-------------|---------| -| **Remove** | Completely removes the field from logs | `password: "secret123"` → *field removed* | -| **Anonymize** | Replaces value with `***` | `email: "user@example.com"` → `email: "***"` | -| **Truncate** | Limits string length to 47 characters + `...` | `longText: "Very long text..."` → `longText: "Very long text that gets cut off here and mo..."` | - -### Configuration - -Enable and configure data sanitization through the WordPress admin: - -1. Navigate to **GraphQL Logging → Settings** -2. Click the **Data Management** tab -3. Enable **Data Sanitization** -4. Choose your sanitization method: - - **Recommended**: Uses pre-configured rules for common sensitive fields - - **Custom**: Define your own field-specific rules - -#### Custom Configuration Fields - -When using custom rules, configure the following fields: - -- **Fields to Remove**: Comma-separated list of field paths to completely remove -- **Fields to Anonymize**: Comma-separated list of field paths to replace with `***` -- **Fields to Truncate**: Comma-separated list of field paths to limit length - -**Example Configuration:** -``` -Remove: variables.password, request.headers.authorization -Anonymize: user.email, variables.input.personalInfo -Truncate: query, variables.input.description -``` - -### Developer Hooks - -Customize sanitization behavior using WordPress filters: - -```php -// Enable/disable sanitization programmatically -add_filter( 'wpgraphql_logging_data_sanitization_enabled', function( $enabled ) { - return current_user_can( 'manage_options' ) ? false : $enabled; -}); - -// Modify recommended rules -add_filter( 'wpgraphql_logging_data_sanitization_recommended_rules', function( $rules ) { - $rules['custom.sensitive.field'] = 'remove'; - return $rules; -}); - -// Modify all sanitization rules -add_filter( 'wpgraphql_logging_data_sanitization_rules', function( $rules ) { - // Add additional rules or modify existing ones - $rules['request.custom_header'] = 'anonymize'; - return $rules; -}); - -// Modify the final log record after sanitization -add_filter( 'wpgraphql_logging_data_sanitization_record', function( $record ) { - // Additional processing after sanitization - return $record; -}); -``` - -### Performance Considerations - -- Sanitization runs on every log record when enabled -- Complex nested field paths may impact performance on high-traffic sites -- Consider using recommended rules for optimal performance -- Test custom rules thoroughly to ensure they target the intended fields - -### Security Best Practices - -1. **Review logs regularly** to ensure sanitization is working as expected -2. **Test field paths** in a development environment before applying to production -3. **Use remove over anonymize** for highly sensitive data -4. **Monitor performance impact** when implementing extensive custom rules -5. **Keep rules updated** as your GraphQL schema evolves - ---- - -## Usage - -WPGraphQL Logging Plugin is highly configurable and extendable and built with developers in mind to allow them to modify, change or add data, loggers etc to this plugin. Please read the docs below: - - -The following documentation is available in the `docs/` directory: - -- [Events](docs/Events.md): - Learn about the event system, available events, and how to subscribe, transform, or listen to WPGraphQL Logging events. - -- [Logging](docs/Logging.md): - Learn about the logging system, Monolog integration, handlers, processors, and how to use or extend the logger. - -- [Admin](docs/admin.md): - Learn how the admin settings page works, all available hooks, and how to add tabs/fields via actions and filters. - ---- - - - -## Configuration - -@TODO - When we integrate plugin configuration. - ---- - -### Settings +Once you have the composer repository setup, please run `composer req wpengine/wpgraphql-logging:*` to install the plugin. -@TODO - When we integrate plugin configuration. +Once installed and configured, the plugin should begin to log uncached WPGraphQL logs into the database. --- -## Admin & Settings - -See `docs/admin.md` for a full overview of the admin/settings architecture, hooks, and examples for adding tabs and fields. - -## Testing +## Documentation -See [Testing.md](TESTING.md) for details on how to test the plugin. +For detailed usage instructions, developer references, and examples, please visit the [Documentation](docs/index.md) folder included with this plugin. -## Screenshots -@TODO - When before BETA release. +## License +WP GPL 2 diff --git a/plugins/wpgraphql-logging/docs/ConfigurationHelper.md b/plugins/wpgraphql-logging/docs/ConfigurationHelper.md deleted file mode 100644 index 706f68f9..00000000 --- a/plugins/wpgraphql-logging/docs/ConfigurationHelper.md +++ /dev/null @@ -1,105 +0,0 @@ -# Configuration Helper Usage Examples - -The `ConfigurationHelper` class provides a centralized and cached way to access WPGraphQL Logging configuration. This class implements a singleton pattern to ensure configuration is only loaded once per request and provides convenient methods for accessing different configuration sections. - -## Basic Usage - -### Getting the Configuration Helper Instance - -```php -use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; - -$config_helper = ConfigurationHelper::get_instance(); -``` - -### Getting Full Configuration - -```php -$config_helper = ConfigurationHelper::get_instance(); -$full_config = $config_helper->get_config(); -``` - -### Getting Configuration Sections - -```php -$config_helper = ConfigurationHelper::get_instance(); - -// Get basic configuration -$basic_config = $config_helper->get_basic_config(); - -// Get data management configuration -$data_management_config = $config_helper->get_data_management_config(); - -// Get any custom section -$custom_section = $config_helper->get_section_config('custom_section', []); -``` - -### Getting Specific Settings - -```php -$config_helper = ConfigurationHelper::get_instance(); - -// Get a specific setting from a section -$log_level = $config_helper->get_setting('basic_configuration', 'log_level', 'info'); - -// Check if a feature is enabled -$is_sanitization_enabled = $config_helper->is_enabled('data_management', 'data_sanitization_enabled'); -``` - -## Migration from Direct get_option() Usage - -### Before (old approach): -```php -$full_config = get_option( WPGRAPHQL_LOGGING_SETTINGS_KEY, [] ); -$basic_config = $full_config['basic_configuration'] ?? []; -$log_level = $basic_config['log_level'] ?? 'info'; -``` - -### After (using ConfigurationHelper): -```php -$config_helper = ConfigurationHelper::get_instance(); -$log_level = $config_helper->get_setting('basic_configuration', 'log_level', 'info'); -``` - -## Cache Management - -### Clearing Cache -```php -$config_helper = ConfigurationHelper::get_instance(); -$config_helper->clear_cache(); // Clears cache, next access will reload from DB -``` - -### Force Reload -```php -$config_helper = ConfigurationHelper::get_instance(); -$config_helper->reload_config(); // Clears cache and immediately reloads -``` - -## Benefits - -1. **Performance**: Configuration is cached in memory and only loaded once per request -2. **Consistency**: Centralized access point prevents inconsistent configuration retrieval -3. **Convenience**: Convenient methods for common access patterns -4. **Cache Management**: Automatic cache invalidation when settings are updated -5. **Type Safety**: Better type hints and documentation - -## Automatic Cache Invalidation - -The ConfigurationHelper automatically clears its cache when WordPress settings are updated. This is initialized in the main Plugin class: - -```php -// This is already set up in src/Plugin.php -ConfigurationHelper::init_cache_hooks(); -``` - -The cache hooks listen for: -- `update_option_{$option_key}` -- `add_option_{$option_key}` -- `delete_option_{$option_key}` - -## Performance Notes - -- Configuration is cached using WordPress's `wp_cache_*` functions -- Multiple cache groups are used for optimal performance -- Cache duration is set to 1 hour by default -- In-memory caching ensures subsequent accesses within the same request are instant diff --git a/plugins/wpgraphql-logging/docs/Events.md b/plugins/wpgraphql-logging/docs/Events.md deleted file mode 100644 index f6d05387..00000000 --- a/plugins/wpgraphql-logging/docs/Events.md +++ /dev/null @@ -1,132 +0,0 @@ -# Events in WPGraphQL Logging - -## Table of Contents - -- [Overview](#overview) -- [Available Events](#available-events) -- [Usage](#usage) - - [Example 1: How to subscribe to an event](#example-1-how-to-subscribe-to-an-event) - - [Example 2: How to add context to an event](#example-2-how-to-add-context-to-an-event) - - [Example 3: How to run a WPGraphQL event](#example-3-how-to-run-a-wpgraphql-event) - - [Example 4: Use WordPress hooks](#example-4-use-wordpress-hooks) - -## Overview - -WPGraphQL Logging implements a pub/sub pattern for events to subscribe to certain WPGraphQL events and allows users to subscribe/publish or transform events. - -This is achieved in the following classes under `src/Events/`: - -- **Events** - List of events the plugin hooks into for WPGraphQL -- **EventManager** - An event manager which creates a pub/sub pattern to allow users to subscribe/publish events and also transform context or level for the current event -- **QueryEventLifecycle** - The service that puts this all together and creates the logic and logs the data into the LoggerService (Monolog logger) - -> **Note**: If we are missing anything from this event system, please feel free to create an issue or contribute. - -## Available Events - -Currently we subscribe to the following WPGraphQL events: - -| Event Constant | WPGraphQL Hook | Description | -| --- | --- | --- | -| `Events::PRE_REQUEST` | `do_graphql_request` | Before the request is processed | -| `Events::BEFORE_GRAPHQL_EXECUTION` | `graphql_before_execute` | Before query execution | -| `Events::BEFORE_RESPONSE_RETURNED` | `graphql_return_response` | Before response is returned to client | - -## Usage - -### Example 1: How to subscribe to an event - -**Use case** You would like to access data for a specific event. - -**Example** - - -```php -info('Resolving field', [ - 'field' => $info->fieldName ?? '', - 'type' => method_exists($info, 'parentType') ? (string) $info->parentType : '', - ]); -}, 10, 4); - -``` - -### Example 4. Use WordPress hooks - -In addition to the internal API, every event also triggers standard WordPress hooks: - -- Action: `wpgraphql_logging_event_{event_name}` receives the published payload -- Filter: `wpgraphql_logging_filter_{event_name}` can modify the payload before logging - - ```php - **Note**: This system leverages the full power of Monolog. For advanced usage, refer to the [Monolog documentation](https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md). - -## Architecture - -### Channels -Channels group log messages by context or component. The default channel is `wpgraphql_logging`, but you can create multiple logger instances with different channels for different parts of your application. - -### Handlers -Handlers determine where log records are written. The plugin includes a simple database handler. -- **WordPressDatabaseHandler** - Writes logs to a WordPress database table (`{$wpdb->prefix}wpgraphql_logging`) - -### Processors -Processors add extra data to each log record. The plugin includes several default processors: - -- **MemoryUsageProcessor** - Adds current memory usage -- **MemoryPeakUsageProcessor** - Adds peak memory usage -- **WebProcessor** - Adds web request data (IP, method, URI, etc.) -- **ProcessIdProcessor** - Adds the process ID -- **RequestHeadersProcessor** - Adds requests headers - -## Default Components - -The LoggerService comes configured with sensible defaults: - -| Component | Default Implementation | Purpose | -| --- | --- | --- | -| Handler | `WordPressDatabaseHandler` | Stores logs in WordPress database | -| Processors | Memory, Web, Process ID, WPGraphQL Query | Enriches log records with contextual data | -| Log Levels | All levels (DEBUG to EMERGENCY) | Monolog's standard PSR-3 log levels | -| Default Context | WP version, plugin version, debug mode, site URL | Consistent context across all logs | - -## Usage - -### Example 1: Basic logging with LoggerService - -**Use case:** You want to log custom events from your plugin or theme. - -```php -info('User performed action', ['user_id' => 123, 'action' => 'login']); -$logger->warning('Rate limit approaching', ['requests' => 95, 'limit' => 100]); -$logger->error('Database connection failed', ['error' => $exception->getMessage()]); - -// Use the generic log method with Monolog levels -$logger->log(Level::Debug, 'Debug information', ['debug_data' => $debug_info]); -``` - -### Example 2: Creating a custom logger instance - -**Use case:** You need a separate logger with a different channel for a specific component. - -```php - 'my_plugin'] // Custom default context -); - -$custom_logger->info('Custom component event', ['data' => 'example']); -``` - -### Example 3: Adding custom handlers - -**Use case:** You want to log to multiple destinations (database + file + external service). - -```php -error('Critical error occurred', ['error' => 'Something went wrong']); -``` - -### Example 4: Adding custom processors - -**Use case:** You want to add custom data to all log records, such as user information or custom metrics. - -```php -extra['user_id'] = get_current_user_id(); - $record->extra['user_role'] = wp_get_current_user()->roles[0] ?? 'unknown'; - } - return $record; - } -} - -// Add custom processors -$processors = array_merge( - LoggerService::get_default_processors(), - [new UserContextProcessor()] -); - -$logger = LoggerService::get_instance('user_aware', null, $processors); -$logger->info('User action logged'); // Will include user_id and user_role in extra data -``` - -### Example 5: Customizing log levels and filtering - -**Use case:** You want to control which log levels are actually processed, or apply filtering based on content. - -```php -debug('Debug message'); // Won't be logged -$logger->info('Info message'); // Won't be logged -$logger->warning('Warning message'); // Will be logged -$logger->error('Error message'); // Will be logged -``` - -### Example 6: Using WordPress filters to modify defaults - -**Use case:** You want to globally modify the default handlers, processors, or context for all LoggerService instances. - -```php -info('This will include the custom context and use all handlers'); -``` - -## Available Log Levels - -WPGraphQL Logging supports all standard [PSR-3 log levels](https://www.php-fig.org/psr/psr-3/) via Monolog: - -| Level | Method | Use Case | -| --- | --- | --- | -| `EMERGENCY` | `$logger->emergency()` | System is unusable | -| `ALERT` | `$logger->alert()` | Action must be taken immediately | -| `CRITICAL` | `$logger->critical()` | Critical conditions | -| `ERROR` | `$logger->error()` | Error conditions | -| `WARNING` | `$logger->warning()` | Warning conditions | -| `NOTICE` | `$logger->notice()` | Normal but significant condition | -| `INFO` | `$logger->info()` | Informational messages | -| `DEBUG` | `$logger->debug()` | Debug-level messages | - -You can also use the generic `$logger->log($level, $message, $context)` method with `Monolog\Level` constants. - -## WordPress Filters - -The following WordPress filters are available to customize the logging system: - -- `wpgraphql_logging_default_handlers` - Modify default handlers -- `wpgraphql_logging_default_processors` - Modify default processors -- `wpgraphql_logging_default_context` - Modify default context -- `wpgraphql_logging_database_name` - Customize the database table name - -## Further Reading - -- [Monolog Documentation](https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md) -- [PSR-3 Logger Interface](https://www.php-fig.org/psr/psr-3/) -- [Monolog Handlers](https://github.com/Seldaek/monolog/blob/main/doc/02-handlers-formatters-processors.md#handlers) -- [Monolog Processors](https://github.com/Seldaek/monolog/blob/main/doc/02-handlers-formatters-processors.md#processors) diff --git a/plugins/wpgraphql-logging/docs/admin.md b/plugins/wpgraphql-logging/docs/admin.md deleted file mode 100644 index cbaab46b..00000000 --- a/plugins/wpgraphql-logging/docs/admin.md +++ /dev/null @@ -1,193 +0,0 @@ -### Admin and Settings - -This document explains how the WPGraphQL Logging admin settings UI is built and how to extend it with your own tabs and fields. - ---- - -## Architecture Overview - -- **Settings page entry**: `WPGraphQL\Logging\Admin\SettingsPage` - - Registers the submenu page and orchestrates fields and tabs - - Hooks added: `init` (init fields), `admin_menu` (page), `admin_init` (fields), `admin_enqueue_scripts` (assets) -- **Menu page**: `WPGraphQL\Logging\Admin\Settings\Menu\MenuPage` - - Adds a submenu under Settings → WPGraphQL Logging (`wpgraphql-logging`) - - Renders template `src/Admin/Settings/Templates/admin.php` -- **Form manager**: `WPGraphQL\Logging\Admin\Settings\SettingsFormManager` - - Registers the settings (`register_setting`) and sections/fields per tab - - Sanitizes and saves values per tab; unknown fields are pruned -- **Field collection**: `WPGraphQL\Logging\Admin\Settings\Fields\SettingsFieldCollection` - - Holds all tabs and fields. A default `BasicConfigurationTab` is registered -- **Tabs**: Implement `SettingsTabInterface` with `get_name()`, `get_label()`, `get_fields()` -- **Fields**: Implement `SettingsFieldInterface` or use built-ins: - - `Field\CheckboxField` - - `Field\TextInputField` - - `Field\SelectField` - -Settings are stored in an array option. Keys are filterable: - -- Option key: `wpgraphql_logging_settings` (filter `wpgraphql_logging_settings_group_option_key`) -- Settings group: `wpgraphql_logging_settings_group` (filter `wpgraphql_logging_settings_group_settings_group`) - -To read values at runtime, use `WPGraphQL\Logging\Admin\Settings\LoggingSettingsService`: - -```php -use WPGraphQL\Logging\Admin\Settings\LoggingSettingsService; - -$settings = new LoggingSettingsService(); -$enabled = $settings->get_setting('basic_configuration', 'enabled', false); -``` - ---- - -## Hooks Reference (Admin) - -- Action: `wpgraphql_logging_settings_init( SettingsPage $instance )` - - Fired after the settings page is initialized -- Action: `wpgraphql_logging_settings_field_collection_init( SettingsFieldCollection $collection )` - - Fired after default tabs/fields are registered; primary extension point to add tabs/fields -- Action: `wpgraphql_logging_settings_form_manager_init( SettingsFormManager $manager )` - - Fired when the form manager is constructed -- Filter: `wpgraphql_logging_settings_group_option_key( string $option_key )` - - Change the option key used to store settings -- Filter: `wpgraphql_logging_settings_group_settings_group( string $group )` - - Change the settings group name used in `register_setting` - -- Filter: `wpgraphql_logging_basic_configuration_fields( array $fields )` - - Modify the default fields rendered in the `basic_configuration` tab. You can add, remove, or replace fields by returning a modified associative array of `field_id => SettingsFieldInterface`. - - Example: - ```php - use WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField; - - add_filter('wpgraphql_logging_basic_configuration_fields', function(array $fields): array { - // Add a custom toggle into the Basic Configuration tab - $fields['enable_feature_x'] = new CheckboxField( - 'enable_feature_x', - 'basic_configuration', - 'Enable Feature X', - '', - 'Turn on extra logging for Feature X.' - ); - - // Optionally remove an existing field - // unset($fields[ WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab::DATA_SAMPLING ]); - - return $fields; - }); - ``` - -Related (non-admin) hooks for context: - -- Action: `wpgraphql_logging_init( Plugin $instance )` (plugin initialized) -- Action: `wpgraphql_logging_activate` / `wpgraphql_logging_deactivate` - ---- - -## Add a New Tab - -Create a tab class implementing `SettingsTabInterface` and register it during `wpgraphql_logging_settings_field_collection_init`. - -```php - new TextInputField( - 'my_setting', - $this->get_name(), - 'My Setting', - '', - 'Describe what this setting does.', - 'e.g., value' - ), - ]; - } -} - -add_action('wpgraphql_logging_settings_field_collection_init', function (SettingsFieldCollection $collection): void { - $collection->add_tab(new My_Custom_Tab()); -}); -``` - -Notes: - -- `get_name()` must be a unique slug; it is used in the admin page URL (`tab` query arg) and section IDs -- Fields returned by `get_fields()` must set their `tab` to this slug so they render on the tab - ---- - -## Add a Field to an Existing Tab - -You can add fields directly to the shared field collection. Ensure the field’s `tab` matches the target tab name. - -```php -add_field( - 'enable_feature_x', - new CheckboxField( - 'enable_feature_x', - 'basic_configuration', // target the built-in Basic Configuration tab - 'Enable Feature X', - '', - 'Turn on extra logging for Feature X.' - ) - ); -}); -``` - -Tips: - -- Only fields present in the collection are saved; unknown keys are pruned during sanitize -- Field input names follow: `{$option_key}[{$tab}][{$field_id}]` - ---- - -## Reading/Saving Behavior - -- Each submit saves only the current tab’s fields -- Sanitization is delegated to each field via `sanitize_field($value)` -- Unknown fields or tabs are ignored/pruned - -Example of reading a value elsewhere: - -```php -use WPGraphQL\Logging\Admin\Settings\LoggingSettingsService; - -$settings = new LoggingSettingsService(); -$thresholdSeconds = (float) $settings->get_setting('basic_configuration', 'performance_metrics', '0'); -``` - ---- - -## Common Use Cases - -- Add organization-specific logging toggles (privacy, PII redaction) -- Integrate with other plugins by exposing their settings under a new tab -- Provide presets for log points (e.g., only log slow queries) via a custom select field - ---- - -## Admin Page Details - -- Menu: Settings → WPGraphQL Logging (`admin.php?page=wpgraphql-logging`) -- Tabs: `admin.php?page=wpgraphql-logging&tab={tab_slug}` -- Sections and fields are rendered with `do_settings_sections('wpgraphql-logging-{tab_slug}')` diff --git a/plugins/wpgraphql-logging/docs/how-to/admin_add_fields.md b/plugins/wpgraphql-logging/docs/how-to/admin_add_fields.md new file mode 100644 index 00000000..bc98d7c6 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/admin_add_fields.md @@ -0,0 +1,66 @@ +## How to add a new field to an existing tab and query it + +This guide shows how to add custom fields to the WPGraphQL Logging settings and how to read or query those values. + +![WPGraphQL Logging Settings Page](../screenshots/admin_how_to_add_field.png) +*The WPGraphQL Logging settings page with Basic Configuration and Data Management tabs where custom fields can be added* + + +### Step 1 — Add a field via filter + +Add a field to an existing tab using the provided filters. Common tabs are `basic_configuration` and `data_management`. + +Example: add a checkbox to Basic Configuration + +```php +add_filter( 'wpgraphql_logging_basic_configuration_fields', function( $fields ) { + $fields['my_feature_enabled'] = new \WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField( + 'my_feature_enabled', + 'basic_configuration', + __( 'Enable My Feature', 'my-plugin' ) + ); + return $fields; +}); +``` + +Example: add a text input to Data Management + +```php +add_filter( 'wpgraphql_logging_data_management_fields', function( $fields ) { + $fields['my_data_region'] = new \WPGraphQL\Logging\Admin\Settings\Fields\Field\TextInputField( + 'my_data_region', + 'data_management', + __( 'Data Region', 'my-plugin' ), + '', + __( 'e.g., us-east-1', 'my-plugin' ), + __( 'us-east-1', 'my-plugin' ) + ); + return $fields; +}); +``` + +Notes: + +- Field classes available: `CheckboxField`, `TextInputField`, `SelectField`, `TextIntegerField`. +- The second argument is the tab key (use the tab’s `get_name()`), not the option key. + +### Step 2 — Where the value is stored + +Values are saved to the option key `wpgraphql_logging_settings` under the tab key and field id, for example: + +```php +$options = get_option( 'wpgraphql_logging_settings', [] ); +// Example structure +// [ +// 'basic_configuration' => [ 'my_feature_enabled' => true ], +// 'data_management' => [ 'my_data_region' => 'us-east-1' ], +// ] +``` + +### Step 3 — Read the value in PHP + +```php +$options = get_option( 'wpgraphql_logging_settings', [] ); +$is_enabled = ! empty( $options['basic_configuration']['my_feature_enabled'] ); +$my_data_region = $options['data_management']['my_data_region'] ?? ''; +``` diff --git a/plugins/wpgraphql-logging/docs/how-to/admin_add_new_tab.md b/plugins/wpgraphql-logging/docs/how-to/admin_add_new_tab.md new file mode 100644 index 00000000..f7ca49b9 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/admin_add_new_tab.md @@ -0,0 +1,112 @@ +## How to add a new Settings tab to WPGraphQL Logging + +This guide shows how to add your own settings tab and fields to the WPGraphQL Logging Admin using the `wpgraphql_logging_settings_field_collection_init` action. + + +![Add New Settings Tab](../screenshots/admin_how_to_add_tab.png) +*Example of a custom settings tab added to WPGraphQL Logging admin interface* + + + +### Step 1 — Create a Tab class implementing `SettingsTabInterface` + +Create a new class that implements the required static methods and returns the fields you want to render/save. + +```php +add_tab( new \MyPlugin\Admin\Settings\Fields\Tab\MyCustomTab() ); +}, 10, 1 ); +``` + +### Step 3 — Reading saved values + +Values are stored with the plugin’s option key (filterable via `wpgraphql_logging_settings_group_option_key`). A simple way to access them: + +```php +namespace MyPlugin\Admin\Settings\Fields\Tab\MyCustomTab; +namespace WPGraphQL\Logging\Admin\Settings\ConfigurationHelper; +// You could also do this +// $options = get_option( 'wpgraphql_logging_settings', [] ); +// $enabled = ! empty( $options['my_custom_tab']['enable_feature'] ); +// $api_url = $options['my_custom_tab']['api_endpoint'] ?? ''; + +// Example: Show an admin notice based on the setting +add_action( 'admin_notices', function() use ( $enabled, $apiUrl ) { + $helper = ConfigurationHelper::get_instance(); + $tab = 'my_custom_tab'; + $enabled = $helper->get_setting($tab, MyCustomTab::ENABLE_FEATURE, false); + $api_url = $helper->get_setting($tab), MyCustomTab::API_ENDPOINT, ''); + + + if ( $enabled && empty( $api_url ) ) { + echo '

'; + echo __( 'Custom feature is enabled but no API endpoint is configured.', 'my-plugin' ); + echo '

'; + } else { + echo '

'; + if ( $enabled ) { + echo sprintf( __( 'Custom feature is enabled. API URL: %s', 'my-plugin' ), esc_html( $api_url ) ); + } else { + echo __( 'Custom feature is disabled.', 'my-plugin' ); + } + echo '

'; + } +}); +``` diff --git a/plugins/wpgraphql-logging/docs/how-to/admin_add_view_column.md b/plugins/wpgraphql-logging/docs/how-to/admin_add_view_column.md new file mode 100644 index 00000000..a9223ec9 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/admin_add_view_column.md @@ -0,0 +1,63 @@ +## How to add a new column to the Logs admin grid + +This guide shows how to add a custom column to the Logs list table using the provided filters. We’ll add a Memory Peak Usage column sourced from the log entry’s `extra.memory_peak_usage`. + + +![Add New Column to Logs Table](../screenshots/admin_how_to_add_column_to_grid.png) +*Example of a custom Memory Peak Usage column added to the WPGraphQL Logging admin table* + + +### Hooks overview + +- `wpgraphql_logging_logs_table_column_headers`: modify the visible columns and sortable metadata +- `wpgraphql_logging_logs_table_column_value`: control how each column’s value is rendered + +Source: `src/Admin/View/List/ListTable.php` + +### Step 1 — Add the column header + +```php +add_filter( 'wpgraphql_logging_logs_table_column_headers', function( $headers ) { + if ( isset( $headers[0] ) && is_array( $headers[0] ) ) { + $headers[0]['peak_memory_usage'] = __( 'Memory Peak (MB)', 'my-plugin' ); + // Optionally make it sortable by a DB column + // $headers[2]['peak_memory_usage'] = [ 'memory_peak_usage', false ]; + return $headers; + } + + // Fallback when filter is called for get_columns() + $headers['peak_memory_usage'] = __( 'Memory Peak (MB)', 'my-plugin' ); + return $headers; +}, 10, 1 ); +``` + +### Step 2 — Provide the cell value + +Each row item is a `\WPGraphQL\Logging\Logger\Database\DatabaseEntity`. Memory data is stored in `$item->get_extra()['memory_peak_usage']`. + +```php +add_filter( 'wpgraphql_logging_logs_table_column_value', function( $value, $item, $column_name ) { + if ( 'peak_memory_usage' !== $column_name ) { + return $value; + } + + if ( ! $item instanceof \WPGraphQL\Logging\Logger\Database\DatabaseEntity ) { + return $value; + } + + $extra = $item->get_extra(); + $raw = $extra['memory_peak_usage'] ?? ''; + if ( '' === $raw ) { + return ''; + } + + // Normalize to MB if the stored value is in bytes + if ( is_numeric( $raw ) ) { + $mb = round( (float) $raw / 1048576, 2 ); + return sprintf( '%s MB', number_format_i18n( $mb, 2 ) ); + } + + // If already formatted (e.g., "24 MB"), just escape and return + return esc_html( (string) $raw ); +}, 10, 3 ); +``` diff --git a/plugins/wpgraphql-logging/docs/how-to/events_add_context.md b/plugins/wpgraphql-logging/docs/how-to/events_add_context.md new file mode 100644 index 00000000..11cfc6f3 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/events_add_context.md @@ -0,0 +1,79 @@ +## How to add context data to a logged WPGraphQL event + +This guide shows two supported ways to inject custom context data into events that WPGraphQL Logging records: + +- Programmatic transform API (recommended for plugins/themes using PHP namespaces) +- WordPress filter API (easy to drop into any project) + +Refer to the [Events Reference](../reference/events.md) for the list of available event names. + + +![Adding custom context data to WPGraphQL events](../screenshots/event_add_context_data.png) +*Example of custom context data being added to a WPGraphQL event log entry* + + +### Option A — Programmatic transform API + +Use the `WPGraphQL\Logging\Plugin::transform()` helper to mutate the event payload before it is logged and emitted. + +```php + $_SERVER['REMOTE_ADDR'] ?? null, + 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? null, + ]; + return $payload; +}, 10, 1 ); +``` diff --git a/plugins/wpgraphql-logging/docs/how-to/events_pub_sub.md b/plugins/wpgraphql-logging/docs/how-to/events_pub_sub.md new file mode 100644 index 00000000..83e6c4ed --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/events_pub_sub.md @@ -0,0 +1,130 @@ +## How to use the WPGraphQL Logging events pub/sub system + +The plugin exposes a lightweight pub/sub bus around key WPGraphQL lifecycle events and bridges them to standard WordPress actions/filters. You can: + +- Subscribe (read-only) to observe event payloads +- Transform payloads before core code logs and emits them +- Publish your own custom events for your app/plugins + +See the [Events Reference](../reference/events.md) for available built-in events and their mappings. + + +### Core concepts + +- Subscribe (read-only): `Plugin::on( $event, callable $listener, $priority )` +- Transform (mutate): `Plugin::transform( $event, callable $transform, $priority )` +- Emit (publish): `Plugin::emit( $event, array $payload )` + +Priorities run ascending (lower numbers first). Transforms must return the updated payload array; subscribers receive the payload and do not return. + + +### Programmatic API (recommended) + +```php + [ 'user_id' => (int) $user_id ], + ] ); +} ); +``` + +Notes: + +- Built-in events are transformed internally before they are logged and then published. +- `emit()` publishes to subscribers and the WordPress action bridge; it does not apply transforms by itself. + - If you want the “transform then publish” pattern for your custom event, call `EventManager::transform( $event, $payload )` yourself before publishing. + + +### WordPress bridge (actions and filters) + +For each event, the system also fires a WordPress action and applies a WordPress filter so you can interact without the PHP helpers. + +- Action: `wpgraphql_logging_event_{event_name}` (fires after subscribers run) +- Filter: `wpgraphql_logging_filter_{event_name}` (used when core transforms a payload) + +Examples: + +```php + $level, + 'level_name' => $level_name, + 'query' => $context['query'] ?? '', + 'context' => $context, + 'message' => 'Test' + ] ); +}, 10); + +``` + +>[!NOTE] +> You can also add a custom handler if you want to log data to that service via the LoggerService. + + + +### Troubleshooting + +- If your transform isn’t taking effect, ensure you’re targeting the correct event and that your callable returns the modified array. +- If you only call `emit()`, transforms won’t run automatically; they only run where core calls `transform()`. +- Use priorities to control ordering with other plugins (`5` runs before `10`). diff --git a/plugins/wpgraphql-logging/docs/how-to/logger_add_new_handler.md b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_handler.md new file mode 100644 index 00000000..c3e08a18 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_handler.md @@ -0,0 +1,89 @@ +## How to Add a New Handler (File Logging) + +This guide shows how to log to a file using a Monolog handler, either in addition to the default WordPress database handler or as a replacement. It also covers per-instance overrides. + +### What is a Handler? + +Handlers decide where logs are written. By default, WPGraphQL Logging uses a custom `WordPressDatabaseHandler` to store logs in the database. You can add more destinations (files, streams, third-party services) or replace the defaults. + +>[!NOTE] +> See for a list of handlers and processors + +### Option A: Add a file handler globally (in addition to the default) + +Use the `wpgraphql_logging_default_handlers` filter to push a `StreamHandler` that writes to a file. The default database handler will remain enabled. + +```php +info( 'Per-instance handlers configured' ); + +// Or replace defaults for the instance (file only) +$fileOnly = LoggerService::get_instance( 'file_only', [ + new StreamHandler( WP_CONTENT_DIR . '/logs/wpgraphql-file-only.log', Level::Warning ), +] ); +$fileOnly->warning( 'This goes only to the file' ); +``` + +### Tips + +- Ensure the logs directory is writable by the web server user. +- Consider `Monolog\\Handler\\RotatingFileHandler` to rotate files by day and limit disk usage. +- You can combine multiple handlers (e.g., database + file + Slack) either globally (filter) or per instance. + +### Related + +- See the [Logger reference](../reference/logging.md#filter-wpgraphql_logging_default_handlers) for `wpgraphql_logging_default_handlers` and other hooks. diff --git a/plugins/wpgraphql-logging/docs/how-to/logger_add_new_processor.md b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_processor.md new file mode 100644 index 00000000..9078bbae --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_processor.md @@ -0,0 +1,57 @@ +## How to Add a New Processor + +This guide shows how to create and register a custom processor that logs the current WordPress environment (e.g. `production`, `staging`, `development`). + +### What is a Processor? + +Processors in Monolog add or transform data on each log record before handlers write it. They can modify the `context` or `extra` arrays on a record. + +### Step 1: Create a processor class + +Create a PHP class that implements `Monolog\Processor\ProcessorInterface` and returns the updated `LogRecord`. + +```php +extra['environment'] = wp_get_environment_type(); + return $record; + } +} +``` + +### Step 2: Register the processor globally + +Use the `wpgraphql_logging_default_processors` filter to add your processor to all logger instances. + +```php +info( 'Environment test' ); +``` + + +You should see `environment` in the log record's `extra` data (e.g. in the Logs admin UI or your chosen handler output). + +### Related + +- See the [Logger reference](../reference/logging.md#filter-wpgraphql_logging_default_processors) for hooks you can use to customize processors: `wpgraphql_logging_default_processors`. diff --git a/plugins/wpgraphql-logging/docs/how-to/logger_add_new_rule.md b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_rule.md new file mode 100644 index 00000000..a21588be --- /dev/null +++ b/plugins/wpgraphql-logging/docs/how-to/logger_add_new_rule.md @@ -0,0 +1,60 @@ +## How to Add a New Rule (Query must contain string) + +This guide shows how to create a custom logging rule that only passes when the GraphQL query contains a specific substring, and how to register it with the RuleManager. + +### What is a Rule? + +Rules implement `WPGraphQL\Logging\Logger\Rules\LoggingRuleInterface` and are evaluated by the `RuleManager`. All rules must pass for logging to proceed. + +Interface reference (methods): +- `passes( array $config, ?string $query_string ): bool` +- `get_name(): string` + +### Step 1: Create the rule class + +Create a class that implements the interface and returns true only if the query contains a given substring. + +```php + fail + } + return stripos( $query_string, $this->needle ) !== false; + } + + public function get_name(): string { + // Ensure unique name per rule; adjust if you need multiple variants + return 'contains_string_rule'; + } +} +``` + +### Step 2: Register the rule with the RuleManager + +Use the `wpgraphql_logging_rule_manager` filter to add your rule. This runs when the logger helper initializes rules. + +```php +add_rule( new \MyPlugin\Logging\Rules\ContainsStringRule( 'GetPost' ) ); + return $rule_manager; +}); +``` + +### Step 3: Verify + +- GraphQL requests whose query string contains `GetPost` will be logged (assuming other rules also pass). +- Requests without that substring will be skipped by this rule, causing `is_enabled` to be false. + +### Related + +- See the [Logger reference](../reference/logging.md#filter-wpgraphql_logging_rule_manager) for the `wpgraphql_logging_rule_manager` filter. diff --git a/plugins/wpgraphql-logging/docs/index.md b/plugins/wpgraphql-logging/docs/index.md new file mode 100644 index 00000000..9ac08e22 --- /dev/null +++ b/plugins/wpgraphql-logging/docs/index.md @@ -0,0 +1,202 @@ +# WPGraphQL Logging + +## Table of Contents + +- [Project Structure](#project-structure) +- [Key Features](#key-features) +- [Setup](#setup) +- [Basic Configuration](#basic-configuration) +- [Viewing Logs](#viewing-logs) +- [Uninstallation and Data Cleanup](#uninstallation-and-data-cleanup) +- [How-to Guides](#how-to-guides) +- [Reference](#reference) + +--- + + +## Project Structure + +```text +wpgraphql-logging/ +├── docs/ # Docs for extending the plugin. Contains developer docs. +├── src/ # Main plugin source code +│ ├── Admin/ # Admin settings, menu, and settings page logic +│ ├── Settings/ # Admin settings functionality for displaying and saving data. +│ ├── Events/ # Event logging, pub/sub event manager for extending the logging. +│ ├── Logger/ # Logger service, Monolog handlers & processors +│ ├── Database/ # Database entity and helper +│ ├── Handlers/ # Monolog WordPress database handler for logging data +│ ├── Processors/ # Monolog processors for data sanitization and request headers +│ ├── Rules/ # Rules and RuleManager to decide whether to log a query +│ ├── Scheduler/ # Automated data cleanup and maintenance tasks +│ ├── Plugin.php # Main plugin class (entry point) +│ └── Autoloader.php # PSR-4 autoloader +├── tests/ # All test suites +│ ├── wpunit/ # WPBrowser/Codeception unit tests +├── [wpgraphql-logging.php] +├── [activation.php] +├── [composer.json] +├── [deactivation.php] +├── [TESTING.md] +├── [README.md] +``` + +--- + +## Key Features + +- **End-to-end GraphQL lifecycle logging** + - **Pre Request** (`do_graphql_request`): captures `query`, `variables`, `operation_name`. + - **Before Execution** (`graphql_before_execute`): snapshots request `params`. + - **Before Response Returned** (`graphql_return_response`): inspects `response`; auto-elevates level to Error when GraphQL `errors` are present (adds `errors` to context). + +- **Developer-friendly pub/sub and transform system** + - Programmatic API: `Plugin::on($event, $listener)`, `Plugin::transform($event, $callable)`, `Plugin::emit($event, $payload)`. + - Prioritized execution: lower priority runs earlier for both subscribers and transforms. + - WordPress bridges: actions `wpgraphql_logging_event_{event}` and filters `wpgraphql_logging_filter_{event}` to integrate with standard hooks. + - Safe-by-default: exceptions in listeners/transforms are caught and logged; they do not break the pipeline. + - See: Reference › Events (`docs/reference/events.md`) and How‑to guides (`docs/how-to/events_pub_sub.md`, `docs/how-to/events_add_context.md`). + +- **Extensible Monolog pipeline** + - Default handler: `WordPressDatabaseHandler` stores logs in `{$wpdb->prefix}wpgraphql_logging`. + - Add handlers via filter `wpgraphql_logging_default_handlers` (e.g., file, Slack, HTTP, etc.). + - Add processors via filter `wpgraphql_logging_default_processors` (e.g., enrich records with user/site data). + - Customize `default_context` via `wpgraphql_logging_default_context`. + - Use `LoggerService::get_instance()` to build custom channels, handlers, processors. + +- **Configurable rule-based logging** + - Built-in rules: enabled toggle, IP restrictions, exclude queries, sampling rate, null query guard, response logging toggle. + - All rules are orchestrated by a `RuleManager` ensuring logs only emit when all rules pass. + - Extend rules: hook `wpgraphql_logging_rule_manager` to add custom `LoggingRuleInterface` implementations. + +- **Automated data management** + - **Daily cleanup scheduler**: removes old logs based on retention. + - **Configurable retention period**: choose days to keep (default 30). + - **Manual cleanup**: trigger from the admin UI. + - **Data sanitization**: built-in `DataSanitizationProcessor` removes/anonymizes/truncates sensitive fields with recommended or custom rules. + +- **Admin UI for operations** + - Logs list view with filters (level, date range) and CSV export. + - Bulk delete actions and visibility controls. + +- **Composable and testable architecture** + - Clear separation: Events bus, Logger service, Rules, Processors, Handlers. + - Designed for extension via interfaces, filters, and helper APIs. + +--- + +## Setup + +Once the plugin is activated, you can activate and configure the plugin under Settings -> WPGraphQL Logging + +### Basic Configuration + +![Basic Configuration](screenshots/admin_configuration_basic.png) + +- **Enabled**: The master switch to turn logging on or off. +- **IP Restrictions**: A comma-separated list of IPv4/IPv6 addresses. When set, only requests originating from these IPs will be logged. This is particularly useful for developers who wish to log only their own queries. +- **Exclude Queries**: A comma-separated list of GraphQL query or mutation names to be excluded from logging. This helps reduce noise by ignoring frequent or uninteresting operations. +- **Data Sampling Rate**: A dropdown to select the percentage of requests that will be logged. This is useful for managing log volume on high-traffic sites by only capturing a sample of the total requests. +- **Log Points**: A multi-select field to choose the specific WPGraphQL lifecycle events for which data should be logged. +- **Log Response**: A toggle to determine whether the GraphQL response body should be included in the log. Disabling this can reduce the size of your log data. + +>[!NOTE] +> Logging enablement is determined by a set of rules managed by a `RuleManager`. All rules must pass to log a request. See the Logger reference for the RuleManager hook: [wpgraphql_logging_rule_manager](reference/logging.md#trait-loggerlogginghelper). + + + +### Data Management + +![Data Management](screenshots/admin_configuration_data_management.png) + +- **Enable Data Deletion**: A toggle to enable a daily WP-Cron job that automatically deletes old log entries based on the retention period. +- **Log Retention Period**: Specify the number of days to keep log data before it is automatically deleted. +- **Enable Data Sanitization**: The master switch to turn data sanitization on or off. When enabled, sensitive data is cleaned from logs before being stored. +- **Data Sanitization Method**: Choose between two sanitization methods: + - **Recommended Rules (Default)**: Uses pre-configured rules to automatically sanitize common sensitive fields in WordPress and WPGraphQL. The following fields are sanitized: + - `request.app_context.viewer.data` (User data object) + - `request.app_context.viewer.allcaps` (User capabilities) + - `request.app_context.viewer.cap_key` (Capability keys) + - `request.app_context.viewer.caps` (User capability array) + - **Custom Rules**: Provides granular control over sanitization with the following options: + - **Fields to Remove**: A comma-separated list of field paths (e.g., `request.app_context.viewer.data`) to completely remove from the log. + - **Fields to Anonymize**: A comma-separated list of field paths whose values will be replaced with `***`. + - **Fields to Truncate**: A comma-separated list of field paths whose string values will be truncated to 50 characters. + + +## Viewing Logs + +Once configured to log data you can find logs under "GraphQL Logs" in the WordPress Menu. + +![Admin View](screenshots/admin_view.png) + +This extends the WordPress `WP_List_Table` class but you can do the following. + +### Download the log + +You can download the log as CSV format e.g. + +```csv +ID,Date,Level,"Level Name",Message,Channel,Query,Context,Extra +5293,"2025-10-06 15:41:34",200,INFO,"WPGraphQL Response",wpgraphql_logging,"{ posts(first: 10) ...""memory_peak_usage"":""18 MB""}" +``` + + +### Filtering Logs + +You can filter the log by + +1. Level +2. Start Date +3. End Date + +![Admin View with Filters](screenshots/admin_view_filters.png) + +>[!NOTE] +> The default UI highlights Info and Error levels. To customize visible columns and sorting, filter `wpgraphql_logging_logs_table_column_headers` (see Admin reference). + + +### Bulk Actions + +Currently you can delete selected or all logs. + + +## Uninstallation and Data Cleanup + +By default, WPGraphQL Logging preserves all logged data when the plugin is deactivated to prevent accidental data loss. If you want to completely remove all plugin data (including database tables) when deactivating the plugin, you must explicitly enable this behavior. + +### Enabling Database Cleanup on Deactivation + +To enable automatic database cleanup when the plugin is deactivated, add the following constant to your `wp-config.php` file or in a must-use plugin: + +```php +define( 'WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN', true ); +``` + +> [!WARNING] +> **Data Loss Warning**: When `WP_GRAPHQL_LOGGING_UNINSTALL_PLUGIN` is defined as `true`, deactivating the plugin will permanently delete all logged data and drop the plugin's database tables. This action is irreversible. + + +## How to Guides + +### Admin +- [How to add a new Settings tab to WPGraphQL Logging](how-to/admin_add_new_tab.md) +- [How to add a new field to an existing tab and query it](how-to/admin_add_fields.md) +- [How to add a new column to the Logs admin grid](how-to/admin_add_view_column.md) + +### Events +- [How to add context data to a logged WPGraphQL event](how-to/events_add_context.md) +- [How to use the WPGraphQL Logging events pub/sub system](how-to/events_pub_sub.md) + +### Logging + +- [How to Add a New Handler (File Logging)](how-to/logger_add_new_handler.md) +- [How to Add a New Processor](how-to/logger_add_new_processor.md) +- [How to Add a New Rule (Query must contain string)](how-to/logger_add_new_rule.md) + + +## Reference + +- Admin: [Actions/Filters](reference/admin.md) +- Events: [Actions/Filters](reference/events.md) +- Logging: [Actions/Filters](reference/logging.md) diff --git a/plugins/wpgraphql-logging/docs/reference/admin.md b/plugins/wpgraphql-logging/docs/reference/admin.md new file mode 100644 index 00000000..35cf246b --- /dev/null +++ b/plugins/wpgraphql-logging/docs/reference/admin.md @@ -0,0 +1,420 @@ +## Admin Reference + +The WPGraphQL Logging plugin provides several filters and actions in the Admin area that allow developers to extend and customize functionality. This reference documents all available hooks with real-world examples. + +## Table of Contents + +- [SettingsPage](#class-settingspage) +- [Settings\ConfigurationHelper](#class-settingsconfigurationhelper) +- [Settings\SettingsFormManager](#class-settingssettingsformmanager) +- [ViewLogsPage](#class-viewlogspage) +- [Settings\Templates\admin.php and View Templates](#class-settingstemplatesadminphp-and-view-templates) + + +--- + + +### Class: `SettingsPage` +Source: + +#### Action: `wpgraphql_logging_settings_init` +Fires once the Settings Page singleton is initialized. + +Parameters: +- `$instance` (SettingsPage) Settings page instance + +Example: +```php +add_action( 'wpgraphql_logging_settings_init', function( $settings_page ) { + add_action( 'admin_notices', function() { + echo '

Custom notice.

+ }); +}, 10, 1 ); +``` + +#### Action: `wpgraphql_logging_admin_enqueue_scripts` +Fires when scripts/styles are enqueued for the Settings page. + +Parameters: +- `$hook_suffix` (string) Current admin page hook + +Example: +```php +add_action( 'wpgraphql_logging_admin_enqueue_scripts', function( $hook_suffix ) { + wp_enqueue_style( 'my-logging-admin', plugins_url( 'assets/css/admin.css', __FILE__ ), [], '1.0.0' ); +}, 10, 1 ); +``` + +#### Filter: `wpgraphql_logging_admin_template_path` +Filters the admin template path used to render the Settings page. + +Parameters: +- `$template_path` (string) Default template path + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_admin_template_path', function( $template_path ) { + return plugin_dir_path( __FILE__ ) . 'templates/custom-admin.php'; +}); +``` + +--- + +### Class: `Settings\Fields\SettingsFieldCollection` +Source: + +#### Action: `wpgraphql_logging_settings_field_collection_init` +Allows developers to register additional settings tabs/fields. + +Parameters: +- `$collection` (SettingsFieldCollection) The collection instance + +Example: +```php +add_action( 'wpgraphql_logging_settings_field_collection_init', function( $collection ) { + $collection->add_tab( new \MyPlugin\Admin\Settings\Fields\Tab\MyCustomTab() ); +}, 10, 1 ); +``` + +>[NOTE] +> See our how to guide [How to add a new Settings tab to WPGraphQL Logging](../how-to/admin_add_new_tab.md) + + +--- + +### Class: `Settings\Tab\BasicConfigurationTab` +Source: + +#### Filter: `wpgraphql_logging_basic_configuration_fields` +Filters the field definitions for the Basic Configuration tab. + +Parameters: +- `$fields` (array) Map of field id => field object + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_basic_configuration_fields', function( $fields ) { + $fields['my_setting'] = new \WPGraphQL\Logging\Admin\Settings\Fields\Field\CheckboxField( + 'my_setting', + 'basic_configuration', + __( 'My Setting', 'my-plugin' ) + ); + return $fields; +}); +``` + +--- + +### Class: `Settings\Tab\DataManagementTab` +Source: + +#### Filter: `wpgraphql_logging_data_management_fields` +Filters the field definitions for the Data Management tab. + +Parameters: +- `$fields` (array) Map of field id => field object + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_data_management_fields', function( $fields ) { + $fields['my_purge_days'] = new \WPGraphQL\Logging\Admin\Settings\Fields\Field\TextIntegerField( + 'my_purge_days', + 'data_management', + __( 'Purge After (days)', 'my-plugin' ) + ); + return $fields; +}); +``` + +--- + +### Class: `Settings\ConfigurationHelper` +Source: + +#### Filter: `wpgraphql_logging_settings_group_option_key` +Filters the option key used to store settings. + +Parameters: +- `$option_key` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_settings_group_option_key', function( $option_key ) { + return $option_key . '_' . wp_get_environment_type(); +}); +``` + +#### Filter: `wpgraphql_logging_settings_group_settings_group` +Filters the settings group name. + +Parameters: +- `$settings_group` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_settings_group_settings_group', function( $group ) { + return is_multisite() ? 'network_' . $group : $group; +}); +``` + +--- + +### Class: `Settings\SettingsFormManager` +Source: + +#### Action: `wpgraphql_logging_settings_form_manager_init` +Fires when the settings form manager is initialized. + +Parameters: +- `$instance` (SettingsFormManager) + +Example: +```php +add_action( 'wpgraphql_logging_settings_form_manager_init', function( $manager ) { + // Place for validation/transform hooks tied to registration lifecycle +}, 10, 1 ); +``` + +--- + +### Class: `ViewLogsPage` +Source: + +#### Action: `wpgraphql_logging_view_logs_init` +Fires once the View Logs page singleton is initialized. + +Parameters: +- `$instance` (ViewLogsPage) + +Example: +```php +add_action( 'wpgraphql_logging_view_logs_init', function( $view_logs_page ) { + // e.g. register custom columns or UI +}, 10, 1 ); +``` + +#### Action: `wpgraphql_logging_view_logs_admin_enqueue_scripts` +Fires when scripts/styles are enqueued for the View Logs page. + +Parameters: +- `$hook_suffix` (string) + +Example: +```php +add_action( 'wpgraphql_logging_view_logs_admin_enqueue_scripts', function( $hook_suffix ) { + wp_enqueue_script( 'my-view-logs', plugins_url( 'assets/js/view-logs.js', __FILE__ ), [ 'jquery' ], '1.0.0', true ); +}, 10, 1 ); +``` + +#### Filter: `wpgraphql_logging_filter_redirect_url` +Filters the redirect URL after submitting filters. + +Parameters: +- `$redirect_url` (string) +- `$filters` (array) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_filter_redirect_url', function( $redirect_url, $filters ) { + return add_query_arg( 'my_flag', '1', $redirect_url ); +}, 10, 2 ); +``` + +#### Filter: `wpgraphql_logging_list_template` +Filters the template path for the logs list. + +Parameters: +- `$template_path` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_list_template', function( $template_path ) { + return plugin_dir_path( __FILE__ ) . 'templates/custom-list.php'; +}); +``` + +#### Filter: `wpgraphql_logging_view_template` +Filters the template path for the single log view. + +Parameters: +- `$template_path` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_view_template', function( $template_path ) { + return plugin_dir_path( __FILE__ ) . 'templates/custom-view.php'; +}); +``` + +--- + +### Class: `View\List\ListTable` +Source: + +#### Filter: `wpgraphql_logging_logs_table_column_headers` +Filters the table columns and sorting metadata. + +Parameters: +- `$column_headers` (array) [ columns, hidden, sortable, primary ] + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_logs_table_column_headers', function( $headers ) { + $headers[0]['app_name'] = __( 'App', 'my-plugin' ); + return $headers; +}); +``` + +#### Filter: `wpgraphql_logging_logs_table_query_args` +Filters the repository query args used to fetch logs. + +Parameters: +- `$args` (array) + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_logs_table_query_args', function( $args ) { + $args['where'][] = "JSON_EXTRACT(context, '$.app_id') IS NOT NULL"; + return $args; +}); +``` + +#### Filter: `wpgraphql_logging_logs_table_column_value` +Filters the rendered value for each column. + +Parameters: +- `$value` (mixed) +- `$item` (\WPGraphQL\Logging\Logger\Database\DatabaseEntity) +- `$column_name` (string) + +Returns: mixed + +Example: +```php +add_filter( 'wpgraphql_logging_logs_table_column_value', function( $value, $item, $column ) { + if ( 'message' === $column ) { + return wp_trim_words( (string) $value, 20 ); + } + return $value; +}, 10, 3 ); +``` + +#### Filter: `wpgraphql_logging_logs_table_where_clauses` +Filters the computed WHERE clauses before querying. + +Parameters: +- `$where_clauses` (array) +- `$request` (array) + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_logs_table_where_clauses', function( $where, $request ) { + if ( ! empty( $request['status_code'] ) ) { + $code = absint( $request['status_code'] ); + $where[] = "JSON_EXTRACT(context, '$.status_code') = {$code}"; + } + return $where; +}, 10, 2 ); +``` + +#### Filter: `wpgraphql_logging_filters_template` +Filters the template path for the filters UI. + +Parameters: +- `$template_path` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_filters_template', function( $template_path ) { + return plugin_dir_path( __FILE__ ) . 'templates/custom-filters.php'; +}); +``` + +--- + +### Class: `View\Download\DownloadLogService` +Source: + +#### Filter: `wpgraphql_logging_csv_filename` +Filters the CSV filename used for a single log export. + +Parameters: +- `$filename` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_csv_filename', function( $filename ) { + return 'myapp_' . gmdate( 'Ymd_His' ) . '.csv'; +}); +``` + +#### Filter: `wpgraphql_logging_csv_headers` +Filters the CSV column headers. + +Parameters: +- `$headers` (array) +- `$log_id` (int) +- `$log` (\WPGraphQL\Logging\Logger\Database\DatabaseEntity) + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_csv_headers', function( $headers ) { + return array_merge( $headers, [ 'Environment', 'Endpoint' ] ); +}, 10, 3 ); +``` + +#### Filter: `wpgraphql_logging_csv_content` +Filters the CSV row values. + +Parameters: +- `$content` (array) +- `$log_id` (int) +- `$log` (\WPGraphQL\Logging\Logger\Database\DatabaseEntity) + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_csv_content', function( $content, $log_id, $log ) { + $context = $log->get_context(); + return array_merge( $content, [ + $context['environment'] ?? 'prod', + $context['headless_endpoint'] ?? '', + ] ); +}, 10, 3 ); +``` + +--- + +### Class: `Settings\Templates\admin.php` and View Templates +Source: `src/Admin/Settings/Templates/admin.php`, `src/Admin/View/Templates/*.php` + +These templates are referenced by the template path filters above and do not define hooks themselves. diff --git a/plugins/wpgraphql-logging/docs/reference/events.md b/plugins/wpgraphql-logging/docs/reference/events.md new file mode 100644 index 00000000..068013bc --- /dev/null +++ b/plugins/wpgraphql-logging/docs/reference/events.md @@ -0,0 +1,142 @@ +## Events Reference + +The WPGraphQL Logging plugin exposes a lightweight pub/sub system for WPGraphQL lifecycle events and bridges them to standard WordPress actions/filters. + +## Table of Contents + +- [Events\Events](#class-eventsevents) +- [Events\EventManager](#class-eventseventmanager) + + +--- + +### Class: `Events\Events` +Source: + +Constants that map to WPGraphQL core hooks: + +- `Events::PRE_REQUEST` → `do_graphql_request` +- `Events::BEFORE_GRAPHQL_EXECUTION` → `graphql_before_execute` +- `Events::BEFORE_RESPONSE_RETURNED` → `graphql_return_response` +- `Events::REQUEST_DATA` → `graphql_request_data` (filter) +- `Events::RESPONSE_HEADERS_TO_SEND` → `graphql_response_headers_to_send` (filter) +- `Events::REQUEST_RESULTS` → `graphql_request_results` (filter) + +Use these with the `Plugin` helpers or the `EventManager` directly. + + +--- + +### Class: `Events\EventManager` +Source: + +#### Action: `wpgraphql_logging_event_{event_name}` +Bridged WordPress action fired whenever an internal event is published. (and data logged) + +Parameters: +- `$payload` (array) Published payload, typically includes `context` and sometimes `level` + +Example: +```php +add_action( 'wpgraphql_logging_event_do_graphql_request', function( array $payload ) { + // Do something with the payload. +}, 10, 1 ); +``` + +#### Filter: `wpgraphql_logging_filter_{event_name}` +Bridged WordPress filter applied whenever an internal event payload is transformed and allow you to log data. + +Parameters: +- `$payload` (array) Mutable payload; return the updated array + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_filter_graphql_return_response', function( array $payload ) { + $payload['context']['wpgraphql-content-blocks'] = ['no_of_blocks' => 100]; + return $payload; +}, 10, 1 ); +``` + +Programmatic API: + +- Subscribe: +```php +use WPGraphQL\Logging\Plugin; +use WPGraphQL\Logging\Events\Events; + +Plugin::on( Events::PRE_REQUEST, function( array $payload ): void { + // ... +}, 5 ); +``` + +- Transform: +```php +use WPGraphQL\Logging\Plugin; +use WPGraphQL\Logging\Events\Events; +use Monolog\Level; + +Plugin::transform( Events::BEFORE_RESPONSE_RETURNED, function( array $payload ): array { + $payload['context']['custom_key'] = 'custom_value'; + $payload['level'] = Level::Debug; + return $payload; +}, 10 ); +``` + + +--- + +### Class: `Events\QueryActionLogger` +Source: + +Hooks into WPGraphQL actions and publishes/records events via the logger service. + +#### Action: `do_graphql_request` (mapped from `Events::PRE_REQUEST`) +Logged as “WPGraphQL Pre Request”. + +Parameters: +- `$query` (string|null) +- `$operation_name` (string|null) +- `$variables` (array|null) + +Example: +```php +add_action( 'init', function() { + \WPGraphQL\Logging\Plugin::on( \WPGraphQL\Logging\Events\Events::PRE_REQUEST, function( array $payload ): void { + $ctx = $payload['context'] ?? []; + // ... + } ); +} ); +``` + +--- + + +### Quick Start + +```php +use WPGraphQL\Logging\Plugin; +use WPGraphQL\Logging\Events\Events; + +// Subscribe (read-only) +Plugin::on( Events::PRE_REQUEST, function( array $payload ): void { + // Inspect $payload['context'] +}, 10 ); + +// Transform (mutate payload before it is logged/emitted) +Plugin::transform( Events::PRE_REQUEST, function( array $payload ): array { + $payload['context']['env'] = wp_get_environment_type(); + return $payload; +}, 10 ); +``` + +--- + +### Further Reading + +- [WPGraphQL Documentation](https://www.wpgraphql.com/docs/) +- [WPGraphQL Actions and Filters](https://www.wpgraphql.com/docs/actions-and-filters/) +- [Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern) +- [Publish-Subscribe Pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) +- [WordPress Plugin API (Actions & Filters)](https://developer.wordpress.org/plugins/hooks/) diff --git a/plugins/wpgraphql-logging/docs/reference/logging.md b/plugins/wpgraphql-logging/docs/reference/logging.md new file mode 100644 index 00000000..b589073b --- /dev/null +++ b/plugins/wpgraphql-logging/docs/reference/logging.md @@ -0,0 +1,314 @@ +## Logger Reference + +The WPGraphQL Logging subsystem is built on [Monolog](https://github.com/Seldaek/monolog). This reference documents the Logger classes under `src/Logger` and all available WordPress actions/filters for extending behavior. + +## Table of Contents + +- [Logger\LoggerService](#class-loggerloggerservice) +- [Logger\LoggingHelper](#trait-loggerlogginghelper) +- [Logger\Handlers\WordPressDatabaseHandler](#class-loggerhandlerswordpressdatabasehandler) +- [Logger\Processors\RequestHeadersProcessor](#class-loggerprocessorsrequestheadersprocessor) +- [Logger\Processors\DataSanitizationProcessor](#class-loggerprocessorsdatasanitizationprocessor) +- [Logger\Database\DatabaseEntity](#class-loggerdatabasedatabaseentity) +- [Logger\Database\LogsRepository](#class-loggerdatabaselogsrepository) +- [Logger\Scheduler\DataDeletionScheduler](#class-loggerschedulerdatadeletionscheduler) +- [Quick Start](#quick-start) +- [Available Log Levels](#available-log-levels) + + +--- + +### Class: `Logger\LoggerService` +Source: + +Manages Monolog instances (per-channel singleton). Provides default handlers, processors, and context. + +#### Filter: `wpgraphql_logging_default_processors` +Filters the default processor list. + +Parameters: +- `$processors` (array) Current processors + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_default_processors', function( array $processors ) { + $processors[] = new \MyPlugin\Logging\UserContextProcessor(); + return $processors; +}); +``` + +#### Filter: `wpgraphql_logging_default_handlers` +Filters the default handler list. + +Parameters: +- `$handlers` (array) Current handlers + +Returns: array + +Example: +```php +use Monolog\Handler\RotatingFileHandler; +use Monolog\Level; + +add_filter( 'wpgraphql_logging_default_handlers', function( array $handlers ) { + $handlers[] = new RotatingFileHandler( WP_CONTENT_DIR . '/logs/wpgraphql.log', 7, Level::Info ); + return $handlers; +}); +``` + +#### Filter: `wpgraphql_logging_default_context` +Filters the default context merged into every record. + +Parameters: +- `$context` (array) Current default context + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_default_context', function( array $context ) { + $context['environment'] = wp_get_environment_type(); + $context['multisite'] = is_multisite(); + return $context; +}); +``` + + +--- + +### Trait: `Logger\LoggingHelper` +Source: + +Common helpers used by logger-aware classes; composes a `RuleManager` and evaluates whether logging is enabled and which events are selected. + +#### Filter: `wpgraphql_logging_rule_manager` +Allows custom rules to be added to the `RuleManager`. + +Parameters: +- `$rule_manager` (RuleManager) The rule manager instance + +Returns: RuleManager + +Example: +```php +add_filter( 'wpgraphql_logging_rule_manager', function( $rule_manager ) { + $rule_manager->add_rule( new \MyPlugin\Logging\Rules\BlockPrivateIPsRule() ); + return $rule_manager; +}); +``` + +#### Filter: `wpgraphql_logging_is_enabled` +Filters the final decision (true/false) for whether logging is enabled for the current request. + +Parameters: +- `$is_enabled` (bool) Computed result from rules +- `$config` (array) Current logging configuration + +Returns: bool + +Example: +```php +add_filter( 'wpgraphql_logging_is_enabled', function( bool $enabled, array $config ) { + if ( defined( 'WPGRAPHQL_LOGGING_FORCE_DISABLE' ) && WPGRAPHQL_LOGGING_FORCE_DISABLE ) { + return false; + } + return $enabled; +}, 10, 2 ); +``` + + +--- + +### Class: `Logger\Handlers\WordPressDatabaseHandler` +Source: + +Monolog handler that persists records to the WordPress database via `DatabaseEntity`. + +Hooks: None. + + +--- + +### Class: `Logger\Processors\RequestHeadersProcessor` +Source: + +Adds request headers to the record `extra` data. + +Hooks: None. + + +--- + +### Class: `Logger\Processors\DataSanitizationProcessor` +Source: + +Sanitizes sensitive fields in record `context` and `extra` based on settings. + +#### Filter: `wpgraphql_logging_data_sanitization_enabled` +Controls whether sanitization is active. + +Parameters: +- `$enabled` (bool) From settings + +Returns: bool + +Example: +```php +add_filter( 'wpgraphql_logging_data_sanitization_enabled', function( $enabled ) { + return $enabled && ! defined( 'WPGRAPHQL_LOGGING_TRUSTED_ENV' ); +}); +``` + +#### Filter: `wpgraphql_logging_data_sanitization_rules` +Filters the active rule map (field path => action). + +Parameters: +- `$rules` (array) Computed rules (recommended or custom) + +Returns: array + +Example: +```php +add_filter( 'wpgraphql_logging_data_sanitization_rules', function( array $rules ) { + $rules['request.params.password'] = 'remove'; + return $rules; +}); +``` + +#### Filter: `wpgraphql_logging_data_sanitization_recommended_rules` +Filters the built-in recommended rules prior to use. + +Parameters: +- `$rules` (array) + +Returns: array + +#### Filter: `wpgraphql_logging_data_sanitization_record` +Filters the final `LogRecord` after sanitization is applied. + +Parameters: +- `$record` (Monolog\LogRecord) + +Returns: Monolog\LogRecord + + +--- + +### Class: `Logger\Database\DatabaseEntity` +Source: + +Represents a single log entry and provides persistence helpers. + +#### Filter: `wpgraphql_logging_database_name` +Filters the database table name used for logs. + +Parameters: +- `$table_name` (string) + +Returns: string + +Example: +```php +add_filter( 'wpgraphql_logging_database_name', function( string $name ) { + return $name . '_tenant_' . get_current_blog_id(); +}); +``` + + +--- + +### Class: `Logger\Database\LogsRepository` +Source: + +Query and mutation helpers for log entries. + +Hooks: None. + + +--- + +### Class: `Logger\Scheduler\DataDeletionScheduler` +Source: + +Schedules and performs periodic deletion of old logs according to retention settings. + +#### Action: `wpgraphql_logging_deletion_cleanup` +Cron hook fired to perform deletion. You can also trigger it manually with `do_action` or WP-CLI cron. + +Parameters: None + +Example: +```php +// Manually trigger cleanup (e.g., in a maintenance task) +do_action( 'wpgraphql_logging_deletion_cleanup' ); +``` + +#### Action: `wpgraphql_logging_cleanup_error` +Fired when an exception occurs during cleanup. + +Parameters: +- `$payload` (array) Includes: `error_message`, `retention_days`, `timestamp` + +Example: +```php +add_action( 'wpgraphql_logging_cleanup_error', function( array $payload ) { + error_log( '[WPGraphQL Logging] Cleanup error: ' . $payload['error_message'] ); +}, 10, 1 ); +``` + + +--- + +### Quick Start + +```php +use WPGraphQL\Logging\Logger\LoggerService; +use Monolog\Level; + +// Default logger +$logger = LoggerService::get_instance(); + +// Context is merged with defaults (WP version, plugin version, etc.) +$logger->info( 'User performed action', [ 'user_id' => 123 ] ); + +// Custom channel with extra handlers/processors +$logger = LoggerService::get_instance( + 'my_channel', + null, // use default handlers via filter + null, // use default processors via filter + [ 'component' => 'catalog' ] +); + +// Generic form +$logger->log( Level::Debug, 'Debug details', [ 'trace_id' => 'abc123' ] ); +``` + + +--- + +### Available Log Levels + +WPGraphQL Logging supports standard PSR-3/Monolog levels: + +| Level | Method | +| --- | --- | +| `EMERGENCY` | `$logger->emergency()` | +| `ALERT` | `$logger->alert()` | +| `CRITICAL` | `$logger->critical()` | +| `ERROR` | `$logger->error()` | +| `WARNING` | `$logger->warning()` | +| `NOTICE` | `$logger->notice()` | +| `INFO` | `$logger->info()` | +| `DEBUG` | `$logger->debug()` | + +You can also call `$logger->log(\Monolog\Level::Info, 'message', $context)`. + +--- + +Further reading: + +- [Monolog Documentation](https://github.com/Seldaek/monolog/blob/main/doc/01-usage.md) +- [PSR-3 Logger Interface](https://www.php-fig.org/psr/psr-3/) +- [WordPress Plugin API (Hooks)](https://developer.wordpress.org/plugins/hooks/) diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_basic.png b/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_basic.png new file mode 100644 index 00000000..2f459e6d Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_basic.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_data_management.png b/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_data_management.png new file mode 100644 index 00000000..7c002358 Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_configuration_data_management.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_column_to_grid.png b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_column_to_grid.png new file mode 100644 index 00000000..80cca9fe Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_column_to_grid.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_field.png b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_field.png new file mode 100644 index 00000000..e4c60b97 Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_field.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_tab.png b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_tab.png new file mode 100644 index 00000000..3bbc4b9e Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_how_to_add_tab.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_view.png b/plugins/wpgraphql-logging/docs/screenshots/admin_view.png new file mode 100644 index 00000000..14cdef89 Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_view.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/admin_view_filters.png b/plugins/wpgraphql-logging/docs/screenshots/admin_view_filters.png new file mode 100644 index 00000000..c507536a Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/admin_view_filters.png differ diff --git a/plugins/wpgraphql-logging/docs/screenshots/event_add_context_data.png b/plugins/wpgraphql-logging/docs/screenshots/event_add_context_data.png new file mode 100644 index 00000000..e60aecf8 Binary files /dev/null and b/plugins/wpgraphql-logging/docs/screenshots/event_add_context_data.png differ diff --git a/plugins/wpgraphql-logging/examples/.gitkeep b/plugins/wpgraphql-logging/examples/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php index e04f8720..47b6a823 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Fields/Tab/BasicConfigurationTab.php @@ -38,13 +38,6 @@ class BasicConfigurationTab implements SettingsTabInterface { */ public const DATA_SAMPLING = 'data_sampling'; - /** - * The field ID for the user-based logging select. - * - * @var string - */ - public const ADMIN_USER_LOGGING = 'admin_user_logging'; - /** * The field ID for the log point selection select. * @@ -100,14 +93,6 @@ public function get_fields(): array { __( 'e.g., __schema,GetSeedNode', 'wpgraphql-logging' ) ); - $fields[ self::ADMIN_USER_LOGGING ] = new CheckboxField( - self::ADMIN_USER_LOGGING, - self::get_name(), - __( 'Admin User Logging', 'wpgraphql-logging' ), - '', - __( 'Log only for admin users.', 'wpgraphql-logging' ) - ); - $fields[ self::DATA_SAMPLING ] = new SelectField( self::DATA_SAMPLING, self::get_name(), diff --git a/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php b/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php index 26428650..2abd12f3 100644 --- a/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php +++ b/plugins/wpgraphql-logging/src/Admin/Settings/Templates/admin.php @@ -69,7 +69,6 @@
  • -
  • diff --git a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php index ca67da78..2db28255 100644 --- a/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php +++ b/plugins/wpgraphql-logging/src/Logger/LoggingHelper.php @@ -5,7 +5,6 @@ namespace WPGraphQL\Logging\Logger; use WPGraphQL\Logging\Admin\Settings\Fields\Tab\BasicConfigurationTab; -use WPGraphQL\Logging\Logger\Rules\AdminUserRule; use WPGraphQL\Logging\Logger\Rules\EnabledRule; use WPGraphQL\Logging\Logger\Rules\ExcludeQueryRule; use WPGraphQL\Logging\Logger\Rules\IpRestrictionsRule; @@ -82,7 +81,6 @@ protected function get_rule_manager(): RuleManager { $this->rule_manager->add_rule( new QueryNullRule() ); $this->rule_manager->add_rule( new SamplingRateRule() ); $this->rule_manager->add_rule( new EnabledRule() ); - $this->rule_manager->add_rule( new AdminUserRule() ); $this->rule_manager->add_rule( new IpRestrictionsRule() ); $this->rule_manager->add_rule( new ExcludeQueryRule() ); apply_filters( 'wpgraphql_logging_rule_manager', $this->rule_manager ); diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php deleted file mode 100644 index dd346efa..00000000 --- a/plugins/wpgraphql-logging/src/Logger/Rules/AdminUserRule.php +++ /dev/null @@ -1,41 +0,0 @@ - $config The logging configuration. - * @param string|null $query_string The GraphQL query string. - * - * @return bool True if the rule passes (logging should continue). - */ - public function passes(array $config, ?string $query_string = null): bool { - - $is_admin_user = (bool) ( $config[ BasicConfigurationTab::ADMIN_USER_LOGGING ] ?? false ); - if ( ! $is_admin_user ) { - return true; - } - - return current_user_can( 'manage_options' ); - } - - /** - * Get the rule name for debugging. - */ - public function get_name(): string { - return 'admin_user_rule'; - } -} diff --git a/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php index 91c1954d..72a9ddb3 100644 --- a/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php +++ b/plugins/wpgraphql-logging/src/Logger/Rules/SamplingRateRule.php @@ -37,6 +37,6 @@ public function passes(array $config, ?string $query_string = null): bool { * Get the rule name for debugging. */ public function get_name(): string { - return 'enabled_rule'; + return 'sampling_rate_rule'; } } diff --git a/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/AdminUserRuleTest.php b/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/AdminUserRuleTest.php deleted file mode 100644 index b406d646..00000000 --- a/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/AdminUserRuleTest.php +++ /dev/null @@ -1,97 +0,0 @@ -rule = new AdminUserRule(); - } - - public function set_admin_user() { - $user_id = $this->factory()->user->create(['role' => 'administrator']); - wp_set_current_user($user_id); - } - - public function test_get_name_returns_correct_name(): void { - $this->assertEquals('admin_user_rule', $this->rule->get_name()); - } - - public function test_passes_when_admin_user_logging_disabled(): void { - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => false, - ]; - - $this->assertTrue($this->rule->passes($config)); - } - - public function test_passes_when_admin_user_logging_config_missing(): void { - $config = []; - - $this->assertTrue($this->rule->passes($config)); - } - - public function test_passes_when_admin_user_logging_enabled_and_user_can_manage_options(): void { - $this->set_admin_user(); - - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => true, - ]; - - $this->assertTrue($this->rule->passes($config)); - - - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => false, - ]; - - $this->assertTrue($this->rule->passes($config)); - } - - public function test_fails_when_admin_user_logging_enabled_and_user_cannot_manage_options(): void { - $user_id = $this->factory()->user->create(['role' => 'subscriber']); - wp_set_current_user($user_id); - - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => true, - ]; - - $this->assertFalse($this->rule->passes($config)); - } - - public function test_fails_when_admin_user_logging_enabled_and_no_user_logged_in(): void { - wp_set_current_user(0); - - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => true, - ]; - - $this->assertFalse($this->rule->passes($config)); - } - - public function test_passes_with_query_string_parameter(): void { - $config = [ - BasicConfigurationTab::ADMIN_USER_LOGGING => false, - ]; - - $this->assertTrue($this->rule->passes($config, 'query { posts { id } }')); - } -} diff --git a/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/SamplingRateRuleTest.php b/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/SamplingRateRuleTest.php index 58d37425..0413a449 100644 --- a/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/SamplingRateRuleTest.php +++ b/plugins/wpgraphql-logging/tests/wpunit/Logger/Rules/SamplingRateRuleTest.php @@ -26,7 +26,7 @@ public function setUp(): void { } public function test_get_name_returns_correct_name(): void { - $this->assertEquals('enabled_rule', $this->rule->get_name()); + $this->assertEquals('sampling_rate_rule', $this->rule->get_name()); } public function test_passes_with_100_percent_sampling_rate(): void {