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

@@ -39,7 +39,7 @@ class ConsoleHandler implements LogHandler
public function __construct(
LogLevel|int $minLevel = LogLevel::DEBUG,
bool $debugOnly = true,
string $outputFormat = '{color}[{level_name}]{reset} {timestamp} {request_id}{message}',
string $outputFormat = '{color}[{level_name}]{reset} {timestamp} {request_id}{message}{structured}',
private readonly LogLevel $stderrLevel = LogLevel::WARNING,
) {
$this->minLevel = $minLevel instanceof LogLevel ? $minLevel : LogLevel::fromValue($minLevel);
@@ -80,6 +80,9 @@ class ConsoleHandler implements LogHandler
? "[{$record->getExtra('request_id')}] "
: '';
// Structured Logging Extras formatieren
$structuredInfo = $this->formatStructuredExtras($record);
// Werte für Platzhalter im Format
$values = [
'{color}' => $color,
@@ -89,6 +92,7 @@ class ConsoleHandler implements LogHandler
'{request_id}' => $requestId,
'{message}' => $record->getMessage(),
'{channel}' => $record->getChannel() ? "[{$record->getChannel()}] " : '',
'{structured}' => $structuredInfo,
];
// Formatierte Ausgabe erstellen
@@ -123,4 +127,91 @@ class ConsoleHandler implements LogHandler
return $this;
}
/**
* Formatiert Structured Logging Extras für Console-Ausgabe mit Farben
*/
private function formatStructuredExtras(LogRecord $record): string
{
$parts = [];
$reset = ConsoleColor::RESET->toAnsi();
// Tags anzeigen (Cyan mit Tag-Symbol)
if ($record->hasExtra('structured_tags')) {
$tags = $record->getExtra('structured_tags');
if (! empty($tags)) {
$cyan = ConsoleColor::CYAN->toAnsi();
$tagString = implode(',', $tags);
$parts[] = "{$cyan}🏷 [{$tagString}]{$reset}";
}
}
// Trace-Kontext anzeigen (Blau mit Trace-Symbol)
if ($record->hasExtra('trace_context')) {
$traceContext = $record->getExtra('trace_context');
$blue = ConsoleColor::BLUE->toAnsi();
if (isset($traceContext['trace_id'])) {
$traceId = substr($traceContext['trace_id'], 0, 8);
$parts[] = "{$blue}🔍 {$traceId}{$reset}";
}
if (isset($traceContext['active_span']['spanId'])) {
$spanId = substr($traceContext['active_span']['spanId'], 0, 8);
$parts[] = "{$blue}{$spanId}{$reset}";
}
}
// User-Kontext anzeigen (Grün mit User-Symbol)
if ($record->hasExtra('user_context')) {
$userContext = $record->getExtra('user_context');
$green = ConsoleColor::GREEN->toAnsi();
if (isset($userContext['user_id'])) {
// Anonymisierte User-ID für Privacy
$userId = substr(md5($userContext['user_id']), 0, 8);
$parts[] = "{$green}👤 {$userId}{$reset}";
} elseif (isset($userContext['is_authenticated']) && ! $userContext['is_authenticated']) {
$parts[] = "{$green}👤 anon{$reset}";
}
}
// Request-Kontext anzeigen (Gelb mit HTTP-Symbol)
if ($record->hasExtra('request_context')) {
$requestContext = $record->getExtra('request_context');
if (isset($requestContext['request_method'], $requestContext['request_uri'])) {
$yellow = ConsoleColor::YELLOW->toAnsi();
$method = $requestContext['request_method'];
$uri = $requestContext['request_uri'];
// Kompakte URI-Darstellung
if (strlen($uri) > 25) {
$uri = substr($uri, 0, 22) . '...';
}
$parts[] = "{$yellow}🌐 {$method} {$uri}{$reset}";
}
}
// Context-Data anzeigen (Grau mit Data-Symbol)
$context = $record->getContext();
if (! empty($context)) {
$contextKeys = array_keys($context);
// Interne Keys ausfiltern
$contextKeys = array_filter($contextKeys, fn ($key) => ! str_starts_with($key, '_'));
if (! empty($contextKeys)) {
$gray = ConsoleColor::GRAY->toAnsi();
$keyCount = count($contextKeys);
if ($keyCount <= 3) {
$keyString = implode('·', $contextKeys);
} else {
$keyString = implode('·', array_slice($contextKeys, 0, 2)) . "·+{$keyCount}";
}
$parts[] = "{$gray}📊 {$keyString}{$reset}";
}
}
return empty($parts) ? '' : "\n " . implode(' ', $parts);
}
}

View File

@@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace App\Framework\Logging\Handlers;
use App\Framework\Core\PathProvider;
use App\Framework\Logging\LogChannel;
use App\Framework\Logging\LogConfig;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
/**
* Handler für automatisches Routing von Log-Nachrichten in verschiedene Dateien
*
* Schreibt Logs basierend auf dem Channel in verschiedene Dateien.
* Verwendet LogConfig für die Pfad-Auflösung und PathProvider für die Pfad-Normalisierung.
*/
final class MultiFileHandler implements LogHandler
{
/**
* @var array<string, resource> Cache für geöffnete Datei-Handles
*/
private array $fileHandles = [];
public function __construct(
private readonly mixed $logConfig,
private readonly mixed $pathProvider,
private readonly LogLevel $minLevel = LogLevel::DEBUG,
private readonly string $outputFormat = '[{timestamp}] [{level_name}] [{channel}] {message}',
private readonly int $fileMode = 0644
) {
}
/**
* Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
*/
public function isHandling(LogRecord $record): bool
{
return $record->getLevel()->isHigherThan($this->minLevel) || $record->getLevel()->isEqual($this->minLevel);
}
/**
* Verarbeitet den Log-Eintrag und schreibt ihn in die entsprechende Datei
*/
public function handle(LogRecord $record): void
{
if (! $this->isHandling($record)) {
return;
}
// Channel aus Record extrahieren, Fallback zu 'app'
$channel = $record->getChannel() ?? LogChannel::APP->value;
// Entsprechende Log-Datei ermitteln
$logFile = $this->getLogFileForChannel($channel);
// Log-Nachricht formatieren
$formattedMessage = $this->formatMessage($record);
// In Datei schreiben
$this->writeToFile($logFile, $formattedMessage);
}
/**
* Ermittelt die Log-Datei für einen Channel
*/
private function getLogFileForChannel(string $channel): string
{
// Channel zu LogConfig-Key mappen
$logPathKey = match ($channel) {
LogChannel::SECURITY->value => 'security',
LogChannel::CACHE->value => 'cache',
LogChannel::DATABASE->value => 'database',
LogChannel::FRAMEWORK->value => 'framework',
LogChannel::ERROR->value => 'error',
default => 'app'
};
return $this->logConfig->getLogPath($logPathKey);
}
/**
* Formatiert die Log-Nachricht
*/
private function formatMessage(LogRecord $record): string
{
$replacements = [
'{timestamp}' => $record->getFormattedTimestamp('Y-m-d H:i:s'),
'{level_name}' => $record->getLevel()->getName(),
'{channel}' => $record->getChannel() ?? 'app',
'{message}' => $record->getMessage(),
];
$formatted = str_replace(array_keys($replacements), array_values($replacements), $this->outputFormat);
// Context hinzufügen, falls vorhanden
$context = $record->getContext();
if (! empty($context)) {
$formatted .= ' ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return $formatted . PHP_EOL;
}
/**
* Schreibt die formatierte Nachricht in eine Datei
*/
private function writeToFile(string $logFile, string $message): void
{
// Verzeichnis erstellen, falls es nicht existiert
$directory = dirname($logFile);
if (! is_dir($directory)) {
if (! mkdir($directory, 0755, true) && ! is_dir($directory)) {
// Fehler beim Erstellen des Verzeichnisses - verwende Fallback
error_log("Failed to create log directory: $directory");
return;
}
}
// Datei-Handle aus Cache holen oder erstellen
$cacheKey = $logFile;
if (! isset($this->fileHandles[$cacheKey])) {
$handle = fopen($logFile, 'a');
if ($handle === false) {
error_log("Failed to open log file: $logFile");
return;
}
// Datei-Berechtigungen setzen
chmod($logFile, $this->fileMode);
$this->fileHandles[$cacheKey] = $handle;
}
// In Datei schreiben
fwrite($this->fileHandles[$cacheKey], $message);
fflush($this->fileHandles[$cacheKey]);
}
/**
* Schließt alle geöffneten Datei-Handles dieser Instanz
*/
public function closeAllHandles(): void
{
foreach ($this->fileHandles as $handle) {
if (is_resource($handle)) {
fclose($handle);
}
}
$this->fileHandles = [];
}
/**
* Destruktor - schließt alle Datei-Handles
*/
public function __destruct()
{
$this->closeAllHandles();
}
}