Files
michaelschiemer/src/Framework/Logging/Handlers/ConsoleHandler.php
Michael Schiemer 5050c7d73a 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
2025-10-05 11:05:04 +02:00

218 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Logging\Handlers;
use App\Framework\Console\ConsoleColor;
use App\Framework\Logging\LogHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
/**
* Handler für die Ausgabe von Log-Einträgen in der Konsole.
*/
class ConsoleHandler implements LogHandler
{
/**
* @var LogLevel Minimales Level, ab dem dieser Handler aktiv wird
*/
private LogLevel $minLevel;
/**
* @var bool Ob der Handler nur im Debug-Modus aktiv ist
*/
private bool $debugOnly;
/**
* @var string Format für die Ausgabe
*/
private string $outputFormat;
/**
* Erstellt einen neuen ConsoleHandler
*
* @param LogLevel|int $minLevel Minimales Level, ab dem dieser Handler aktiv wird
* @param bool $debugOnly Ob der Handler nur im Debug-Modus aktiv ist
* @param string $outputFormat Format für die Ausgabe
*/
public function __construct(
LogLevel|int $minLevel = LogLevel::DEBUG,
bool $debugOnly = true,
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);
$this->debugOnly = $debugOnly;
$this->outputFormat = $outputFormat;
}
/**
* Überprüft, ob dieser Handler den Log-Eintrag verarbeiten soll
*/
public function isHandling(LogRecord $record): bool
{
// Nur im CLI-Modus aktiv - NIE bei Web-Requests!
if (PHP_SAPI !== 'cli') {
return false;
}
// Optional: Debug-Modus-Check nur in CLI
if ($this->debugOnly && ! filter_var(getenv('APP_DEBUG'), FILTER_VALIDATE_BOOLEAN)) {
return false;
}
return $record->getLevel()->value >= $this->minLevel->value;
}
/**
* Verarbeitet einen Log-Eintrag
*/
public function handle(LogRecord $record): void
{
$logLevel = $record->getLevel();
$color = $logLevel->getConsoleColor()->value;
$reset = ConsoleColor::RESET->value;
// Request-ID-Teil erstellen, falls vorhanden
$requestId = $record->hasExtra('request_id')
? "[{$record->getExtra('request_id')}] "
: '';
// Structured Logging Extras formatieren
$structuredInfo = $this->formatStructuredExtras($record);
// Werte für Platzhalter im Format
$values = [
'{color}' => $color,
'{reset}' => $reset,
'{level_name}' => $record->getLevel()->getName(),
'{timestamp}' => $record->getFormattedTimestamp(),
'{request_id}' => $requestId,
'{message}' => $record->getMessage(),
'{channel}' => $record->getChannel() ? "[{$record->getChannel()}] " : '',
'{structured}' => $structuredInfo,
];
// Formatierte Ausgabe erstellen
$output = strtr($this->outputFormat, $values) . PHP_EOL;
// Fehler und Warnungen auf stderr, alles andere auf stdout
if ($record->getLevel()->value >= $this->stderrLevel->value) {
// WARNING, ERROR, CRITICAL, ALERT, EMERGENCY -> stderr
file_put_contents('php://stderr', $output);
} else {
// DEBUG, INFO, NOTICE -> stdout
echo $output;
}
}
/**
* Minimales Log-Level setzen
*/
public function setMinLevel(LogLevel|int $level): self
{
$this->minLevel = $level instanceof LogLevel ? $level : LogLevel::fromValue($level);
return $this;
}
/**
* Ausgabeformat setzen
*/
public function setOutputFormat(string $format): self
{
$this->outputFormat = $format;
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);
}
}