Skip to content

Instantly share code, notes, and snippets.

@vollyimnetz
Last active February 17, 2022 10:08
Show Gist options
  • Save vollyimnetz/3670de82fd2b967722875627c27ea446 to your computer and use it in GitHub Desktop.
Save vollyimnetz/3670de82fd2b967722875627c27ea446 to your computer and use it in GitHub Desktop.
Wordpress Backend Logger
<?php
namespace tm;
/**
* @see https://gist.github.com/vollyimnetz/3670de82fd2b967722875627c27ea446
*/
class BackendLogger {
private static $secondsToRelaod = 5;
private static $version = '1.1';
public static function init() {
self::$secondsToRelaod = apply_filters('tm_backendlogger_seconds_to_reload', 5);
add_action( 'init', [self::class,'validateAndRegistrate']);
add_action( 'rest_api_init', [self::class, 'initRest']);
}
public static function validateAndRegistrate() {
add_action( 'admin_menu', [self::class,'registerMenu' ]);
add_action( 'admin_enqueue_scripts', [self::class,'enqueueAdminScripts' ]);
}
public static function registerMenu() {
add_submenu_page( 'tools.php', 'Backend Logger', 'Backend Logger', 'manage_options', 'tm_backend_logger', [self::class, 'htmlOutput']);
}
/**
* Settings page display callback.
*/
public static function htmlOutput() {
$noenceField = 'tm_backend_logger_noence';
$noenceKey = 'tm_backend_logger_noence_0298540v00s09öä1sdf9';
?>
<div class="wrap">
<h2>Backend Logger <?php echo '(v'.self::$version.')'?></h2>
<?php if(empty(WP_DEBUG_LOG)) : ?>
<h3>Fehlerhaftes Setup?</h3>
<p>Es sieht so aus, als ob die PHP Konstante "WP_DEBUG_LOG" nicht korrekt gesetzt wurde. Hier der typische Standardcode. Achten Sie darauf die wp-debug.log nicht in einen vom Internet abrufbaren Ort abzulegen (z.B. unterhalb des Domaineinstiegspunktes).</p>
<textarea class="large-text code" rows="5">define('WP_DEBUG', true);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', __DIR__.'/wp-debug.log');
@ini_set('display_errors', 0);
</textarea>
<hr />
<?php endif; ?>
<div id="tm_backend_logger">
<div class="errorsWrapper">
<h3>Fehler bei der Log Abfrage</h3>
<div class="errors"></div>
<button class="button doDeleteErrors" type="button">zurücksetzen</button>
</div>
<div class="logsWrapper">
<h3>PHP Debug Log (neuste zuerst)</h3>
<fieldset>
<label for="tm_backend_logger_autosync">
<input type="checkbox" id="tm_backend_logger_autosync" name="tm_backend_logger_autosync" value="1" checked>
Automatisch alle <?php echo self::$secondsToRelaod; ?> Sekunden aktualisieren?
</label>
</fieldset>
<p>
<button class="button doSyncNow" type="button">jetzt aktualisieren</button>
<button class="button doTruncate" type="button">Log-File leeren</button>
</p>
<div class="logs"></div>
</div>
</div>
</div>
<script>
jQuery(function($) {
var settings = {
root : '<?php echo esc_url_raw( rest_url() ) ?>',
nonceRest : '<?php echo wp_create_nonce( 'wp_rest' ) ?>',
};
var errors = [];
var log = [];
var secondsToReload = <?php echo self::$secondsToRelaod; ?>;
var printedHashes = [];
jQuery('#tm_backend_logger .doDeleteErrors').click(() => {
console.log('errors zurückgesetzt');
errors = [];
printErrors();
});
jQuery('#tm_backend_logger .doSyncNow').click(() => {
getLog(false);
});
jQuery('#tm_backend_logger .doTruncate').click(() => {
doTruncate();
});
function doTruncate() {
jQuery.ajax({
method: 'GET',
url: settings.root+'backendlogger/v1/truncate',
headers: { 'X-WP-Nonce': settings.nonceRest },
})
.success(result => {
reset();
getLog(false);
})
.fail(() => {
addError('Beim Laden des Logs ist es zu einem Fehler gekommen.');
});
}
function reset() {
errors = [];
log = [];
printedHashes = [];
printErrors();
jQuery('#tm_backend_logger').find('.logs').empty();
}
function getLog(doTimeout = true) {
jQuery.ajax({
method: 'GET',
url: settings.root+'backendlogger/v1/read',
headers: { 'X-WP-Nonce': settings.nonceRest },
})
.success(result => {
handleLog(result);
if(doTimeout) setTimeout(() => { getLog( document.getElementById('tm_backend_logger_autosync').checked ); }, secondsToReload*1000 );
})
.fail(() => {
addError('Beim Laden des Logs ist es zu einem Fehler gekommen.');
if(doTimeout) setTimeout(() => { getLog( document.getElementById('tm_backend_logger_autosync').checked ); }, secondsToReload*1000 );
});
}
function handleLog(data) {
if(data.logs) {
let baseLogs = jQuery('#tm_backend_logger').find('.logs');
data.logs.forEach(elem => {
if(!printedHashes.includes( elem.hash ) ) {
printedHashes.unshift( elem.hash );
baseLogs.prepend('<div class="logEntry"><div class="time">'+formatDate(new Date( elem.timestamp*1000 ))+' ('+(elem.time)+')</div><div class="text">'+optimizeErrorOutput(elem.text)+'</div></div>');
}
});
//window.scrollTo(0,document.body.scrollHeight);
}
}
function addError(text) {
errors.push({
time: new Date(),
text: text,
});
while(errors.length>3) {
errors.shift();
}
printErrors();
}
function printErrors() {
let baseErrors = jQuery('#tm_backend_logger').find('.errors');
baseErrors.empty();
if(errors.length===0) {
jQuery('#tm_backend_logger .errorsWrapper').hide();
} else {
jQuery('#tm_backend_logger .errorsWrapper').show();
}
errors.forEach(elem => {
baseErrors.prepend('<div class="errorEntry"><div class="time">'+formatDate(elem.time)+'</div><div class="text">'+elem.text+'</div></div>');
});
//window.scrollTo(0,document.body.scrollHeight);
}
function optimizeErrorOutput(text) {
text = text.replaceAll("\n","<br>");
return text;
}
function formatDate(date) {
let year = date.getFullYear();
let month = ('0'+(date.getMonth()+1)).slice(-2);
let day = ('0'+date.getDate()).slice(-2);
let hour = ('0'+date.getHours()).slice(-2);
let minute = ('0'+date.getMinutes()).slice(-2);
let second = ('0'+date.getSeconds()).slice(-2);
let result = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
return result;
}
getLog();
printErrors();
});
</script>
<style type="text/css">
#tm_backend_logger .errors { border-bottom: 3px dashed rgba(0,0,0,0.2); }
#tm_backend_logger .errorsWrapper .button { margin:.5em 0 1em; }
#tm_backend_logger .errors .errorEntry .time { font-size:.9em; font-weight:bold; }
#tm_backend_logger .errors,
#tm_backend_logger .logs { background:#fff; }
#tm_backend_logger .errors .errorEntry,
#tm_backend_logger .logs .logEntry { border-top: 1px solid rgba(0,0,0,0.2); padding: 1.5em 2em; overflow-x: auto; white-space: nowrap; }
#tm_backend_logger .logs .logEntry .time { font-size:.8em; font-weight:bold; }
</style>
<?php
}
public static function enqueueAdminScripts($hook) {
if( $hook !== 'tools_page_tm_backend_logger' ) {
return;
}
wp_enqueue_script( 'jquery');
}
public static function initRest() {
//add endpoint customeranalysis
register_rest_route( 'backendlogger/v1', 'read', [
'methods' => 'GET',
'callback' => [self::class,'rest_backendlogger'],
'schema' => null,
'permission_callback' => [self::class,'checkRestPermission']
]);
register_rest_route( 'backendlogger/v1', 'truncate', [
'methods' => 'GET',
'callback' => [self::class,'rest_backendlogger_truncate'],
'schema' => null,
'permission_callback' => [self::class,'checkRestPermission']
]);
}
public static function checkRestPermission() {
if(!current_user_can('manage_options')) {
error_log('Try to access without permission (API).');
return false;
}
return true;
}
public static function rest_backendlogger_truncate() {
$pathToLogFile = WP_DEBUG_LOG;
if(!file_exists($pathToLogFile)) {
return ['error' => 'Log File can not be found.'];
}
$fp = fopen($pathToLogFile, "w");
fclose($fp);
return ['result' => 'OK'];
}
public static function rest_backendlogger(\WP_REST_Request $request) {
$pathToLogFile = WP_DEBUG_LOG;
if(!file_exists($pathToLogFile)) {
return ['error' => 'Log File can not be found.'];
}
$logFileContent = self::readLastLines($pathToLogFile, \apply_filters('tm_backend_logger_last_x_lines', 1000));
//optimize for the regex to match perfect
//-> add line break at start
//-> add line break at end and an "["
$logFileContent = "\n".$logFileContent."\n[";
//@see https://regex101.com/r/ZMdC0c/1
$re = '/\[(\d\d-\w\w\w-\d\d\d\d \d\d:\d\d:\d\d UTC)\]([\s\S]+?(?=\n\[))/m';
preg_match_all($re, $logFileContent, $matches, PREG_SET_ORDER, 0);
$result = [];
if(!empty($matches) && is_array($matches)) {
foreach($matches as $values) {
if(sizeof($values)!==3) continue;
$date = \DateTime::createFromFormat('d-M-Y H:i:s \U\T\C',$values[1]);
$tmp = [
'hash' => '',
'time' => $values[1],
'text' => htmlentities( trim($values[2]) ),
'timestamp' => $date->format('U'),
'date' => $date,
];
$tmp['hash'] = md5($tmp['time'].$tmp['text']);
$result[] = $tmp;
}
}
return ['logs' => $result];
}
private static function readLastLines($filename, $num, $reverse = false) {
if(filesize($filename)===0) return '';
$file = new \SplFileObject($filename, 'r');
$file->seek(PHP_INT_MAX);
$last_line = $file->key();
$first_line = $last_line - $num < 0 ? 0 : $last_line - $num;
$lines = new \LimitIterator($file, $first_line, $last_line);
$arr = iterator_to_array($lines);
if($reverse) $arr = array_reverse($arr);
return implode('', $arr);
}
}
BackendLogger::init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment