- 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.
1164 lines
30 KiB
Markdown
1164 lines
30 KiB
Markdown
# LiveComponents DevTools & Debugging Guide
|
|
|
|
Umfassender Leitfaden für Debugging und Development Tools für LiveComponents.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Debug Mode](#debug-mode)
|
|
2. [Component State Inspection](#component-state-inspection)
|
|
3. [Request/Response Debugging](#requestresponse-debugging)
|
|
4. [Performance Profiling](#performance-profiling)
|
|
5. [Common Debugging Scenarios](#common-debugging-scenarios)
|
|
6. [Browser DevTools Integration](#browser-devtools-integration)
|
|
7. [Server-Side Logging](#server-side-logging)
|
|
8. [Testing & Development Helpers](#testing--development-helpers)
|
|
9. [Troubleshooting Workflows](#troubleshooting-workflows)
|
|
10. [Production Debugging](#production-debugging)
|
|
|
|
---
|
|
|
|
## Debug Mode
|
|
|
|
### Enabling Debug Mode
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Config\LiveComponentsConfig;
|
|
|
|
// In your configuration (e.g., config/livecomponents.php)
|
|
return new LiveComponentsConfig(
|
|
debug: true, // Enable debug mode
|
|
enableDevTools: true, // Enable DevTools helpers
|
|
logLevel: 'debug' // Verbose logging
|
|
);
|
|
```
|
|
|
|
### Debug Mode Features
|
|
|
|
**Automatic Features When Enabled:**
|
|
|
|
1. **Verbose Logging**: All component lifecycle events logged
|
|
2. **State Snapshots**: Before/after state changes captured
|
|
3. **Performance Metrics**: Render times, action execution times
|
|
4. **Error Context**: Enhanced error messages with full state dump
|
|
5. **Request/Response Logging**: All AJAX requests/responses logged
|
|
|
|
### Environment-Based Configuration
|
|
|
|
```php
|
|
// config/livecomponents.php
|
|
use App\Framework\Core\Environment;
|
|
use App\Framework\Config\EnvKey;
|
|
|
|
return new LiveComponentsConfig(
|
|
debug: $container->get(Environment::class)->get(EnvKey::APP_ENV) === 'development',
|
|
enableDevTools: $container->get(Environment::class)->get(EnvKey::APP_ENV) !== 'production',
|
|
logLevel: match ($container->get(Environment::class)->get(EnvKey::APP_ENV)) {
|
|
'production' => 'error',
|
|
'staging' => 'warning',
|
|
'development' => 'debug',
|
|
default => 'info'
|
|
}
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Component State Inspection
|
|
|
|
### Server-Side State Inspection
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Debug\StateInspector;
|
|
|
|
final readonly class ShoppingCartComponent implements LiveComponentContract
|
|
{
|
|
public function __construct(
|
|
private StateInspector $stateInspector // Injected when debug=true
|
|
) {}
|
|
|
|
#[Action]
|
|
public function addItem(CartItem $item): ShoppingCartState
|
|
{
|
|
// Capture state before action
|
|
$this->stateInspector->captureSnapshot('before_add_item', $this->state);
|
|
|
|
$newState = $this->state->addItem($item);
|
|
|
|
// Capture state after action
|
|
$this->stateInspector->captureSnapshot('after_add_item', $newState);
|
|
|
|
// Analyze state diff
|
|
$diff = $this->stateInspector->diff($this->state, $newState);
|
|
$this->stateInspector->log('State changed', [
|
|
'action' => 'addItem',
|
|
'changes' => $diff,
|
|
'item_count_before' => count($this->state->items),
|
|
'item_count_after' => count($newState->items)
|
|
]);
|
|
|
|
return $newState;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Client-Side State Inspection
|
|
|
|
```javascript
|
|
// Access component state from browser console
|
|
const component = window.LiveComponents.get('shopping-cart:main');
|
|
|
|
// Current state
|
|
console.log('Current State:', component.state);
|
|
|
|
// State history (when debug=true)
|
|
console.log('State History:', component._debugStateHistory);
|
|
|
|
// Last action result
|
|
console.log('Last Action:', component._lastAction);
|
|
|
|
// Component metadata
|
|
console.log('Component Info:', {
|
|
id: component.id,
|
|
name: component.name,
|
|
version: component.version,
|
|
renderCount: component._renderCount,
|
|
actionCount: component._actionCount
|
|
});
|
|
```
|
|
|
|
### State Snapshots
|
|
|
|
```php
|
|
// Automatic snapshots during development
|
|
final readonly class StateSnapshot
|
|
{
|
|
public function __construct(
|
|
public string $label,
|
|
public ComponentData $state,
|
|
public float $timestamp,
|
|
public ?string $triggeredBy = null, // Action name or event
|
|
public ?array $metadata = null
|
|
) {}
|
|
}
|
|
|
|
// Usage
|
|
$inspector->captureSnapshot('cart_loaded', $this->state, metadata: [
|
|
'user_id' => $userId,
|
|
'session_id' => $sessionId,
|
|
'item_count' => count($this->state->items)
|
|
]);
|
|
|
|
// Retrieve snapshots
|
|
$snapshots = $inspector->getSnapshots($componentId);
|
|
|
|
// Compare snapshots
|
|
$diff = $inspector->compareSnapshots($snapshot1, $snapshot2);
|
|
```
|
|
|
|
---
|
|
|
|
## Request/Response Debugging
|
|
|
|
### Server-Side Request Logging
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Debug\RequestLogger;
|
|
|
|
final readonly class LiveComponentsRequestLogger
|
|
{
|
|
public function logRequest(
|
|
string $componentId,
|
|
string $action,
|
|
array $parameters,
|
|
ComponentData $stateBefore,
|
|
ComponentData $stateAfter,
|
|
float $executionTime
|
|
): void {
|
|
$this->logger->debug('LiveComponent Action', [
|
|
'component_id' => $componentId,
|
|
'action' => $action,
|
|
'parameters' => $parameters,
|
|
'execution_time_ms' => round($executionTime * 1000, 2),
|
|
'state_diff' => $this->calculateDiff($stateBefore, $stateAfter),
|
|
'timestamp' => microtime(true)
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Client-Side Network Debugging
|
|
|
|
```javascript
|
|
// Enable detailed network logging
|
|
window.LiveComponents.setDebug(true);
|
|
|
|
// Network request interceptor (when debug=true)
|
|
window.LiveComponents.onRequest((request) => {
|
|
console.group(`📤 LiveComponent Request: ${request.action}`);
|
|
console.log('Component:', request.componentId);
|
|
console.log('Action:', request.action);
|
|
console.log('Parameters:', request.parameters);
|
|
console.log('State Hash:', request.stateHash);
|
|
console.groupEnd();
|
|
});
|
|
|
|
window.LiveComponents.onResponse((response) => {
|
|
console.group(`📥 LiveComponent Response: ${response.action}`);
|
|
console.log('Duration:', `${response.duration}ms`);
|
|
console.log('State Updated:', response.stateChanged);
|
|
console.log('Fragments:', response.fragments?.length || 0);
|
|
console.log('Errors:', response.errors || 'None');
|
|
console.groupEnd();
|
|
});
|
|
|
|
window.LiveComponents.onError((error) => {
|
|
console.group(`❌ LiveComponent Error: ${error.action}`);
|
|
console.error('Message:', error.message);
|
|
console.error('Component:', error.componentId);
|
|
console.error('Stack:', error.stack);
|
|
console.groupEnd();
|
|
});
|
|
```
|
|
|
|
### Browser DevTools Network Panel
|
|
|
|
**Request Inspection:**
|
|
|
|
```
|
|
POST /livecomponent/action
|
|
Headers:
|
|
X-LiveComponent: true
|
|
X-Component-Id: shopping-cart:main
|
|
X-Action: addItem
|
|
X-State-Hash: a1b2c3d4e5f6
|
|
|
|
Request Payload:
|
|
{
|
|
"item": {
|
|
"id": "prod-123",
|
|
"name": "Product Name",
|
|
"price": 2999,
|
|
"quantity": 1
|
|
}
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"success": true,
|
|
"stateHash": "f6e5d4c3b2a1",
|
|
"fragments": [
|
|
{
|
|
"selector": "#cart-items",
|
|
"html": "<div class='cart-item'>...</div>"
|
|
},
|
|
{
|
|
"selector": "#cart-total",
|
|
"html": "<span>€29.99</span>"
|
|
}
|
|
],
|
|
"meta": {
|
|
"executionTime": 23.45,
|
|
"cacheHit": false,
|
|
"fragmentCount": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Profiling
|
|
|
|
### Server-Side Performance Metrics
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Performance\ComponentProfiler;
|
|
|
|
final readonly class PerformanceProfiler
|
|
{
|
|
public function profileAction(
|
|
string $componentId,
|
|
string $action,
|
|
callable $actionHandler
|
|
): ProfilingResult {
|
|
$startTime = microtime(true);
|
|
$startMemory = memory_get_usage(true);
|
|
|
|
// Execute action
|
|
$result = $actionHandler();
|
|
|
|
$endTime = microtime(true);
|
|
$endMemory = memory_get_usage(true);
|
|
|
|
return new ProfilingResult(
|
|
componentId: $componentId,
|
|
action: $action,
|
|
executionTime: Duration::fromSeconds($endTime - $startTime),
|
|
memoryUsed: $endMemory - $startMemory,
|
|
peakMemory: memory_get_peak_usage(true),
|
|
result: $result
|
|
);
|
|
}
|
|
}
|
|
|
|
// Usage in component
|
|
#[Action]
|
|
public function processPayment(PaymentRequest $request): ComponentData
|
|
{
|
|
return $this->profiler->profileAction(
|
|
$this->id->toString(),
|
|
'processPayment',
|
|
fn() => $this->executePayment($request)
|
|
)->result;
|
|
}
|
|
```
|
|
|
|
### Component Lifecycle Profiling
|
|
|
|
```php
|
|
final readonly class LifecycleProfiler
|
|
{
|
|
public function profileRender(LiveComponentContract $component): RenderProfile
|
|
{
|
|
$metrics = [
|
|
'template_load' => $this->measureTemplateLoad($component),
|
|
'state_serialization' => $this->measureStateSerialization($component),
|
|
'html_generation' => $this->measureHtmlGeneration($component),
|
|
'dom_manipulation' => $this->measureDomManipulation($component)
|
|
];
|
|
|
|
return new RenderProfile(
|
|
componentId: $component->id,
|
|
totalTime: array_sum($metrics),
|
|
breakdown: $metrics,
|
|
cacheHit: $this->wasCached($component)
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Client-Side Performance Metrics
|
|
|
|
```javascript
|
|
// Performance API integration
|
|
const performanceObserver = new PerformanceObserver((list) => {
|
|
for (const entry of list.getEntries()) {
|
|
if (entry.name.startsWith('livecomponent:')) {
|
|
console.log('Performance Entry:', {
|
|
name: entry.name,
|
|
duration: `${entry.duration.toFixed(2)}ms`,
|
|
startTime: entry.startTime,
|
|
type: entry.entryType
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
performanceObserver.observe({ entryTypes: ['measure'] });
|
|
|
|
// Measure action execution
|
|
performance.mark('livecomponent:action:start');
|
|
await component.call('addItem', { item });
|
|
performance.mark('livecomponent:action:end');
|
|
|
|
performance.measure(
|
|
'livecomponent:action:addItem',
|
|
'livecomponent:action:start',
|
|
'livecomponent:action:end'
|
|
);
|
|
```
|
|
|
|
### Render Performance Tracking
|
|
|
|
```javascript
|
|
// Track rendering performance
|
|
class RenderPerformanceTracker {
|
|
trackRender(componentId, metrics) {
|
|
const entry = {
|
|
componentId,
|
|
timestamp: Date.now(),
|
|
domPatchTime: metrics.domPatchTime,
|
|
fragmentCount: metrics.fragmentCount,
|
|
bytesReceived: metrics.bytesReceived,
|
|
cacheHit: metrics.cacheHit
|
|
};
|
|
|
|
// Store in IndexedDB for analysis
|
|
this.storeMetric(entry);
|
|
|
|
// Warn on slow renders
|
|
if (metrics.domPatchTime > 100) {
|
|
console.warn(`Slow render detected: ${componentId}`, metrics);
|
|
}
|
|
}
|
|
|
|
getAverageRenderTime(componentId) {
|
|
const entries = this.getMetrics(componentId);
|
|
return entries.reduce((sum, e) => sum + e.domPatchTime, 0) / entries.length;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Common Debugging Scenarios
|
|
|
|
### Scenario 1: Action Not Triggering
|
|
|
|
**Symptoms:** Button click doesn't trigger action
|
|
|
|
**Debug Steps:**
|
|
|
|
```javascript
|
|
// 1. Check if component is registered
|
|
const component = window.LiveComponents.get('shopping-cart:main');
|
|
console.log('Component exists:', !!component);
|
|
|
|
// 2. Check action attribute
|
|
const button = document.querySelector('[data-action="addItem"]');
|
|
console.log('Action attribute:', button?.dataset.action);
|
|
console.log('Component ID attribute:', button?.dataset.componentId);
|
|
|
|
// 3. Check event listeners
|
|
console.log('Event listeners:', getEventListeners(button));
|
|
|
|
// 4. Enable verbose logging
|
|
window.LiveComponents.setDebug(true);
|
|
|
|
// 5. Try manual action call
|
|
component.call('addItem', { item: testItem })
|
|
.then(result => console.log('Manual call success:', result))
|
|
.catch(error => console.error('Manual call failed:', error));
|
|
```
|
|
|
|
**Common Fixes:**
|
|
- Missing `data-component-id` attribute
|
|
- Incorrect action name (case-sensitive)
|
|
- Component not initialized (check initialization order)
|
|
- JavaScript errors preventing event binding
|
|
|
|
### Scenario 2: State Not Updating
|
|
|
|
**Symptoms:** Server returns success but UI doesn't update
|
|
|
|
**Debug Steps:**
|
|
|
|
```php
|
|
// Server-side: Verify state change
|
|
#[Action]
|
|
public function addItem(CartItem $item): ShoppingCartState
|
|
{
|
|
$oldState = $this->state;
|
|
$newState = $this->state->addItem($item);
|
|
|
|
// Debug logging
|
|
error_log("Old item count: " . count($oldState->items));
|
|
error_log("New item count: " . count($newState->items));
|
|
error_log("State changed: " . ($oldState !== $newState ? 'yes' : 'no'));
|
|
|
|
return $newState;
|
|
}
|
|
```
|
|
|
|
```javascript
|
|
// Client-side: Track state updates
|
|
window.LiveComponents.onStateChange((event) => {
|
|
console.log('State changed:', {
|
|
componentId: event.componentId,
|
|
oldHash: event.oldHash,
|
|
newHash: event.newHash,
|
|
changed: event.oldHash !== event.newHash
|
|
});
|
|
});
|
|
```
|
|
|
|
**Common Fixes:**
|
|
- State object not immutable (modifying instead of returning new instance)
|
|
- Missing fragments in response
|
|
- DomPatcher not applying changes (check selectors)
|
|
- Cache preventing updates (clear cache)
|
|
|
|
### Scenario 3: Memory Leaks
|
|
|
|
**Symptoms:** Browser memory grows over time
|
|
|
|
**Debug Steps:**
|
|
|
|
```javascript
|
|
// Heap snapshot comparison
|
|
// 1. Take baseline snapshot
|
|
console.log('Taking baseline snapshot...');
|
|
// Use Chrome DevTools > Memory > Take snapshot
|
|
|
|
// 2. Perform actions
|
|
for (let i = 0; i < 100; i++) {
|
|
await component.call('addItem', { item: generateTestItem(i) });
|
|
}
|
|
|
|
// 3. Take second snapshot
|
|
console.log('Taking second snapshot...');
|
|
// Compare snapshots in DevTools
|
|
|
|
// Check component cleanup
|
|
window.addEventListener('beforeunload', () => {
|
|
console.log('Active components:', window.LiveComponents.getAll());
|
|
console.log('Event listeners:', window.LiveComponents._eventListeners.size);
|
|
});
|
|
```
|
|
|
|
**Common Fixes:**
|
|
- Event listeners not cleaned up on component destroy
|
|
- DOM references held in closures
|
|
- Polling not stopped on unmount
|
|
- Large state objects not garbage collected
|
|
|
|
### Scenario 4: CSRF Token Mismatch
|
|
|
|
**Symptoms:** 403 Forbidden on action calls
|
|
|
|
**Debug Steps:**
|
|
|
|
```javascript
|
|
// Check CSRF token presence
|
|
const form = document.querySelector('form');
|
|
const csrfToken = form.querySelector('[name="_csrf_token"]')?.value;
|
|
console.log('CSRF Token in form:', csrfToken);
|
|
|
|
// Check request headers
|
|
window.LiveComponents.onRequest((request) => {
|
|
console.log('CSRF Token in request:', request.headers['X-CSRF-Token']);
|
|
});
|
|
|
|
// Check session token
|
|
fetch('/api/csrf-token')
|
|
.then(r => r.json())
|
|
.then(data => console.log('Current session CSRF:', data.token));
|
|
```
|
|
|
|
**Common Fixes:**
|
|
- Token not included in request (check middleware)
|
|
- Token expired (implement rotation)
|
|
- Session cleared (re-authenticate)
|
|
- Token not matching session (clear cookies and refresh)
|
|
|
|
### Scenario 5: Rate Limiting Issues
|
|
|
|
**Symptoms:** Actions suddenly fail with 429 Too Many Requests
|
|
|
|
**Debug Steps:**
|
|
|
|
```javascript
|
|
// Track rate limit headers
|
|
window.LiveComponents.onResponse((response) => {
|
|
if (response.status === 429) {
|
|
console.error('Rate Limited:', {
|
|
retryAfter: response.headers['Retry-After'],
|
|
limit: response.headers['X-RateLimit-Limit'],
|
|
remaining: response.headers['X-RateLimit-Remaining'],
|
|
reset: response.headers['X-RateLimit-Reset']
|
|
});
|
|
}
|
|
});
|
|
|
|
// Monitor action frequency
|
|
let actionCount = 0;
|
|
let startTime = Date.now();
|
|
|
|
window.LiveComponents.onRequest(() => {
|
|
actionCount++;
|
|
const elapsed = (Date.now() - startTime) / 1000;
|
|
const rate = actionCount / elapsed;
|
|
|
|
console.log(`Action rate: ${rate.toFixed(2)} req/sec`);
|
|
|
|
if (rate > 5) {
|
|
console.warn('High action rate detected - may trigger rate limiting');
|
|
}
|
|
});
|
|
```
|
|
|
|
**Common Fixes:**
|
|
- Implement debouncing for user actions
|
|
- Increase rate limits for legitimate use cases
|
|
- Add exponential backoff on 429 responses
|
|
- Batch multiple actions into single request
|
|
|
|
---
|
|
|
|
## Browser DevTools Integration
|
|
|
|
### Console Helpers
|
|
|
|
```javascript
|
|
// Global debug helpers (available in console)
|
|
|
|
// Get component by ID
|
|
lc('shopping-cart:main') // Shorthand for window.LiveComponents.get()
|
|
|
|
// List all components
|
|
lc.all()
|
|
|
|
// Component state
|
|
lc.state('shopping-cart:main')
|
|
|
|
// Component history
|
|
lc.history('shopping-cart:main')
|
|
|
|
// Trigger action manually
|
|
lc.call('shopping-cart:main', 'addItem', { item: testItem })
|
|
|
|
// Enable/disable debug mode
|
|
lc.debug(true) // Enable
|
|
lc.debug(false) // Disable
|
|
|
|
// Performance stats
|
|
lc.stats('shopping-cart:main')
|
|
|
|
// Clear component cache
|
|
lc.clearCache('shopping-cart:main')
|
|
```
|
|
|
|
### React DevTools Style Inspector
|
|
|
|
```javascript
|
|
// Component tree visualization
|
|
window.LiveComponents.inspect()
|
|
// Logs component hierarchy:
|
|
/*
|
|
shopping-cart:main
|
|
├── cart-item:item-1
|
|
├── cart-item:item-2
|
|
└── cart-total:main
|
|
*/
|
|
|
|
// Component details
|
|
window.LiveComponents.inspectComponent('shopping-cart:main')
|
|
// Logs:
|
|
/*
|
|
Component: shopping-cart:main
|
|
State: { items: [...], total: 2999 }
|
|
Actions: [addItem, removeItem, updateQuantity]
|
|
Events: [itemAdded, itemRemoved]
|
|
Polling: { enabled: true, interval: 5000ms }
|
|
Cache: { enabled: true, ttl: 300s, hits: 23, misses: 5 }
|
|
Performance: { avgRenderTime: 15ms, actionCount: 47 }
|
|
*/
|
|
```
|
|
|
|
---
|
|
|
|
## Server-Side Logging
|
|
|
|
### Structured Logging
|
|
|
|
```php
|
|
use App\Framework\LiveComponents\Logging\ComponentLogger;
|
|
use Psr\Log\LogLevel;
|
|
|
|
final readonly class ComponentLogger
|
|
{
|
|
public function logAction(
|
|
string $componentId,
|
|
string $action,
|
|
array $context = []
|
|
): void {
|
|
$this->logger->log(LogLevel::INFO, 'LiveComponent action executed', [
|
|
'component_id' => $componentId,
|
|
'action' => $action,
|
|
'user_id' => $context['user_id'] ?? null,
|
|
'session_id' => $context['session_id'] ?? null,
|
|
'execution_time_ms' => $context['execution_time'] ?? null,
|
|
'timestamp' => time()
|
|
]);
|
|
}
|
|
|
|
public function logError(
|
|
string $componentId,
|
|
string $action,
|
|
\Throwable $error,
|
|
array $context = []
|
|
): void {
|
|
$this->logger->log(LogLevel::ERROR, 'LiveComponent action failed', [
|
|
'component_id' => $componentId,
|
|
'action' => $action,
|
|
'error_message' => $error->getMessage(),
|
|
'error_class' => get_class($error),
|
|
'stack_trace' => $error->getTraceAsString(),
|
|
'state_snapshot' => $context['state'] ?? null,
|
|
'parameters' => $context['parameters'] ?? null
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Action Audit Log
|
|
|
|
```php
|
|
final readonly class ActionAuditLogger
|
|
{
|
|
public function logActionExecution(
|
|
ActionExecution $execution
|
|
): void {
|
|
$this->auditLog->write([
|
|
'event' => 'livecomponent.action',
|
|
'component_id' => $execution->componentId,
|
|
'action' => $execution->action,
|
|
'user_id' => $execution->userId,
|
|
'ip_address' => $execution->ipAddress,
|
|
'user_agent' => $execution->userAgent,
|
|
'parameters' => $this->sanitizeParameters($execution->parameters),
|
|
'success' => $execution->success,
|
|
'error' => $execution->error?->getMessage(),
|
|
'execution_time_ms' => $execution->executionTime->toMilliseconds(),
|
|
'timestamp' => $execution->timestamp->format('Y-m-d H:i:s')
|
|
]);
|
|
}
|
|
|
|
private function sanitizeParameters(array $parameters): array
|
|
{
|
|
// Remove sensitive data from logs
|
|
$sanitized = $parameters;
|
|
|
|
foreach (['password', 'token', 'api_key', 'secret'] as $sensitive) {
|
|
if (isset($sanitized[$sensitive])) {
|
|
$sanitized[$sensitive] = '[REDACTED]';
|
|
}
|
|
}
|
|
|
|
return $sanitized;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Log Aggregation Queries
|
|
|
|
```bash
|
|
# Find slow actions (Elasticsearch/Kibana query)
|
|
GET /livecomponents-logs/_search
|
|
{
|
|
"query": {
|
|
"bool": {
|
|
"must": [
|
|
{ "match": { "event": "livecomponent.action" } },
|
|
{ "range": { "execution_time_ms": { "gte": 100 } } }
|
|
]
|
|
}
|
|
},
|
|
"aggs": {
|
|
"slow_actions": {
|
|
"terms": {
|
|
"field": "action.keyword",
|
|
"size": 10
|
|
},
|
|
"aggs": {
|
|
"avg_time": { "avg": { "field": "execution_time_ms" } }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Find error patterns
|
|
GET /livecomponents-logs/_search
|
|
{
|
|
"query": {
|
|
"bool": {
|
|
"must": [
|
|
{ "match": { "success": false } }
|
|
]
|
|
}
|
|
},
|
|
"aggs": {
|
|
"error_types": {
|
|
"terms": { "field": "error_class.keyword" }
|
|
},
|
|
"affected_components": {
|
|
"terms": { "field": "component_id.keyword" }
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Testing & Development Helpers
|
|
|
|
### Pest Testing Helpers
|
|
|
|
```php
|
|
use function Pest\LiveComponents\mountComponent;
|
|
use function Pest\LiveComponents\callAction;
|
|
|
|
// Mount component with debug enabled
|
|
it('debugs component state changes', function () {
|
|
$component = mountComponent('shopping-cart:main', [
|
|
'items' => [],
|
|
'total' => 0
|
|
], debug: true); // Enable debug mode
|
|
|
|
// Call action with debug output
|
|
$result = callAction($component, 'addItem', [
|
|
'item' => ['id' => '1', 'name' => 'Test', 'price' => 1000]
|
|
], debug: true);
|
|
|
|
// Debug output automatically logged:
|
|
/*
|
|
[DEBUG] Action: addItem
|
|
[DEBUG] Before State: {"items":[],"total":0}
|
|
[DEBUG] Parameters: {"item":{"id":"1","name":"Test","price":1000}}
|
|
[DEBUG] After State: {"items":[...],"total":1000}
|
|
[DEBUG] Execution Time: 2.34ms
|
|
*/
|
|
|
|
expect($result['state']['total'])->toBe(1000);
|
|
});
|
|
```
|
|
|
|
### Mock State Inspector
|
|
|
|
```php
|
|
// Create mock state inspector for testing
|
|
final readonly class MockStateInspector implements StateInspector
|
|
{
|
|
private array $snapshots = [];
|
|
private array $logs = [];
|
|
|
|
public function captureSnapshot(string $label, ComponentData $state, ?array $metadata = null): void
|
|
{
|
|
$this->snapshots[] = new StateSnapshot($label, $state, microtime(true), metadata: $metadata);
|
|
}
|
|
|
|
public function getSnapshots(string $componentId): array
|
|
{
|
|
return array_filter(
|
|
$this->snapshots,
|
|
fn($s) => str_starts_with($s->label, $componentId)
|
|
);
|
|
}
|
|
|
|
public function log(string $message, array $context = []): void
|
|
{
|
|
$this->logs[] = ['message' => $message, 'context' => $context, 'time' => microtime(true)];
|
|
}
|
|
|
|
public function getLogs(): array
|
|
{
|
|
return $this->logs;
|
|
}
|
|
}
|
|
|
|
// Use in tests
|
|
it('captures state snapshots during action', function () {
|
|
$inspector = new MockStateInspector();
|
|
$component = new ShoppingCartComponent(
|
|
id: ComponentId::fromString('cart:test'),
|
|
state: new ShoppingCartState([], 0),
|
|
stateInspector: $inspector
|
|
);
|
|
|
|
$component->addItem(new CartItem('1', 'Test', 1000));
|
|
|
|
$snapshots = $inspector->getSnapshots('cart:test');
|
|
expect($snapshots)->toHaveCount(2) // before and after
|
|
->and($inspector->getLogs())->toContain(fn($log) => $log['message'] === 'State changed');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting Workflows
|
|
|
|
### Workflow 1: Component Not Rendering
|
|
|
|
```
|
|
1. Check component registration
|
|
→ Verify #[LiveComponent] attribute present
|
|
→ Check component name matches template usage
|
|
|
|
2. Check initialization
|
|
→ Console: window.LiveComponents.get('component:id')
|
|
→ Should return component instance
|
|
|
|
3. Verify template
|
|
→ Check render() method returns correct template path
|
|
→ Verify template file exists
|
|
→ Check template syntax
|
|
|
|
4. Check state
|
|
→ Verify state class is readonly
|
|
→ Check constructor parameters
|
|
→ Ensure state serializable
|
|
|
|
5. Server logs
|
|
→ Check for component registration errors
|
|
→ Look for template loading errors
|
|
→ Verify DI container bindings
|
|
```
|
|
|
|
### Workflow 2: Action Fails Silently
|
|
|
|
```
|
|
1. Enable debug mode
|
|
→ Set debug: true in config
|
|
→ Check browser console for errors
|
|
|
|
2. Network inspection
|
|
→ DevTools > Network > Filter: livecomponent
|
|
→ Check request payload
|
|
→ Verify response status (200/403/429/500)
|
|
|
|
3. Server-side debugging
|
|
→ Add error_log() in action method
|
|
→ Check action parameter types match
|
|
→ Verify return type is ComponentData
|
|
|
|
4. State validation
|
|
→ Ensure new state is returned (not modified)
|
|
→ Check state is serializable
|
|
→ Verify no circular references
|
|
|
|
5. Security checks
|
|
→ CSRF token present and valid
|
|
→ Rate limit not exceeded
|
|
→ Authentication/authorization passed
|
|
```
|
|
|
|
### Workflow 3: Performance Degradation
|
|
|
|
```
|
|
1. Measure baseline
|
|
→ Use Performance API to measure render times
|
|
→ Track action execution times
|
|
→ Monitor network request sizes
|
|
|
|
2. Identify bottleneck
|
|
→ Profile component lifecycle
|
|
→ Check database query count (N+1 problems)
|
|
→ Verify cache effectiveness
|
|
|
|
3. Optimize rendering
|
|
→ Enable fragment rendering
|
|
→ Implement component caching with varyBy
|
|
→ Use SWR for non-critical updates
|
|
|
|
4. Reduce network overhead
|
|
→ Enable request batching
|
|
→ Compress responses (gzip)
|
|
→ Implement polling backoff
|
|
|
|
5. Server-side optimization
|
|
→ Add database indexes
|
|
→ Optimize queries (eager loading)
|
|
→ Implement query result caching
|
|
→ Use Redis for session storage
|
|
```
|
|
|
|
---
|
|
|
|
## Production Debugging
|
|
|
|
### Safe Production Debugging
|
|
|
|
```php
|
|
// Enable debug mode for specific users only
|
|
final readonly class DebugModeManager
|
|
{
|
|
public function isDebugEnabledForUser(?string $userId): bool
|
|
{
|
|
if (!$userId) {
|
|
return false;
|
|
}
|
|
|
|
// Allow debug mode for admin users
|
|
if ($this->userService->isAdmin($userId)) {
|
|
return true;
|
|
}
|
|
|
|
// Allow debug mode via feature flag
|
|
if ($this->featureFlags->isEnabled('livecomponents-debug', $userId)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Conditional debug logging
|
|
$config = new LiveComponentsConfig(
|
|
debug: $this->debugManager->isDebugEnabledForUser($currentUserId),
|
|
enableDevTools: false, // Never enable in production
|
|
logLevel: 'error' // Only log errors in production
|
|
);
|
|
```
|
|
|
|
### Production Error Tracking
|
|
|
|
```php
|
|
use App\Framework\ErrorReporting\ErrorReporter;
|
|
|
|
final readonly class ProductionErrorHandler
|
|
{
|
|
public function __construct(
|
|
private ErrorReporter $errorReporter // e.g., Sentry, Rollbar
|
|
) {}
|
|
|
|
public function handleActionError(
|
|
string $componentId,
|
|
string $action,
|
|
\Throwable $error,
|
|
array $context
|
|
): void {
|
|
$this->errorReporter->captureException($error, [
|
|
'tags' => [
|
|
'component' => 'livecomponents',
|
|
'component_id' => $componentId,
|
|
'action' => $action
|
|
],
|
|
'extra' => [
|
|
'user_id' => $context['user_id'] ?? null,
|
|
'session_id' => $context['session_id'] ?? null,
|
|
'state_hash' => $context['state_hash'] ?? null,
|
|
'parameters' => $this->sanitize($context['parameters'] ?? [])
|
|
],
|
|
'fingerprint' => [
|
|
$componentId,
|
|
$action,
|
|
get_class($error)
|
|
]
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Production Monitoring
|
|
|
|
```php
|
|
// Metrics collection for production monitoring
|
|
final readonly class ProductionMetricsCollector
|
|
{
|
|
public function recordActionExecution(
|
|
string $componentId,
|
|
string $action,
|
|
float $executionTime,
|
|
bool $success
|
|
): void {
|
|
// StatsD/Prometheus metrics
|
|
$this->metrics->increment('livecomponents.action.count', [
|
|
'component' => $componentId,
|
|
'action' => $action,
|
|
'success' => $success ? 'true' : 'false'
|
|
]);
|
|
|
|
$this->metrics->timing('livecomponents.action.duration', $executionTime, [
|
|
'component' => $componentId,
|
|
'action' => $action
|
|
]);
|
|
}
|
|
|
|
public function recordCacheHit(string $componentId, bool $hit): void
|
|
{
|
|
$this->metrics->increment('livecomponents.cache.' . ($hit ? 'hit' : 'miss'), [
|
|
'component' => $componentId
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Feature Flags for Debugging
|
|
|
|
```php
|
|
// Use feature flags to enable debugging features safely
|
|
final readonly class FeatureFlagDebugger
|
|
{
|
|
public function shouldEnableVerboseLogging(string $componentId): bool
|
|
{
|
|
return $this->featureFlags->isEnabled("debug:verbose:{$componentId}");
|
|
}
|
|
|
|
public function shouldCaptureSnapshots(string $componentId): bool
|
|
{
|
|
return $this->featureFlags->isEnabled("debug:snapshots:{$componentId}");
|
|
}
|
|
|
|
public function shouldProfilePerformance(string $componentId): bool
|
|
{
|
|
return $this->featureFlags->isEnabled("debug:profiling:{$componentId}");
|
|
}
|
|
}
|
|
|
|
// Usage in component
|
|
#[Action]
|
|
public function processPayment(PaymentRequest $request): ComponentData
|
|
{
|
|
if ($this->debugger->shouldCaptureSnapshots($this->id->toString())) {
|
|
$this->stateInspector->captureSnapshot('before_payment', $this->state);
|
|
}
|
|
|
|
$result = $this->paymentService->process($request);
|
|
|
|
if ($this->debugger->shouldCaptureSnapshots($this->id->toString())) {
|
|
$this->stateInspector->captureSnapshot('after_payment', $this->state);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
### Quick Reference
|
|
|
|
**Enable Debug Mode:**
|
|
```php
|
|
new LiveComponentsConfig(debug: true, enableDevTools: true, logLevel: 'debug')
|
|
```
|
|
|
|
**Client-Side Debugging:**
|
|
```javascript
|
|
window.LiveComponents.setDebug(true);
|
|
lc('component:id').state; // Inspect state
|
|
lc.call('component:id', 'action', params); // Manual action
|
|
```
|
|
|
|
**Server-Side Debugging:**
|
|
```php
|
|
$this->stateInspector->captureSnapshot('label', $state);
|
|
$this->logger->debug('Action executed', $context);
|
|
```
|
|
|
|
**Performance Profiling:**
|
|
```javascript
|
|
performance.mark('start');
|
|
await action();
|
|
performance.mark('end');
|
|
performance.measure('action', 'start', 'end');
|
|
```
|
|
|
|
**Production Monitoring:**
|
|
```php
|
|
$this->errorReporter->captureException($error, $context);
|
|
$this->metrics->timing('action.duration', $time);
|
|
```
|
|
|
|
### Best Practices
|
|
|
|
1. **Development**: Enable debug mode and DevTools
|
|
2. **Staging**: Enable selective debug for specific users
|
|
3. **Production**: Disable debug, use error reporting and metrics
|
|
4. **Logging**: Use structured logging with proper context
|
|
5. **Performance**: Profile regularly, set performance budgets
|
|
6. **Security**: Never log sensitive data (passwords, tokens)
|
|
7. **Monitoring**: Set up alerts for error rates and slow actions
|
|
8. **Testing**: Use debug helpers in Pest tests
|
|
|
|
### Resources
|
|
|
|
- **Browser DevTools**: Chrome/Firefox/Edge Developer Tools
|
|
- **Error Tracking**: Sentry, Rollbar, Bugsnag
|
|
- **Metrics**: StatsD, Prometheus, Datadog
|
|
- **Logging**: ELK Stack, Graylog, Splunk
|
|
- **Profiling**: Blackfire, Tideways, XHProf
|
|
- **Testing**: Pest, PHPUnit, Playwright
|
|
|
|
This guide covers debugging workflows, tools, and best practices for developing and maintaining LiveComponents in development, staging, and production environments.
|