143 lines
4.6 KiB
PHP
143 lines
4.6 KiB
PHP
<?php
|
|
/**
|
|
* Absturzprotokoll (25.08.2026).
|
|
*
|
|
* Der Shop schaltet in index.php bewusst alles ab:
|
|
* ini_set('display_errors', 0); error_reporting(0);
|
|
* Das ist fuer die Anzeige richtig -- nur wurde damit auch das Protokollieren still
|
|
* gelegt. Ueber 130 HTTP-500 in zehn Tagen standen nirgends: nicht in error_log.txt,
|
|
* nicht im Apache-Log. Gefunden wurden sie erst, als Google sich beschwerte.
|
|
*
|
|
* DIESE DATEI AENDERT NICHTS AM VERHALTEN DES SHOPS.
|
|
* register_shutdown_function() und error_get_last() arbeiten unabhaengig von
|
|
* error_reporting -- kein ini_set, kein eigener Exception-Handler (der wuerde aus einem
|
|
* sauberen HTTP 500 eine halb gerenderte 200er-Seite machen). Ungefangene Exceptions
|
|
* erzeugen ihrerseits einen E_ERROR und landen deshalb ebenfalls hier.
|
|
*
|
|
* Geschrieben wird in eine EIGENE Datei, nicht in error_log.txt -- dort gehen zwischen
|
|
* ein paar tausend Bot-Meldungen pro Tag genau die Zeilen unter, um die es geht.
|
|
*
|
|
* Auswertung:
|
|
* tail -n 40 logs/fatal.log
|
|
* grep -c FATAL logs/fatal.log
|
|
* awk -F' | ' '{print $4}' logs/fatal.log | sort | uniq -c | sort -rn # haeufigste Stellen
|
|
*/
|
|
|
|
if (!function_exists('intelectra_fatal_log_init')) {
|
|
|
|
function intelectra_fatal_log_init() {
|
|
register_shutdown_function('intelectra_fatal_log_shutdown');
|
|
}
|
|
|
|
function intelectra_fatal_log_shutdown() {
|
|
$fehler = error_get_last();
|
|
|
|
if (!is_array($fehler)) {
|
|
return;
|
|
}
|
|
|
|
// Nur echte Abstuerze. Warnungen und Notices interessieren hier nicht -- der
|
|
// letzte error_get_last() waere sonst bei fast jedem Request gefuellt.
|
|
$fatal = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING,
|
|
E_COMPILE_ERROR, E_COMPILE_WARNING, E_USER_ERROR);
|
|
|
|
if (!in_array($fehler['type'], $fatal, true)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$datei = __DIR__ . '/../logs/fatal.log';
|
|
|
|
// Gleiche Stelle nicht im Sekundentakt mitschreiben: eine Bot-Welle auf eine
|
|
// kaputte Seite wuerde das Log sonst in Minuten unbrauchbar machen.
|
|
if (intelectra_fatal_log_schon_gesehen($fehler)) {
|
|
return;
|
|
}
|
|
|
|
// Bei 5 MB einmal wegrotieren. Mehr als zwei Generationen braucht es nicht --
|
|
// wer hier nachsieht, sucht den Fehler von heute.
|
|
if (@filesize($datei) > 5242880) {
|
|
@rename($datei, $datei . '.1');
|
|
}
|
|
|
|
$anfrage = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '-';
|
|
$methode = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : '-';
|
|
$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '-';
|
|
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '-';
|
|
$agent = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 120) : '-';
|
|
|
|
// Die Meldung einer ungefangenen Exception bringt den Stack-Trace mit. Der ist
|
|
// das Wertvollste daran, aber mehrzeilig -- fuer eine Zeile pro Fehler wird er
|
|
// auf die ersten Ebenen eingedampft.
|
|
$meldung = preg_replace('/\s+/', ' ', trim($fehler['message']));
|
|
if (strlen($meldung) > 600) {
|
|
$meldung = substr($meldung, 0, 600) . ' …';
|
|
}
|
|
|
|
$zeile = sprintf(
|
|
"[%s] FATAL | %s | %s:%d | %s %s | ref=%s | ip=%s | ua=%s\n",
|
|
date('Y-m-d H:i:s'),
|
|
$meldung,
|
|
$fehler['file'],
|
|
$fehler['line'],
|
|
$methode,
|
|
$anfrage,
|
|
$referer,
|
|
$ip,
|
|
$agent
|
|
);
|
|
|
|
@file_put_contents($datei, $zeile, FILE_APPEND | LOCK_EX);
|
|
} catch (Throwable $e) {
|
|
// Ein Protokoll, das selbst Fehler wirft, ist schlimmer als keins.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wurde genau dieser Fehler gerade eben schon protokolliert?
|
|
*
|
|
* Schluessel ist Datei + Zeile + Meldung, Sperrfrist 60 Sekunden. Die Merkliste liegt
|
|
* neben dem Log und wird nur im Fehlerfall angefasst -- im Normalbetrieb kostet diese
|
|
* Datei nichts.
|
|
*/
|
|
function intelectra_fatal_log_schon_gesehen($fehler) {
|
|
$merkdatei = __DIR__ . '/../logs/.fatal_seen.json';
|
|
$schluessel = md5($fehler['file'] . ':' . $fehler['line'] . ':' . $fehler['message']);
|
|
$jetzt = time();
|
|
|
|
$gesehen = array();
|
|
if (is_readable($merkdatei)) {
|
|
$roh = @file_get_contents($merkdatei);
|
|
if ($roh) {
|
|
$dekodiert = @json_decode($roh, true);
|
|
if (is_array($dekodiert)) {
|
|
$gesehen = $dekodiert;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($gesehen[$schluessel]) && ($jetzt - (int)$gesehen[$schluessel]) < 60) {
|
|
return true;
|
|
}
|
|
|
|
// Abgelaufene Eintraege raus, damit die Datei nicht waechst.
|
|
foreach ($gesehen as $k => $zeitpunkt) {
|
|
if (($jetzt - (int)$zeitpunkt) > 3600) {
|
|
unset($gesehen[$k]);
|
|
}
|
|
}
|
|
|
|
$gesehen[$schluessel] = $jetzt;
|
|
|
|
if (count($gesehen) > 200) {
|
|
$gesehen = array_slice($gesehen, -200, 200, true);
|
|
}
|
|
|
|
@file_put_contents($merkdatei, json_encode($gesehen), LOCK_EX);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* EOF */
|