Skip to content

Instantly share code, notes, and snippets.

@afragen
Last active December 25, 2022 10:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afragen/d46d3cc2c7e07d99f921560dfbb70246 to your computer and use it in GitHub Desktop.
Save afragen/d46d3cc2c7e07d99f921560dfbb70246 to your computer and use it in GitHub Desktop.
Catches 'PHP Fatal error Allowed memory size of xxxxxxx bytes exhausted' and 'RedisException' debug.log messages and flushes object cache.
<?php
/**
* Auto-flush object cache.
*
* @package Fragen\Auto_Flush_Cache
*
* Plugin Name: Auto-Flush Object Cache
* Plugin URI: https://gist.github.com/afragen/d46d3cc2c7e07d99f921560dfbb70246
* Description: Auto-flush the object cache when error log records PHP Fatal error for allowed memory size.
* Version: 0.7.0
* Author: Andy Fragen
* License: MIT
* Requires at least: 5.2
* Requires PHP: 7.1
* Gist Plugin URI: https://gist.github.com/afragen/d46d3cc2c7e07d99f921560dfbb70246
*/
namespace Fragen;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Allow plugins to define location of error log.
add_action(
'plugins_loaded',
function() {
$error_log_path = ini_get( 'error_log' );
if ( ! empty( $error_log_path ) ) {
( new Auto_Flush_Cache() )->run( $error_log_path );
}
}
);
/**
* Class Auto_Flush_Cache
*/
class Auto_Flush_Cache {
/**
* Let's get going.
*
* @param string $error_log_path File path to error log.
*
* @return void
*/
public function run( $error_log_path ) {
$tail_error_log = $this->tail_custom( $error_log_path, 50 );
$regex_errors = [ '/PHP Fatal error Allowed memory size of (\d+) bytes exhausted/', '/RedisException:/' ];
foreach ( $regex_errors as $regex ) {
if ( preg_match( $regex, $tail_error_log ) ) {
wp_cache_flush();
file_put_contents( $error_log_path, '' );
error_log( 'Object cache has been flushed and error log purged.' );
}
}
}
/**
* Slightly modified version of http://www.geekality.net/2011/05/28/php-tail-tackling-large-files/
*
* @author Torleif Berger, Lorenzo Stanco
* @link http://stackoverflow.com/a/15025877/995958
* @license http://creativecommons.org/licenses/by/3.0/
*
* @param string $filepath File path.
* @param int $lines Number of lines to tail.
* @param bool $adaptive Dynamic buffer length.
*/
private function tail_custom( $filepath, $lines = 1, $adaptive = true ) {
// Open file.
$f = @fopen( $filepath, 'rb' );
if ( false === $f ) {
return false;
}
// Sets buffer size, according to the number of lines to retrieve.
// This gives a performance boost when reading a few lines from the file.
if ( ! $adaptive ) {
$buffer = 4096;
} else {
$buffer = ( $lines < 2 ? 64 : ( $lines < 10 ? 512 : 4096 ) );
}
// Jump to last character.
fseek( $f, -1, SEEK_END );
// Read it and adjust line number if necessary.
// (Otherwise the result would be wrong if file doesn't end with a blank line).
if ( fread( $f, 1 ) !== "\n" ) {
--$lines;
}
// Start reading.
$output = '';
$chunk = '';
// While we would like more.
while ( ftell( $f ) > 0 && $lines >= 0 ) {
// Figure out how far back we should jump.
$seek = min( ftell( $f ), $buffer );
// Do the jump (backwards, relative to where we are).
fseek( $f, -$seek, SEEK_CUR );
// Read a chunk and prepend it to our output.
$output = ( $chunk = fread( $f, $seek ) ) . $output;
// Jump back to where we started reading.
fseek( $f, -mb_strlen( $chunk, '8bit' ), SEEK_CUR );
// Decrease our line counter.
$lines -= substr_count( $chunk, "\n" );
}
// While we have too many lines.
// (Because of buffer size we might have read too many).
while ( $lines++ < 0 ) {
// Find first newline and remove all text before that.
$output = substr( $output, strpos( $output, "\n" ) + 1 );
}
// Close file and return.
fclose( $f );
return trim( $output );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment