dennisvanbeersel/symfony-logger-client
最新稳定版本:v0.2.2
Composer 安装命令:
composer require dennisvanbeersel/symfony-logger-client
包简介
Symfony bundle for Application Logger error tracking platform
README 文档
README
🛡️ Privacy-First Error Tracking for Symfony - Hosted in EU
Resilience-first error tracking with integrated JavaScript SDK - your app never slows down ⚡
Quick Start • Why This Bundle? • Features • Documentation
📦 TL;DR - Get Started in 2 Minutes
# 1. Install composer require dennisvanbeersel/symfony-logger-client # 2. Configure (config/packages/application_logger.yaml) application_logger: dsn: '%env(APPLICATION_LOGGER_DSN)%' api_key: '%env(APPLICATION_LOGGER_API_KEY)%' # 3. Add credentials to .env APPLICATION_LOGGER_DSN=https://applogger.eu/your-project-uuid APPLICATION_LOGGER_API_KEY=your-64-character-api-key-here # 4. Clear cache php bin/console cache:clear
Done! All PHP exceptions and JavaScript errors are now automatically tracked. No code changes needed.
🎯 Why This Bundle?
AppLogger (applogger.eu) is an EU-hosted, privacy-first error tracking SaaS platform specifically designed for Symfony applications. This bundle provides zero-config integration with production-grade resilience.
Most error tracking solutions have a critical flaw: they can slow down or even crash your application when the tracking service is down. This bundle is different.
Core Philosophy: Never Impact Your Application
We achieve this through battle-tested resilience patterns:
| Feature | This Bundle | Typical Solutions | Impact |
|---|---|---|---|
| Timeout | ⚡ 2s max (configurable) | ⏰ Often 30s+ or none | 50ms vs 30s+ delay |
| Circuit Breaker | ✅ Automatic failover | ❌ Keep retrying | Stops wasting resources |
| Fire & Forget | ✅ Returns instantly | ❌ Waits for response | <1ms vs 2000ms |
| Exception Safety | ✅ Never throws | ⚠️ Can crash app | 100% uptime guarantee |
| JS Offline Queue | ✅ localStorage backup | ❌ Errors lost | Zero data loss |
| JS Rate Limiting | ✅ Token bucket | ❌ Can overwhelm API | Protected from error storms |
Real-World Impact
Without resilience patterns:
// API is down, timeout is 30s $start = microtime(true); errorTracker()->captureException($e); // Blocks for 30 seconds! $elapsed = microtime(true) - $start; // 30,000ms // User waited 30 seconds for page to load 😱
With this bundle:
// API is down, circuit breaker is open $start = microtime(true); errorTracker()->captureException($e); // Returns instantly $elapsed = microtime(true) - $start; // <1ms // User doesn't notice anything 🎉
✨ Features
PHP Backend Features
|
Automatic Capture
|
Resilience (Production-Grade)
|
|
Security (GDPR Compliant)
|
Developer Experience
|
JavaScript SDK Features (Included!)
No separate npm package needed! The JavaScript SDK is bundled with this Symfony bundle.
|
Automatic Capture
|
Resilience (Client-Side)
|
📊 What Gets Tracked?
This bundle provides comprehensive monitoring for both backend and frontend:
PHP Backend Tracking
- Exceptions: All uncaught exceptions via Symfony event subscriber
- HTTP Errors: 4xx and 5xx status codes (404, 500, etc.)
- Monolog Integration: Error-level logs when configured
- Context: Request/response data, user context, server metadata
- Breadcrumbs: User actions leading up to errors
JavaScript Frontend Tracking
- Browser Errors: Uncaught exceptions and unhandled promise rejections
- API Failures: Failed HTTP requests with status codes
- Error-Triggered Session Replay: Captures user actions (30s/10 clicks before and after errors) with DOM snapshots
- Session Tracking: Page views, navigation flows, session duration
- User Context: Browser, platform, screen resolution
GDPR Compliance
- Automatic PII Scrubbing: Passwords, tokens, sensitive fields removed
- IP Anonymization: Last octet masked (192.168.1.0 instead of 192.168.1.100)
- Session Hashing: User identifiers are cryptographically hashed
- EU Data Residency: All data stored in EU datacenters
🚀 Quick Start
Installation
composer require dennisvanbeersel/symfony-logger-client
If you're not using Symfony Flex, register the bundle in config/bundles.php:
return [ // ... ApplicationLogger\Bundle\ApplicationLoggerBundle::class => ['all' => true], ];
Configuration
Minimal Configuration (Recommended)
# config/packages/application_logger.yaml application_logger: dsn: '%env(APPLICATION_LOGGER_DSN)%'
Add to .env:
APPLICATION_LOGGER_DSN=https://public_key@logger.example.com/project_id
APP_VERSION=1.0.0 # Optional but recommended
Full Configuration Example
Click to see all available options
# config/packages/application_logger.yaml application_logger: # Required: Your AppLogger DSN (get from applogger.eu dashboard) dsn: '%env(APPLICATION_LOGGER_DSN)%' # Optional: Enable/disable the bundle enabled: true # Optional: Application version for release tracking release: '%env(APP_VERSION)%' # Optional: Environment identifier environment: '%kernel.environment%' # Resilience Settings timeout: 2.0 # API timeout (0.5-5.0 seconds) retry_attempts: 0 # Retry failed requests (0-3, 0=fail fast) async: true # Fire-and-forget mode (recommended) # Circuit Breaker circuit_breaker: enabled: true # Enable circuit breaker pattern failure_threshold: 5 # Open after N consecutive failures timeout: 60 # Stay open for N seconds half_open_attempts: 1 # Test requests before closing # What to Capture capture_level: error # Monolog level: debug, info, warning, error, critical # Breadcrumbs max_breadcrumbs: 50 # Maximum breadcrumbs to keep (10-100) # Security: Sensitive Data Scrubbing scrub_fields: - password - token - api_key - secret - authorization - credit_card - ssn # Session Tracking (Required for session replay) session_tracking: enabled: true # Enable automatic session tracking (default: true) track_page_views: true # Track page views as session events (default: true) idle_timeout: 1800 # Session idle timeout in seconds (default: 30 min) # Error-Triggered Session Replay session_replay: enabled: true # Enable session replay (default: true) buffer_before_error_seconds: 30 # Seconds to buffer before error (5-60, default: 30) buffer_before_error_clicks: 10 # Clicks to buffer before error (1-15, default: 10) buffer_after_error_seconds: 30 # Seconds to buffer after error (5-60, default: 30) buffer_after_error_clicks: 10 # Clicks to buffer after error (1-15, default: 10) click_debounce_ms: 1000 # Click debounce delay (100-5000ms, default: 1000) snapshot_throttle_ms: 1000 # DOM snapshot throttle (500-5000ms, default: 1000) max_snapshot_size: 1048576 # Max snapshot size in bytes (default: 1MB) session_timeout_minutes: 30 # Cross-page session timeout (5-120 min, default: 30) max_buffer_size_mb: 5 # Max localStorage size (1-20MB, default: 5MB) expose_api: true # Expose JS API for user control (default: true) # JavaScript SDK javascript: enabled: true # Enable Twig globals for JS SDK auto_inject: true # Auto-inject init script (recommended) debug: false # Enable console.log debugging # Debug debug: '%kernel.debug%' # Enable internal logging
Clear Cache
php bin/console cache:clear
Done! All exceptions are now automatically tracked. Visit your AppLogger dashboard at applogger.eu to see errors.
📖 Usage
1️⃣ PHP Backend Usage
Automatic Capture (Zero Code Changes)
The bundle automatically captures:
- ✅ Uncaught exceptions via Symfony event subscriber
- ✅ HTTP status codes (404, 403, 500, etc.)
- ✅ Monolog error logs (when configured)
- ✅ User context from Symfony Security
- ✅ Request data (headers, POST data, query params)
No code changes required! Just install and configure.
Monolog Integration
Send error-level logs to AppLogger:
# config/packages/monolog.yaml monolog: handlers: application_logger: type: service id: ApplicationLogger\Bundle\Monolog\Handler\ApplicationLoggerHandler level: error channels: ['!event'] # Exclude to avoid duplication
Now all $logger->error(), $logger->critical(), etc. calls are tracked.
Manual Error Capture
For custom error handling:
use ApplicationLogger\Bundle\Service\ApiClient; use ApplicationLogger\Bundle\Service\BreadcrumbCollector; class PaymentService { public function __construct( private ApiClient $apiClient, private BreadcrumbCollector $breadcrumbs ) {} public function processPayment(Order $order): void { // Add breadcrumb for context $this->breadcrumbs->add([ 'type' => 'user', 'category' => 'payment', 'message' => 'Processing payment', 'data' => ['order_id' => $order->getId()], ]); try { $this->chargeCustomer($order); } catch (\Exception $e) { // Manual error reporting $this->apiClient->sendError([ 'exception' => [ 'type' => $e::class, 'value' => $e->getMessage(), 'stacktrace' => $this->formatStackTrace($e), ], 'level' => 'error', 'tags' => ['feature' => 'payment'], ]); throw $e; // Re-throw if needed } } }
Adding Breadcrumbs
Track user actions leading up to errors:
use ApplicationLogger\Bundle\Service\BreadcrumbCollector; class CheckoutController extends AbstractController { public function __construct( private BreadcrumbCollector $breadcrumbs ) {} #[Route('/checkout/step-1')] public function step1(): Response { $this->breadcrumbs->add([ 'type' => 'navigation', 'category' => 'checkout', 'message' => 'User entered checkout', 'level' => 'info', ]); // ... your code } }
2️⃣ JavaScript SDK Usage
Zero-Config Mode (Automatic) ⭐ Recommended
Default behavior - no setup needed!
The bundle automatically:
- ✅ Registers JS SDK with AssetMapper
- ✅ Injects initialization script on all HTML pages
- ✅ Configures with your DSN
- ✅ Sets environment and release
- ✅ Populates user context
- ✅ Makes
window.appLoggeravailable
Just install the bundle - JavaScript tracking works immediately!
Manual Mode (Custom Control)
If you want control over when/where the SDK loads:
# config/packages/application_logger.yaml application_logger: javascript: auto_inject: false # Disable automatic injection
Then manually add to your templates:
{# templates/base.html.twig #} <!DOCTYPE html> <html> <body> {% block body %}{% endblock %} {# Manually place the initialization script #} {{ application_logger_init() }} </body> </html>
Using the JavaScript SDK
Once loaded, use window.appLogger:
// Capture exceptions try { riskyOperation(); } catch (error) { window.appLogger.captureException(error, { tags: { component: 'checkout' }, extra: { orderId: 12345 } }); } // Capture messages window.appLogger.captureMessage('Payment processed', 'info'); // Add breadcrumbs window.appLogger.addBreadcrumb({ type: 'user', message: 'User clicked checkout button', data: { cartTotal: 99.99 } }); // Set user context window.appLogger.setUser({ id: 'user-123', email: 'user@example.com' }); // Check circuit breaker status window.appLogger.transport.getStats(); // {queueSize: 0, rateLimitTokens: 9.2, circuitBreaker: {state: 'closed'}}
🛡️ Resilience Features Explained
Circuit Breaker Pattern
Problem: When the API is down, your app wastes resources retrying.
Solution: Circuit breaker with three states:
CLOSED (normal) → [5 failures] → OPEN (service down)
↓
[60 seconds wait]
↓
CLOSED ← [success] ← HALF_OPEN ← [timeout passed]
[failure] → OPEN
PHP Implementation:
- Uses Symfony Cache for state persistence
- After 5 consecutive failures → opens for 60 seconds
- While OPEN: all API calls skip immediately (zero overhead)
- After 60s: enters HALF_OPEN, tries 1 request
- Success → CLOSED, failure → OPEN for another 60s
JavaScript Implementation:
- Uses sessionStorage for state persistence
- Same 3-state logic as PHP
- Prevents browser from hitting failing API
Monitoring:
// PHP $state = $apiClient->getCircuitBreakerState(); // ['state' => 'closed', 'failureCount' => 2, 'openedAt' => null]
// JavaScript window.appLogger.transport.circuitBreaker.getState(); // {state: 'closed', failureCount: 0, openedAt: null}
Timeout Protection
PHP:
- Maximum 2 seconds per API call (configurable 0.5-5s)
- Configured at HTTP client level
- After timeout: connection aborted, circuit breaker records failure
JavaScript:
- Maximum 3 seconds per API call
- Uses
AbortControllerto forcefully abort - After timeout: error queued to localStorage
Fire-and-Forget Mode (PHP)
When async: true (default):
// With async: false (synchronous) $start = microtime(true); $apiClient->sendError($payload); $elapsed = microtime(true) - $start; // $elapsed could be 2000ms (full timeout) // With async: true (fire-and-forget) $start = microtime(true); $apiClient->sendError($payload); $elapsed = microtime(true) - $start; // $elapsed is typically < 1ms (request queued, method returns)
Offline Queue (JavaScript)
When API is unreachable:
- Errors stored in localStorage (FIFO queue)
- Maximum 50 errors (oldest removed first)
- Errors expire after 24 hours
- On next successful connection: queue automatically flushed
Handles quota errors gracefully:
- If localStorage full → removes oldest 50%
- If still full → clears entire queue
Rate Limiting (JavaScript)
Token bucket algorithm prevents error storms:
- Capacity: 10 tokens
- Refill rate: ~1 token per 6 seconds (~10 per minute)
- Behavior: No tokens → error goes to offline queue
window.appLogger.transport.getStats(); // {rateLimitTokens: 8.5, queueSize: 0, ...}
Deduplication (JavaScript)
Prevents sending the same error repeatedly:
- Creates hash from: error type + message + top 3 stack frames
- Remembers recently sent errors for 5 seconds
- Duplicate detected → ignored
Beacon API (JavaScript)
Problem: When user closes tab, errors in queue are lost.
Solution: navigator.sendBeacon() API
- Listens to
beforeunloadandvisibilitychange - Flushes up to 10 most recent errors
- Guaranteed delivery even as page closes
🔒 Security Features
Automatic Data Scrubbing
Sensitive data automatically removed from error reports:
Default scrubbed fields:
- password, passwd, pwd
- token, api_key, secret
- authorization, auth
- credit_card, ssn, private_key
How it works:
- Recursive key check (case-insensitive substring matching)
- Replaces values with
[REDACTED] - Applies to: request data, headers, cookies, extra context
Example:
$request->request->all(); // ['email' => 'user@example.com', 'password' => 'secret123'] // Sent to API as: // ['email' => 'user@example.com', 'password' => '[REDACTED]']
Custom scrub fields:
application_logger: scrub_fields: - password - credit_card - my_custom_secret
IP Address Anonymization
IPv4: Masks last octet
192.168.1.100 → 192.168.1.0
IPv6: Masks last 80 bits
2001:0db8:85a3:0000:0000:8a2e:0370:7334
→ 2001:0db8:85a3:0000:0000:0000:0000:0000
Why: GDPR compliance - IP addresses are personal data.
🔧 Advanced Configuration
Disable in Development
# config/packages/dev/application_logger.yaml application_logger: enabled: false
Or use .env.local:
APPLICATION_LOGGER_ENABLED=false
Multiple Projects
Send errors to different AppLogger projects:
# config/services.yaml services: app.logger.project_a: class: ApplicationLogger\Bundle\Service\ApiClient arguments: $dsn: '%env(LOGGER_DSN_PROJECT_A)%' $timeout: 2.0 $circuitBreaker: '@ApplicationLogger\Bundle\Service\CircuitBreaker' app.logger.project_b: class: ApplicationLogger\Bundle\Service\ApiClient arguments: $dsn: '%env(LOGGER_DSN_PROJECT_B)%' $timeout: 2.0 $circuitBreaker: '@ApplicationLogger\Bundle\Service\CircuitBreaker'
Custom Error Handler
use ApplicationLogger\Bundle\Service\ApiClient; use ApplicationLogger\Bundle\Service\BreadcrumbCollector; use ApplicationLogger\Bundle\Service\ContextCollector; class CustomErrorHandler { public function __construct( private ApiClient $apiClient, private ContextCollector $contextCollector, private BreadcrumbCollector $breadcrumbs ) {} public function handleBusinessError(BusinessException $e): void { $this->apiClient->sendError([ 'exception' => [ 'type' => $e::class, 'value' => $e->getMessage(), 'stacktrace' => $this->formatTrace($e), ], 'level' => 'warning', // Business errors are warnings 'context' => $this->contextCollector->collectContext(), 'breadcrumbs' => $this->breadcrumbs->get(), 'tags' => [ 'error_type' => 'business', 'rule' => $e->getBusinessRule(), ], ]); } }
🐛 Troubleshooting
Errors Not Appearing in Dashboard
1. Check bundle is enabled:
php bin/console debug:config application_logger
2. Check DSN is correct:
php bin/console debug:container --parameters | grep application_logger.dsn
3. Check circuit breaker:
$cbState = $this->apiClient->getCircuitBreakerState(); // If state is 'open', wait 60s or clear cache
4. Enable debug mode:
application_logger: debug: true
Check var/log/dev.log for details.
Circuit Breaker Stuck Open
Solution 1: Wait for timeout (default 60 seconds)
Solution 2: Clear cache:
php bin/console cache:clear
Solution 3: Manually reset:
$cache->delete('app_logger_circuit_breaker_state');
JavaScript SDK Not Loading
1. Check AssetMapper:
php bin/console debug:asset-map | grep application-logger
2. Check browser console for import errors
3. Verify meta tag exists:
<meta name="app-logger-dsn" content="https://...">
DSN Format Error
Correct format:
https://public_key@your-host.com/project_id
Common mistakes:
❌ http://public_key@host/project (use https://)
❌ https://host/project (missing public_key@)
❌ https://public_key:secret@host/proj (secret not needed)
❌ https://public_key@host (missing /project_id)
🛠️ Development
Code Quality
composer lint # PHP-CS-Fixer + PHPStan composer cs-check # Check PSR-12 composer cs-fix # Auto-fix PSR-12 composer phpstan # Static analysis (level 6) npm run lint # ESLint npm run lint:fix # Auto-fix ESLint
Testing
# PHP tests composer test vendor/bin/phpunit # JavaScript tests npm test npm run test:coverage
Requirements
Minimum:
- PHP 8.2+
- Symfony 6.4 or 7.x
- ext-json, ext-curl
Recommended:
- PHP 8.3+
- Symfony 7.1+
- APCu or Redis (production cache)
📚 Documentation
| Document | Description |
|---|---|
| AppLogger Website | Sign up and get your DSN |
| API Reference | REST API documentation |
| Architecture | Technical architecture |
| Security & Testing | Security practices and testing guidelines |
📝 License
Part of the AppLogger project - see main LICENSE file.
🙏 Credits
Key Design Principles:
- Resilience first - never impact the host application
- Secure by default - no sensitive data exposure
- Zero configuration - works out of the box
- Production ready - battle-tested patterns
- Developer friendly - comprehensive docs
Built with ❤️ for the Symfony community.
Questions? Issues? Feedback?
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-10-27