- Add initializer count logging in DiscoveryServiceBootstrapper - Add route structure analysis in RouterSetup - Add request parameter logging in HttpRouter - Update PHP production config for better OPcache handling - Fix various config and error handling improvements
170 lines
6.8 KiB
PHP
170 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\ErrorReporting;
|
|
|
|
use App\Framework\Config\Environment;
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\DateTime\Clock;
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\DI\Initializer;
|
|
use App\Framework\ErrorReporting\Analytics\ErrorAnalyticsEngine;
|
|
use App\Framework\ErrorReporting\Processors\RequestContextProcessor;
|
|
use App\Framework\ErrorReporting\Processors\UserContextProcessor;
|
|
use App\Framework\ErrorReporting\Storage\DatabaseErrorReportStorage;
|
|
use App\Framework\ErrorReporting\Storage\ErrorReportStorageInterface;
|
|
use App\Framework\Http\Session\Session;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Queue\Queue;
|
|
|
|
/**
|
|
* Initializer for Error Reporting system
|
|
*/
|
|
final readonly class ErrorReportingInitializer
|
|
{
|
|
#[Initializer]
|
|
public function initialize(Container $container): void
|
|
{
|
|
$env = $container->get(Environment::class);
|
|
$enabled = $env->getBool('ERROR_REPORTING_ENABLED', true);
|
|
|
|
// Storage
|
|
$container->bind(ErrorReportStorageInterface::class, function (Container $container) use ($enabled) {
|
|
if (! $enabled) {
|
|
// Return storage even if disabled (might be used for queries)
|
|
return new DatabaseErrorReportStorage(
|
|
connection: $container->get(ConnectionInterface::class)
|
|
);
|
|
}
|
|
|
|
return new DatabaseErrorReportStorage(
|
|
connection: $container->get(ConnectionInterface::class)
|
|
);
|
|
});
|
|
|
|
// Analytics Engine
|
|
$container->bind(ErrorAnalyticsEngine::class, function (Container $container) {
|
|
return new ErrorAnalyticsEngine(
|
|
storage: $container->get(ErrorReportStorageInterface::class),
|
|
clock: $container->get(Clock::class)
|
|
);
|
|
});
|
|
|
|
// Error Reporter Interface - bind to concrete or Null implementation
|
|
$container->bind(ErrorReporterInterface::class, function (Container $container) use ($enabled) {
|
|
if (! $enabled) {
|
|
return new NullErrorReporter();
|
|
}
|
|
|
|
$env = $container->get(Environment::class);
|
|
$processors = [];
|
|
$filters = [];
|
|
|
|
// Add built-in processors
|
|
if ($container->has(RequestContextProcessor::class)) {
|
|
$processors[] = $container->get(RequestContextProcessor::class);
|
|
}
|
|
|
|
if ($container->has(UserContextProcessor::class)) {
|
|
$processors[] = $container->get(UserContextProcessor::class);
|
|
}
|
|
|
|
// Add environment-based filters
|
|
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
|
|
if ($filterLevels !== '') {
|
|
$allowedLevels = explode(',', $filterLevels);
|
|
$filters[] = function (ErrorReport $report) use ($allowedLevels) {
|
|
return in_array($report->level, $allowedLevels);
|
|
};
|
|
}
|
|
|
|
// Add environment filter for production
|
|
$appEnv = $env->getString('APP_ENV', 'production');
|
|
if ($appEnv === 'production') {
|
|
$filters[] = function (ErrorReport $report) {
|
|
// Don't report debug/info in production
|
|
return ! in_array($report->level, ['debug', 'info']);
|
|
};
|
|
}
|
|
|
|
return new ErrorReporter(
|
|
storage: $container->get(ErrorReportStorageInterface::class),
|
|
clock: $container->get(Clock::class),
|
|
logger: $container->has(Logger::class) ? $container->get(Logger::class) : null,
|
|
queue: $container->has(Queue::class) ? $container->get(Queue::class) : null,
|
|
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
|
|
processors: $processors,
|
|
filters: $filters
|
|
);
|
|
});
|
|
|
|
// Error Reporter (concrete class) - delegate to interface
|
|
$container->bind(ErrorReporter::class, function (Container $container) use ($enabled) {
|
|
if (! $enabled) {
|
|
throw new \RuntimeException('ErrorReporter is disabled. Use ErrorReporterInterface instead.');
|
|
}
|
|
|
|
$env = $container->get(Environment::class);
|
|
$processors = [];
|
|
$filters = [];
|
|
|
|
// Add built-in processors
|
|
if ($container->has(RequestContextProcessor::class)) {
|
|
$processors[] = $container->get(RequestContextProcessor::class);
|
|
}
|
|
|
|
if ($container->has(UserContextProcessor::class)) {
|
|
$processors[] = $container->get(UserContextProcessor::class);
|
|
}
|
|
|
|
// Add environment-based filters
|
|
$filterLevels = $env->getString('ERROR_REPORTING_FILTER_LEVELS', '');
|
|
if ($filterLevels !== '') {
|
|
$allowedLevels = explode(',', $filterLevels);
|
|
$filters[] = function (ErrorReport $report) use ($allowedLevels) {
|
|
return in_array($report->level, $allowedLevels);
|
|
};
|
|
}
|
|
|
|
// Add environment filter for production
|
|
$appEnv = $env->getString('APP_ENV', 'production');
|
|
if ($appEnv === 'production') {
|
|
$filters[] = function (ErrorReport $report) {
|
|
// Don't report debug/info in production
|
|
return ! in_array($report->level, ['debug', 'info']);
|
|
};
|
|
}
|
|
|
|
return new ErrorReporter(
|
|
storage: $container->get(ErrorReportStorageInterface::class),
|
|
clock: $container->get(Clock::class),
|
|
logger: $container->has(Logger::class) ? $container->get(Logger::class) : null,
|
|
queue: $container->has(Queue::class) ? $container->get(Queue::class) : null,
|
|
asyncProcessing: $env->getBool('ERROR_REPORTING_ASYNC', true),
|
|
processors: $processors,
|
|
filters: $filters
|
|
);
|
|
});
|
|
|
|
// Middleware
|
|
$container->bind(ErrorReportingMiddleware::class, function (Container $container) use ($enabled) {
|
|
return new ErrorReportingMiddleware(
|
|
reporter: $container->get(ErrorReporter::class),
|
|
enabled: $enabled
|
|
);
|
|
});
|
|
|
|
// Processors
|
|
$container->bind(RequestContextProcessor::class, function (Container $container) {
|
|
return new RequestContextProcessor();
|
|
});
|
|
|
|
$container->bind(UserContextProcessor::class, function (Container $container) {
|
|
// Session is not a singleton service - it's created per-request by SessionManager
|
|
// Don't try to resolve it here as it may not exist or trigger circular dependencies
|
|
return new UserContextProcessor(null);
|
|
});
|
|
}
|
|
}
|