docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,291 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging;
use App\Framework\Attributes\Singleton;
use App\Framework\Logging\ValueObjects\LogContext;
/**
* Interceptor für PHP's native error_log() - Elegant und transparent
*/
#[Singleton]
final class PhpErrorLogInterceptor
{
private $interceptStream = null;
private ?string $originalErrorLog = null;
private bool $isInstalled = false;
public function __construct(
private Logger $logger
) {
}
/**
* Installiert den Interceptor - fängt alle error_log() Aufrufe ab
*/
public function install(): void
{
if ($this->isInstalled) {
return;
}
// Aktuelles error_log Setting speichern
$this->originalErrorLog = ini_get('error_log') ?: null;
// Temporären Stream für Interception erstellen
$this->interceptStream = fopen('php://temp', 'r+');
if ($this->interceptStream === false) {
throw new \RuntimeException('Could not create intercept stream');
}
// PHP error_log auf unseren Stream umleiten
$streamPath = stream_get_meta_data($this->interceptStream)['uri'];
ini_set('error_log', $streamPath);
// Stream-Monitoring in separatem Process starten
$this->startStreamMonitoring();
$this->isInstalled = true;
}
/**
* Deinstalliert den Interceptor und stellt original error_log wieder her
*/
public function uninstall(): void
{
if (! $this->isInstalled) {
return;
}
// Original error_log Setting wiederherstellen
if ($this->originalErrorLog !== null) {
ini_set('error_log', $this->originalErrorLog);
} else {
ini_restore('error_log');
}
// Stream schließen
if ($this->interceptStream !== null) {
fclose($this->interceptStream);
$this->interceptStream = null;
}
$this->isInstalled = false;
}
/**
* Startet das Stream-Monitoring für error_log Interception
*/
private function startStreamMonitoring(): void
{
if ($this->interceptStream === null) {
return;
}
// Nicht-blockierender Stream
stream_set_blocking($this->interceptStream, false);
// Register shutdown function um letzten Content zu lesen
register_shutdown_function(function () {
$this->readAndForwardLogs();
});
// Für CLI: Monitoring-Loop in Background
if (php_sapi_name() === 'cli') {
$this->startCliMonitoring();
} else {
// Für Web: Output Buffer Hook
$this->startWebMonitoring();
}
}
/**
* CLI Monitoring - Background Stream Reading
*/
private function startCliMonitoring(): void
{
// Registriere Tick-Handler für periodisches Lesen
declare(ticks=1);
register_tick_function(function () {
$this->readAndForwardLogs();
});
}
/**
* Web Monitoring - Output Buffer Hook
*/
private function startWebMonitoring(): void
{
// Output buffer callback um am Ende der Request zu lesen
ob_start(function (string $buffer) {
$this->readAndForwardLogs();
return $buffer;
});
}
/**
* Liest Logs aus dem Stream und leitet sie an Framework Logger weiter
*/
private function readAndForwardLogs(): void
{
if ($this->interceptStream === null) {
return;
}
// Stream position zurücksetzen
rewind($this->interceptStream);
// Alles aus dem Stream lesen
$content = stream_get_contents($this->interceptStream);
if ($content === false || $content === '') {
return;
}
// Stream für nächste Writes leeren
ftruncate($this->interceptStream, 0);
rewind($this->interceptStream);
// Content in einzelne Log-Zeilen aufteilen
$lines = array_filter(explode("\n", trim($content)));
foreach ($lines as $line) {
$this->forwardLogLine(trim($line));
}
}
/**
* Leitet eine einzelne Log-Zeile an Framework Logger weiter
*/
private function forwardLogLine(string $line): void
{
if (empty($line)) {
return;
}
// Parse PHP error_log Format: [timestamp] message
$context = $this->parseErrorLogLine($line);
// Bestimme Log Level basierend auf Content
$level = $this->determineLogLevel($line);
// An Framework Logger weiterleiten
$this->logger->error->log($level, $line, $context);
// Optional: Auch an original error_log weiterleiten
if ($this->originalErrorLog !== null && $this->originalErrorLog !== '') {
$this->forwardToOriginalErrorLog($line);
}
}
/**
* Parst eine error_log Zeile und extrahiert Context
*/
private function parseErrorLogLine(string $line): LogContext
{
$caller = $this->extractCaller();
$context = LogContext::empty()
->withData([
'source' => 'php_error_log',
'original_line' => $line,
'caller' => $caller,
'intercepted_at' => date('Y-m-d H:i:s'),
]);
// Parse PHP Error Format wenn möglich
if (preg_match('/^\[([^\]]+)\]\s+(.+)$/', $line, $matches)) {
$context = $context->withData([
'original_timestamp' => $matches[1],
'parsed_message' => $matches[2],
]);
}
return $context;
}
/**
* Bestimmt das passende Log Level für eine Nachricht
*/
private function determineLogLevel(string $line): LogLevel
{
$lowerLine = strtolower($line);
return match (true) {
str_contains($lowerLine, 'fatal') || str_contains($lowerLine, 'emergency') => LogLevel::EMERGENCY,
str_contains($lowerLine, 'critical') || str_contains($lowerLine, 'alert') => LogLevel::CRITICAL,
str_contains($lowerLine, 'error') => LogLevel::ERROR,
str_contains($lowerLine, 'warning') || str_contains($lowerLine, 'warn') => LogLevel::WARNING,
str_contains($lowerLine, 'notice') => LogLevel::NOTICE,
str_contains($lowerLine, 'info') => LogLevel::INFO,
str_contains($lowerLine, 'debug') => LogLevel::DEBUG,
default => LogLevel::ERROR, // Default für error_log
};
}
/**
* Extrahiert Aufrufer-Informationen aus Backtrace
*/
private function extractCaller(): array
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// Suche nach dem ersten Aufruf außerhalb des Interceptors
foreach ($trace as $frame) {
if (
isset($frame['file']) &&
! str_contains($frame['file'], 'PhpErrorLogInterceptor.php') &&
($frame['function'] ?? '') !== 'error_log'
) {
return [
'file' => $frame['file'],
'line' => $frame['line'] ?? 0,
'function' => $frame['function'] ?? 'unknown',
'class' => $frame['class'] ?? null,
];
}
}
return ['file' => 'unknown', 'line' => 0, 'function' => 'unknown', 'class' => null];
}
/**
* Leitet Log an originales error_log weiter (optional)
*/
private function forwardToOriginalErrorLog(string $line): void
{
if ($this->originalErrorLog === null) {
return;
}
// Temporär original error_log wiederherstellen
$current = ini_get('error_log');
ini_set('error_log', $this->originalErrorLog);
// An original error_log senden
\error_log($line);
// Wieder auf unseren Stream umstellen
ini_set('error_log', $current);
}
/**
* Prüft ob der Interceptor installiert ist
*/
public function isInstalled(): bool
{
return $this->isInstalled;
}
/**
* Destruktor - stellt sicher dass alles sauber aufgeräumt wird
*/
public function __destruct()
{
$this->readAndForwardLogs(); // Letzte Logs lesen
}
}