feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,306 @@
<?php
declare(strict_types=1);
use App\Framework\LiveComponents\Observability\ComponentMetricsCollector;
use App\Framework\Performance\PerformanceCollector;
describe('ComponentMetricsCollector', function () {
it('records component render with metrics', function () {
$collector = new ComponentMetricsCollector();
$collector->recordRender('test-component-123', 45.5, false);
$metrics = $collector->getMetrics();
expect(count($metrics))->toBeGreaterThan(0);
expect(isset($metrics['livecomponent_renders_total{component_id=test-component-123,cached=false}']))->toBeTrue();
});
it('records cached vs non-cached renders separately', function () {
$collector = new ComponentMetricsCollector();
$collector->recordRender('comp-1', 45.5, false); // non-cached
$collector->recordRender('comp-2', 10.2, true); // cached
$metrics = $collector->getMetrics();
// Should have separate metrics for cached and non-cached
$renderMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_renders_total')
);
expect(count($renderMetrics))->toBeGreaterThanOrEqual(1);
});
it('records action execution with duration', function () {
$collector = new ComponentMetricsCollector();
$collector->recordAction('comp-123', 'handleClick', 25.5, true);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_actions_total');
expect($metrics['livecomponent_actions_total']->value)->toBe(1.0);
});
it('tracks action errors separately', function () {
$collector = new ComponentMetricsCollector();
$collector->recordAction('comp-123', 'handleClick', 25.5, false);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_action_errors_total');
expect($metrics['livecomponent_action_errors_total']->value)->toBe(1.0);
});
it('records cache hits and misses', function () {
$collector = new ComponentMetricsCollector();
$collector->recordCacheHit('comp-1', true);
$collector->recordCacheHit('comp-2', false);
$collector->recordCacheHit('comp-3', true);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_cache_hits_total');
expect($metrics)->toHaveKey('livecomponent_cache_misses_total');
expect($metrics['livecomponent_cache_hits_total']->value)->toBe(2.0);
expect($metrics['livecomponent_cache_misses_total']->value)->toBe(1.0);
});
it('records event dispatching', function () {
$collector = new ComponentMetricsCollector();
$collector->recordEventDispatched('comp-1', 'user:updated');
$collector->recordEventDispatched('comp-2', 'data:loaded');
$metrics = $collector->getMetrics();
$eventMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_events_dispatched_total')
);
expect(count($eventMetrics))->toBeGreaterThanOrEqual(1);
});
it('records event receiving', function () {
$collector = new ComponentMetricsCollector();
$collector->recordEventReceived('comp-1', 'user:updated');
$collector->recordEventReceived('comp-2', 'data:loaded');
$metrics = $collector->getMetrics();
$eventMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_events_received_total')
);
expect(count($eventMetrics))->toBeGreaterThanOrEqual(1);
});
it('records hydration time', function () {
$collector = new ComponentMetricsCollector();
$collector->recordHydration('comp-123', 15.5);
$metrics = $collector->getMetrics();
$hydrationMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_hydration_duration_ms')
);
expect(count($hydrationMetrics))->toBeGreaterThanOrEqual(1);
});
it('records batch operations', function () {
$collector = new ComponentMetricsCollector();
$collector->recordBatch(10, 125.5, 8, 2);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_batch_operations_total');
expect($metrics)->toHaveKey('livecomponent_batch_success_total');
expect($metrics)->toHaveKey('livecomponent_batch_failure_total');
expect($metrics['livecomponent_batch_success_total']->value)->toBe(8.0);
expect($metrics['livecomponent_batch_failure_total']->value)->toBe(2.0);
});
it('records fragment updates', function () {
$collector = new ComponentMetricsCollector();
$collector->recordFragmentUpdate('comp-123', 3, 45.5);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_fragment_updates_total');
});
it('records upload chunks', function () {
$collector = new ComponentMetricsCollector();
$collector->recordUploadChunk('session-abc', 0, 125.5, true);
$collector->recordUploadChunk('session-abc', 1, 130.2, true);
$collector->recordUploadChunk('session-abc', 2, 128.8, false);
$metrics = $collector->getMetrics();
$uploadMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_upload_chunks_total')
);
expect(count($uploadMetrics))->toBeGreaterThanOrEqual(1);
});
it('records upload completion', function () {
$collector = new ComponentMetricsCollector();
$collector->recordUploadComplete('session-abc', 1500.5, 5);
$metrics = $collector->getMetrics();
expect($metrics)->toHaveKey('livecomponent_uploads_completed_total');
});
it('calculates summary statistics', function () {
$collector = new ComponentMetricsCollector();
// Record various operations
$collector->recordRender('comp-1', 45.5, false);
$collector->recordRender('comp-2', 30.2, true);
$collector->recordAction('comp-1', 'handleClick', 25.5, true);
$collector->recordAction('comp-2', 'handleSubmit', 35.8, false);
$collector->recordCacheHit('comp-1', true);
$collector->recordCacheHit('comp-2', false);
$collector->recordCacheHit('comp-3', true);
$summary = $collector->getSummary();
expect($summary)->toHaveKey('total_renders');
expect($summary)->toHaveKey('total_actions');
expect($summary)->toHaveKey('cache_hits');
expect($summary)->toHaveKey('cache_misses');
expect($summary)->toHaveKey('cache_hit_rate');
expect($summary)->toHaveKey('action_errors');
expect($summary['total_renders'])->toBe(2);
expect($summary['total_actions'])->toBe(2);
expect($summary['cache_hits'])->toBe(2);
expect($summary['cache_misses'])->toBe(1);
expect($summary['action_errors'])->toBe(1);
expect($summary['cache_hit_rate'])->toBeGreaterThan(60.0);
});
it('exports metrics in Prometheus format', function () {
$collector = new ComponentMetricsCollector();
$collector->recordRender('comp-1', 45.5, false);
$collector->recordAction('comp-1', 'handleClick', 25.5, true);
$prometheus = $collector->exportPrometheus();
expect($prometheus)->toBeString();
expect($prometheus)->toContain('# HELP LiveComponents metrics');
expect($prometheus)->toContain('livecomponent_');
});
it('resets all metrics', function () {
$collector = new ComponentMetricsCollector();
$collector->recordRender('comp-1', 45.5, false);
$collector->recordAction('comp-1', 'handleClick', 25.5, true);
expect($collector->getMetrics())->not->toBeEmpty();
$collector->reset();
expect($collector->getMetrics())->toBeEmpty();
});
it('integrates with PerformanceCollector', function () {
$performanceCollector = $this->createMock(PerformanceCollector::class);
$performanceCollector->expects($this->once())
->method('recordMetric')
->with(
$this->stringContains('livecomponent.render'),
$this->anything(),
$this->equalTo(45.5),
$this->anything()
);
$collector = new ComponentMetricsCollector($performanceCollector);
$collector->recordRender('comp-1', 45.5, false);
});
it('tracks multiple components independently', function () {
$collector = new ComponentMetricsCollector();
// Component 1: 3 renders, 2 actions
$collector->recordRender('comp-1', 45.5, false);
$collector->recordRender('comp-1', 40.2, false);
$collector->recordRender('comp-1', 35.8, false);
$collector->recordAction('comp-1', 'handleClick', 25.5, true);
$collector->recordAction('comp-1', 'handleSubmit', 30.2, true);
// Component 2: 2 renders, 1 action
$collector->recordRender('comp-2', 20.5, true);
$collector->recordRender('comp-2', 18.2, true);
$collector->recordAction('comp-2', 'handleClick', 15.5, true);
$metrics = $collector->getMetrics();
// Should have metrics for both components
$renderMetrics = array_filter(
$metrics,
fn($m) => str_contains($m->name, 'livecomponent_renders_total')
);
expect(count($renderMetrics))->toBeGreaterThanOrEqual(1);
});
it('handles edge case with zero operations', function () {
$collector = new ComponentMetricsCollector();
$summary = $collector->getSummary();
expect($summary['total_renders'])->toBe(0);
expect($summary['total_actions'])->toBe(0);
expect($summary['cache_hit_rate'])->toBe(0.0);
});
it('handles edge case with all cache misses', function () {
$collector = new ComponentMetricsCollector();
$collector->recordCacheHit('comp-1', false);
$collector->recordCacheHit('comp-2', false);
$collector->recordCacheHit('comp-3', false);
$summary = $collector->getSummary();
expect($summary['cache_hits'])->toBe(0);
expect($summary['cache_misses'])->toBe(3);
expect($summary['cache_hit_rate'])->toBe(0.0);
});
it('handles edge case with all cache hits', function () {
$collector = new ComponentMetricsCollector();
$collector->recordCacheHit('comp-1', true);
$collector->recordCacheHit('comp-2', true);
$collector->recordCacheHit('comp-3', true);
$summary = $collector->getSummary();
expect($summary['cache_hits'])->toBe(3);
expect($summary['cache_misses'])->toBe(0);
expect($summary['cache_hit_rate'])->toBe(100.0);
});
});