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; } }