PHPackages                             brianhenryie/bh-wp-logger - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. brianhenryie/bh-wp-logger

ActiveLibrary

brianhenryie/bh-wp-logger
=========================

A PSR logger for WordPress plugins, with a nice WP\_List\_Table UI.

0.2.1(2mo ago)1914.2k↓50%3[1 issues](https://github.com/BrianHenryIE/bh-wp-logger/issues)[4 PRs](https://github.com/BrianHenryIE/bh-wp-logger/pulls)GPL-2.0-or-laterPHPPHP &gt;=8.0CI failing

Since Apr 21Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/BrianHenryIE/bh-wp-logger)[ Packagist](https://packagist.org/packages/brianhenryie/bh-wp-logger)[ RSS](/packages/brianhenryie-bh-wp-logger/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (4)Dependencies (25)Versions (19)Used By (0)

[![PHP](https://camo.githubusercontent.com/0aa406681a2defd6e07476f8eaba3a362addecd2caa890e6d610eedf0325744d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e302d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/0aa406681a2defd6e07476f8eaba3a362addecd2caa890e6d610eedf0325744d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e302d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465) [![WordPress tested 6.9](https://camo.githubusercontent.com/463fa70a795b53a9ee0239e2ef5e3a5b983430e8295db3fbdd75c51556e89de3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f576f726450726573732d76362e392532307465737465642d3030373361612e737667)](#) [![PHPCS WPCS](https://camo.githubusercontent.com/c33b04161cac29bdc3a50b0920a3e07fbe9d034e5bf956d30b42821b2d55092e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f50485043532d576f72645072657373253230436f64696e672532305374616e64617264732532302d3838393242462e737667)](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) [![PHPUnit ](.github/coverage.svg)](https://brianhenryie.github.io/bh-wp-logger/) [![PHPStan ](https://camo.githubusercontent.com/da44494d619aa8e3e1a52136f46d36b289bfbd2d6c723437753d0f079b69da50/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d4c6576656c25323031302532302d3261356561372e737667)](https://github.com/szepeviktor/phpstan-wordpress)

BH WP Logger
============

[](#bh-wp-logger)

Zero-config logger UI for WordPress plugins.

```
$logger = Logger::instance();
```

Wraps existing [PSR-3](https://www.php-fig.org/psr/psr-3/) loggers and adds some UI.

- [KLogger](https://github.com/katzgrau/KLogger)
- [WC\_Logger](https://github.com/woocommerce/woocommerce/blob/trunk/includes/class-wc-logger.php)
- [PSR-3 NullLogger](https://github.com/php-fig/log/blob/master/Psr/Log/NullLogger.php)

Uses KLogger by default, WC\_Logger when specified, NullLogger when log level is set to "none".

Uses PHP's `set_error_handler()` to catch PHP deprecated/warning/notice/errors.

Hook into WordPress's deprecated function hooks (`deprecated_function_run` etc.) to log those only once per day.

Uses PHP's `register_shutdown_function()` to catch Exceptions related to the plugin.

Deletes log files older than 30 days on cron.

Records a full backtrace on errors. Records two steps of backtrace on every log when the level is Debug.

UI
--

[](#ui)

Displays logs in `WP_List_Table`.

[![Logs WP_List_Table](./.github/logs-wp-list-table.png "Logs WP_List_Table")](./.github/logs-wp-list-table.png)

Shows a dismissible admin error notice each time there is a new error.

[![Admin Error Notice](./.github/admin-error-notice.png "Admin error notice")](./.github/admin-error-notice.png)

Adds a link to the logs view on the plugin's entry on plugins.php.

[![Plugins page logs link](./.github/plugins-page-logs-link.png "Plugins page logs link")](./.github/plugins-page-logs-link.png)

When log messages contain ``wp_user:123`` (NB: surrounded by single backticks) it will be replaced with a link to the user profile. This allows for logging useful references to users without logging their PII.

Similarly, any post type can be linked with ``post_type_name:123``, e.g. ``shop_order:123`` will link to the WooCommerce order.

[![Links in log message](./.github/links-in-logs.png "Log messages link to user or post")](./.github/links-in-logs.png)

Use
---

[](#use)

### Composer

[](#composer)

```
composer require brianhenryie/bh-wp-logger

```

Expect breaking changes with every release until v1.0.0.

### Instantiate

[](#instantiate)

You should use [brianhenryie/strauss](https://github.com/BrianHenryIE/strauss) to prefix the library's namespace. Then Strauss's autoloader will include the files for you.

The following will work, but it will be faster and more reliable to provide the settings:

```
$logger = Logger::instance();
```

Provide the settings:

```
$logger_settings = new class() implements BrianHenryIE\WP_Logger\API\Logger_Settings_Interface {
    use BrianHenryIE\WP_Logger\Logger_Settings_Trait;

	public function get_log_level(): string {
		return LogLevel::INFO;
	}

	// This is used in admin notices.
	public function get_plugin_name(): string {
		return "My Plugin Name";
	}

	// This is used in option names and URLs.
	public function get_plugin_slug(): string {
		return "my-plugin-name";
	}

	// This is needed for the plugins.php logs link.
	public function get_plugin_basename(): string {
		return "my-plugin-name/my-plugin-name.php";
	}
};
$logger = Logger::instance( $logger_settings );
```

Then pass around your `$logger` instance.

After the logger has been instantiated once, subsequent calls to `::instance()` return the existing instance and any `$logger_settings` passed is ignored.

To use WooCommerce's native `WC_Logger`, use the `WooCommerce_Logger_Interface` interface (which just extends `Logger_Settings_Interface`) on the settings object.

```
$logger_settings = new class() implements BrianHenryIE\WP_Logger\WooCommerce\WooCommerce_Logger_Settings_Interface {
    ...
```

### WooCommerce Settings

[](#woocommerce-settings)

Something like this can be used for WooCommerce Settings API.

```
$log_levels        = array( 'none', LogLevel::ERROR, LogLevel::WARNING, LogLevel::NOTICE, LogLevel::INFO, LogLevel::DEBUG );
$log_levels_option = array();
foreach ( $log_levels as $log_level ) {
    $log_levels_option[ $log_level ] = ucfirst( $log_level );
}

$setting_fields[] = array(
    'title'    => __( 'Log Level', 'text-domain' ),
    'label'    => __( 'Enable Logging', 'text-domain' ),
    'type'     => 'select',
    'options'  => $log_levels_option,
    'desc'    => __( 'Increasingly detailed levels of logs. ', 'text-domain' ) . 'View Logs',
    'desc_tip' => false,
    'default'  => 'notice',
    'id'       => 'text-domain-log-level',
);
```

[![WooCommerce Settings](./.github/woocommerce-settings.png "WooCommerce Settings")](./.github/woocommerce-settings.png)

Filters
-------

[](#filters)

Two filters are present, to modify the log data as it is being saved, and to modify the log data as it is being presented.

E.g. change the log level for specific log messages:

```
/**
 * Modify the log data array or return null to cancel logging this entry.
 * The library php-http/logger-plugin is using INFO for detailed logging, let's change that to DEBUG.
 *
 * @hooked {$plugin_slug}_bh_wp_logger_log
 *
 * @pararm array{level:string,message:string,context:array} $log_data
 * @param Logger_Settings_Interface $settings
 * @param BH_WP_PSR_Logger $bh_wp_psr_logger
 */
function modify_log_data( array $log_data, \BrianHenryIE\WP_Logger\Logger_Settings_Interface $settings, \BrianHenryIE\WP_Logger\API\BH_WP_PSR_Logger $bh_wp_psr_logger): ?array {

    if ( 0 === strpos( $log_data['message'], 'Sending request' ) ) {
        $log_data['level'] = LogLevel::DEBUG;
    }

    return $log_data;
}
add_filter( "{$plugin_slug}_bh_wp_logger_log", 'modify_log_data', 10, 3 );
```

E.g. turn text in logs into hyperlinks as it is being displayed in the logs table:

```
/**
 * Update ` `wc_order:123` ` with links to the order.
 * Use preg_replace_callback to find and replace all instances in the string.
 *
 * @hooked {$plugin_slug}_bh_wp_logger_column
 *
 * @param string                                                          $column_output The column output so far.
 * @param array{time:string, level:string, message:string, context:array} $item The log entry row.
 * @param string                                                          $column_name The current column name.
 * @param Logger_Settings_Interface                                       $logger_settings The logger settings.
 * @param BH_WP_PSR_Logger                                                $bh_wp_psr_logger The logger API instance.
 *
 * @return string
 */
function replace_wc_order_id_with_link( string $column_output, array $item, string $column_name,\BrianHenryIE\WP_Logger\Logger_Settings_Interface $logger_settings, \BrianHenryIE\WP_Logger\API\BH_WP_PSR_Logger $bh_wp_psr_logger ): string {

    if ( 'message' !== $column_name ) {
        return $column_output;
    }

    $callback = function( array $matches ): string {

        $url  = admin_url( "post.php?post={$matches[1]}&action=edit" );
        $link = "Order {$matches[1]}";

        return $link;
    };

    $message = preg_replace_callback( '/`wc_order:(\d+)`/', $callback, $column_output ) ?? $column_output;

    return $message;
}
add_filter( "{$plugin_slug}_bh_wp_logger_column", 'replace_wc_order_id_with_link', 10, 5 );
```

WP\_Mock
--------

[](#wp_mock)

If using WP\_Mock for your tests, and you are instantiating this logger, the following should help:

```
\Patchwork\redefine(
    array( Logger::class, '__construct' ),
    function( $settings ) {}
);
```

### Test Plugin

[](#test-plugin)

The `development-plugin` folder contains a small plugin with buttons to trigger the types of errors that can be logged.

[![Test Plugin](./.github/development-plugin.png "Test Plugin")](./.github/development-plugin.png)

### Best Practice

[](#best-practice)

From my limited logging experience, I find it useful to add a `debug` log at the beginning of functions and an appropriate `info`...`error` as the function returns.

TODO
----

[](#todo)

- Check log directory is not publicly accessible
- Check uploads dir chmod (is writable). =&gt; see [brianhenryie/bh-wp-private-uploads](https://github.com/BrianHenryIE/bh-wp-private-uploads)
- Add current user to context
- Don't log empty context (WC)
- Option for what level of errors to display as admin notices
- Option for user capability for displaying admin notices (filter, at least)
- Zero-config WC\_Logger: detect "wc", "woo" in plugin names
- Use [Code prettify](https://github.com/googlearchive/code-prettify) on the context json : Used [caldwell/renderjson](https://github.com/caldwell/renderjson)
- Paging and filtering
- Hyperlinks in messages
- Record timestamp the logs were last viewed at, make the plugins.php link bold if new logs are present.
- Auto-delete old logs
- Log notice should dismiss when the log page is visited
- Redact sensitive data. e.g. use `userid:123` in the saved logs and replace it with richer data when displaying them
- Add errors to dashboard Site Health widget
- Ensure output is properly escaped

Minor concerns:

- Debug logging could maybe be moved to a shutdown handler
- Transients to suppress duplicate logs might be inefficient

Status
======

[](#status)

To date I think it has been used mostly by me, i.e. internal projects. There are no egregious issues. It *should* work for everyone but I would like some feedback from others on how well it works for you.

I'll start at Semver 1.0.0 once I've caught up with WPCS ✅, PhpStan ✅ and PhpUnit. There's about 65 tests and 43% coverage. WPCS + PHPStan are both pretty good now 100%.

I think that's higher code-quality than most WordPress plugins.

###  Health Score

47

—

FairBetter than 94% of packages

Maintenance86

Actively maintained with recent releases

Popularity36

Limited adoption so far

Community12

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 96.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~167 days

Total

5

Last Release

89d ago

PHP version history (2 changes)0.1PHP &gt;=7.4

0.2PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/3541a510f89dedd04f0e7103201e411f6f22685a13e941b3384d97d4c7d32b09?d=identicon)[BrianHenryIE](/maintainers/BrianHenryIE)

---

Top Contributors

[![BrianHenryIE](https://avatars.githubusercontent.com/u/4720401?v=4)](https://github.com/BrianHenryIE "BrianHenryIE (327 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (11 commits)")

---

Tags

psrpsr-3wordpress

###  Code Quality

Static AnalysisRector

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/brianhenryie-bh-wp-logger/health.svg)

```
[![Health](https://phpackages.com/badges/brianhenryie-bh-wp-logger/health.svg)](https://phpackages.com/packages/brianhenryie-bh-wp-logger)
```

###  Alternatives

[elgg/elgg

Elgg is an award-winning social networking engine, delivering the building blocks that enable businesses, schools, universities and associations to create their own fully-featured social networks and applications.

1.7k15.7k5](/packages/elgg-elgg)[api-platform/metadata

API Resource-oriented metadata attributes and factories

243.5M96](/packages/api-platform-metadata)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[flowwow/cloudpayments-php-client

cloudpayments api client

2188.2k](/packages/flowwow-cloudpayments-php-client)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
