Files
michaelschiemer/src/Framework/ErrorReporting/ErrorReportingConfig.php

232 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\ErrorReporting;
use App\Framework\Config\Environment;
use App\Framework\Config\EnvironmentType;
use App\Framework\Config\EnvKey;
/**
* Error Reporting Configuration that adapts to environment
*
* Provides type-safe configuration for error reporting system
* with environment-specific defaults (dev/staging/production).
*
* Features:
* - Environment-based factory methods
* - Type-safe configuration properties
* - Framework-compliant readonly Value Object
* - Sensible defaults per environment
*/
final readonly class ErrorReportingConfig
{
/**
* @param bool $enabled Enable/disable error reporting globally
* @param bool $asyncProcessing Process error reports asynchronously via queue
* @param string[] $filterLevels Only report these log levels (empty = all levels)
* @param string[] $excludedExceptionTypes Exception types to exclude from reporting
* @param bool $captureRequestContext Capture HTTP request context
* @param bool $captureUserContext Capture user/session context
* @param bool $captureStackTraces Include full stack traces in reports
* @param int $maxStackTraceDepth Maximum stack trace depth
* @param bool $sanitizeSensitiveData Sanitize passwords, tokens, etc.
* @param int $samplingRate Sampling rate for high-volume errors (1-100, 100 = all)
* @param int $maxReportsPerMinute Rate limit for error reports
* @param bool $enableAnalytics Enable error analytics and anomaly detection
* @param int $analyticsRetentionDays Days to retain error reports for analytics
*/
public function __construct(
public bool $enabled = true,
public bool $asyncProcessing = true,
public array $filterLevels = [],
public array $excludedExceptionTypes = [],
public bool $captureRequestContext = true,
public bool $captureUserContext = true,
public bool $captureStackTraces = true,
public int $maxStackTraceDepth = 20,
public bool $sanitizeSensitiveData = true,
public int $samplingRate = 100,
public int $maxReportsPerMinute = 60,
public bool $enableAnalytics = true,
public int $analyticsRetentionDays = 30
) {
// Validation
if ($samplingRate < 1 || $samplingRate > 100) {
throw new \InvalidArgumentException('Sampling rate must be between 1 and 100');
}
if ($maxStackTraceDepth < 1) {
throw new \InvalidArgumentException('Max stack trace depth must be at least 1');
}
if ($analyticsRetentionDays < 1) {
throw new \InvalidArgumentException('Analytics retention days must be at least 1');
}
}
/**
* Create configuration from environment
*/
public static function fromEnvironment(Environment $env): self
{
$appEnv = $env->getString(EnvKey::APP_ENV, 'production');
$environmentType = match (strtolower($appEnv)) {
'production', 'prod' => EnvironmentType::PROD,
'staging', 'stage' => EnvironmentType::STAGING,
'development', 'dev', 'local' => EnvironmentType::DEV,
default => EnvironmentType::PROD
};
return self::forEnvironment($environmentType, $env);
}
/**
* Create environment-specific configuration
*/
public static function forEnvironment(EnvironmentType $environment, Environment $env): self
{
return match ($environment) {
EnvironmentType::PROD => self::production($env),
EnvironmentType::STAGING => self::staging($env),
EnvironmentType::DEV => self::development($env),
};
}
/**
* Production configuration - strict, sanitized, sampled
*/
private static function production(Environment $env): self
{
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
$excludedTypes = $env->getString('ERROR_REPORTING_EXCLUDED_TYPES', '');
// Production default: only error, critical, alert, emergency
$defaultFilterLevels = ['error', 'critical', 'alert', 'emergency'];
return new self(
enabled: $env->getBool('ERROR_REPORTING_ENABLED', true),
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
filterLevels: $filterLevels !== ''
? explode(',', $filterLevels)
: $defaultFilterLevels,
excludedExceptionTypes: $excludedTypes !== ''
? explode(',', $excludedTypes)
: [],
captureRequestContext: $env->getBool('ERROR_REPORTING_CAPTURE_REQUEST', true),
captureUserContext: $env->getBool('ERROR_REPORTING_CAPTURE_USER', true),
captureStackTraces: $env->getBool('ERROR_REPORTING_CAPTURE_STACK_TRACES', true),
maxStackTraceDepth: $env->getInt('ERROR_REPORTING_MAX_STACK_DEPTH', 15),
sanitizeSensitiveData: $env->getBool('ERROR_REPORTING_SANITIZE', true),
samplingRate: $env->getInt('ERROR_REPORTING_SAMPLING_RATE', 100),
maxReportsPerMinute: $env->getInt('ERROR_REPORTING_MAX_PER_MINUTE', 30),
enableAnalytics: $env->getBool('ERROR_REPORTING_ANALYTICS', true),
analyticsRetentionDays: $env->getInt('ERROR_REPORTING_RETENTION_DAYS', 30)
);
}
/**
* Staging configuration - more verbose, less restrictive
*/
private static function staging(Environment $env): self
{
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
$excludedTypes = $env->getString('ERROR_REPORTING_EXCLUDED_TYPES', '');
// Staging default: warning and above
$defaultFilterLevels = ['warning', 'error', 'critical', 'alert', 'emergency'];
return new self(
enabled: $env->getBool('ERROR_REPORTING_ENABLED', true),
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
filterLevels: $filterLevels !== ''
? explode(',', $filterLevels)
: $defaultFilterLevels,
excludedExceptionTypes: $excludedTypes !== ''
? explode(',', $excludedTypes)
: [],
captureRequestContext: $env->getBool('ERROR_REPORTING_CAPTURE_REQUEST', true),
captureUserContext: $env->getBool('ERROR_REPORTING_CAPTURE_USER', true),
captureStackTraces: $env->getBool('ERROR_REPORTING_CAPTURE_STACK_TRACES', true),
maxStackTraceDepth: $env->getInt('ERROR_REPORTING_MAX_STACK_DEPTH', 20),
sanitizeSensitiveData: $env->getBool('ERROR_REPORTING_SANITIZE', true),
samplingRate: $env->getInt('ERROR_REPORTING_SAMPLING_RATE', 100),
maxReportsPerMinute: $env->getInt('ERROR_REPORTING_MAX_PER_MINUTE', 60),
enableAnalytics: $env->getBool('ERROR_REPORTING_ANALYTICS', true),
analyticsRetentionDays: $env->getInt('ERROR_REPORTING_RETENTION_DAYS', 14)
);
}
/**
* Development configuration - verbose, everything enabled
*/
private static function development(Environment $env): self
{
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
$excludedTypes = $env->getString('ERROR_REPORTING_EXCLUDED_TYPES', '');
return new self(
enabled: $env->getBool('ERROR_REPORTING_ENABLED', true),
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', false), // Sync in dev for debugging
filterLevels: $filterLevels !== '' ? explode(',', $filterLevels) : [], // All levels
excludedExceptionTypes: $excludedTypes !== '' ? explode(',', $excludedTypes) : [],
captureRequestContext: $env->getBool('ERROR_REPORTING_CAPTURE_REQUEST', true),
captureUserContext: $env->getBool('ERROR_REPORTING_CAPTURE_USER', true),
captureStackTraces: $env->getBool('ERROR_REPORTING_CAPTURE_STACK_TRACES', true),
maxStackTraceDepth: $env->getInt('ERROR_REPORTING_MAX_STACK_DEPTH', 30), // Deep traces
sanitizeSensitiveData: $env->getBool('ERROR_REPORTING_SANITIZE', false), // See real data
samplingRate: $env->getInt('ERROR_REPORTING_SAMPLING_RATE', 100), // All errors
maxReportsPerMinute: $env->getInt('ERROR_REPORTING_MAX_PER_MINUTE', 1000), // No limit
enableAnalytics: $env->getBool('ERROR_REPORTING_ANALYTICS', true),
analyticsRetentionDays: $env->getInt('ERROR_REPORTING_RETENTION_DAYS', 7)
);
}
/**
* Check if error level should be reported
*/
public function shouldReportLevel(string $level): bool
{
// Empty filter = all levels
if (empty($this->filterLevels)) {
return true;
}
return in_array(strtolower($level), array_map('strtolower', $this->filterLevels), true);
}
/**
* Check if exception type should be reported
*/
public function shouldReportException(\Throwable $exception): bool
{
if (empty($this->excludedExceptionTypes)) {
return true;
}
$exceptionClass = get_class($exception);
foreach ($this->excludedExceptionTypes as $excludedType) {
if ($exceptionClass === $excludedType || is_subclass_of($exceptionClass, $excludedType)) {
return false;
}
}
return true;
}
/**
* Check if error should be sampled (for high-volume scenarios)
*/
public function shouldSample(): bool
{
if ($this->samplingRate === 100) {
return true;
}
return random_int(1, 100) <= $this->samplingRate;
}
}