Last active
August 22, 2024 12:37
-
-
Save vollyimnetz/3670de82fd2b967722875627c27ea446 to your computer and use it in GitHub Desktop.
Wordpress Backend Logger
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace tm; | |
/** | |
* @see https://gist.github.com/vollyimnetz/3670de82fd2b967722875627c27ea446 | |
*/ | |
class BackendLogger { | |
private static $secondsToRelaod = 5; | |
private static $version = '1.2'; | |
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; } | |
#tm_backend_logger .logs .logEntry .text { white-space: break-spaces; } | |
</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