fulgid / log-management
A Laravel package for log notification system and real-time log streaming
Installs: 15
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/fulgid/log-management
Requires
- php: ^8.0
- guzzlehttp/guzzle: ^7.0
- illuminate/broadcasting: ^9.0|^10.0|^11.0
- illuminate/database: ^9.0|^10.0|^11.0
- illuminate/mail: ^9.0|^10.0|^11.0
- illuminate/support: ^9.0|^10.0|^11.0
Requires (Dev)
- mockery/mockery: ^1.4
- orchestra/testbench: ^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.0|^10.0
README
π The most comprehensive Laravel log monitoring solution with real-time streaming, intelligent alerts, and multi-channel notifications
β¨ Key Features
π Quick Start
β‘ Installation (30 seconds)
# 1. Install package composer require fulgid/log-management # 2. One-command setup php artisan log-management:install # 3. Start monitoring! π
That's it! Your logs are now being monitored. Visit /log-management to see your dashboard.
π― Test Drive
// Trigger a test alert Log::error('Houston, we have a problem!', ['user_id' => 123]); // Or use the facade for custom alerts LogManagement::alert('Payment gateway is down!', [ 'severity' => 'critical', 'service' => 'payments' ]);
π Table of Contents
π Click to expand navigation
π Requirements
|
π PHP Requirements
|
πΈ Laravel Support
|
ποΈ Database Support
|
π§ Installation & Setup
π¦ Step 1: Install Package
composer require fulgid/log-management
βοΈ Step 2: Run Installation Wizard
php artisan log-management:install
π What the installer does
β
Publishes configuration files
β
Runs database migrations
β
Generates secure API keys
β
Creates storage directories
β
Tests your notification channels
β
Shows you next steps
ποΈ Step 3: Configure Environment
Add to your .env file:
# π§ Core Configuration LOG_MANAGEMENT_ENABLED=true LOG_MANAGEMENT_ENVIRONMENTS=production,staging # π Notifications LOG_MANAGEMENT_EMAIL_TO=admin@yourapp.com LOG_MANAGEMENT_SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/WEBHOOK # π Security LOG_MANAGEMENT_API_KEY_1=lm_your_secure_api_key_here # π Performance LOG_MANAGEMENT_RETENTION_DAYS=30 LOG_MANAGEMENT_RATE_LIMIT_MAX=10
β Step 4: Verify Installation
# Test your setup
php artisan log-management:test --all
βοΈ Configuration
π§ Core Settings
π Basic Configuration
# Package Control LOG_MANAGEMENT_ENABLED=true LOG_MANAGEMENT_DEBUG=false LOG_MANAGEMENT_ENVIRONMENTS=production,staging # Database Settings LOG_MANAGEMENT_DATABASE_ENABLED=true LOG_MANAGEMENT_AUTO_CLEANUP_ENABLED=true LOG_MANAGEMENT_RETENTION_DAYS=30 # Performance Tuning LOG_MANAGEMENT_BATCH_SIZE=100 LOG_MANAGEMENT_MEMORY_LIMIT=256M LOG_MANAGEMENT_ASYNC_PROCESSING=true
π Notification Channels
π§ Email Configuration
LOG_MANAGEMENT_EMAIL_ENABLED=true LOG_MANAGEMENT_EMAIL_TO=admin@yourapp.com,devteam@yourapp.com LOG_MANAGEMENT_EMAIL_FROM=alerts@yourapp.com LOG_MANAGEMENT_EMAIL_FROM_NAME="π¨ Log Management System" LOG_MANAGEMENT_EMAIL_SUBJECT_PREFIX="[ALERT]" LOG_MANAGEMENT_EMAIL_TEMPLATE=custom-alert-template
π¬ Slack Configuration
LOG_MANAGEMENT_SLACK_ENABLED=true LOG_MANAGEMENT_SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK LOG_MANAGEMENT_SLACK_CHANNEL=#alerts LOG_MANAGEMENT_SLACK_USERNAME="π€ Log Bot" LOG_MANAGEMENT_SLACK_ICON_EMOJI=:warning: LOG_MANAGEMENT_SLACK_MENTION_USERS=@channel,@devteam
π Webhook Configuration
LOG_MANAGEMENT_WEBHOOK_ENABLED=true LOG_MANAGEMENT_WEBHOOK_URL=https://your-monitoring-service.com/webhooks/logs LOG_MANAGEMENT_WEBHOOK_METHOD=POST LOG_MANAGEMENT_WEBHOOK_TIMEOUT=10 LOG_MANAGEMENT_WEBHOOK_AUTH_TYPE=bearer LOG_MANAGEMENT_WEBHOOK_AUTH_TOKEN=your-secret-token LOG_MANAGEMENT_WEBHOOK_RETRY_ATTEMPTS=3
π Security & Authentication
π‘οΈ Security Settings
# Authentication LOG_MANAGEMENT_AUTH_ENABLED=true LOG_MANAGEMENT_PERMISSION=view-logs # API Keys (auto-generated during install) LOG_MANAGEMENT_API_KEY_1=lm_your_generated_api_key_1 LOG_MANAGEMENT_API_KEY_2=lm_your_generated_api_key_2 # Rate Limiting LOG_MANAGEMENT_RATE_LIMIT_ENABLED=true LOG_MANAGEMENT_RATE_LIMIT_MAX=60 LOG_MANAGEMENT_RATE_LIMIT_WINDOW=60 # IP Restrictions (optional) LOG_MANAGEMENT_IP_WHITELIST=192.168.1.0/24,10.0.0.0/8
π‘ Real-time Streaming
π SSE Configuration
# Server-Sent Events LOG_MANAGEMENT_SSE_ENABLED=true LOG_MANAGEMENT_SSE_MAX_CONNECTIONS=100 LOG_MANAGEMENT_SSE_HEARTBEAT_INTERVAL=30 LOG_MANAGEMENT_SSE_BUFFER_SIZE=1024 # WebSocket Support (optional) LOG_MANAGEMENT_WEBSOCKET_ENABLED=false LOG_MANAGEMENT_WEBSOCKET_PORT=6001
ποΈ Dashboard & Monitoring
π Web Dashboard
Access your beautiful dashboard at: https://yourapp.com/log-management
|
π Analytics View
|
π Search & Filter
|
π― Key Metrics
| Metric | Description | Alert Threshold |
|---|---|---|
| Error Rate | Errors per minute | > 10/min |
| Response Time | Avg. response time | > 2000ms |
| Memory Usage | Peak memory usage | > 80% |
| Queue Depth | Pending jobs | > 1000 |
π± Mobile Dashboard
The dashboard is fully responsive and works perfectly on:
- π± Mobile phones
- π± Tablets
- π» Desktop computers
- π₯οΈ Large monitors
π API Reference
π Authentication
Include your API key in requests using any method:
# Method 1: Header (Recommended) curl -H "X-Log-Management-Key: lm_your_api_key" \ https://yourapp.com/log-management/api/logs # Method 2: Bearer Token curl -H "Authorization: Bearer lm_your_api_key" \ https://yourapp.com/log-management/api/logs # Method 3: Query Parameter curl "https://yourapp.com/log-management/api/logs?key=lm_your_api_key"
π Core Endpoints
π Log Management
# Get paginated logs GET /log-management/api/logs ?level[]=error&level[]=critical # Filter by levels &search=database # Search in messages &from=2024-01-01&to=2024-01-31 # Date range &user_id=123 # Filter by user &per_page=50&page=2 # Pagination # Get specific log entry GET /log-management/api/logs/{id} # Get log statistics GET /log-management/api/stats ?period=24h # 1h, 24h, 7d, 30d &group_by=level # level, channel, hour # Export logs GET /log-management/api/logs/export ?format=csv # csv, json, excel &filters[level]=error
π Notification Management
# Test notifications POST /log-management/api/notifications/test { "channel": "slack", # email, slack, webhook, all "message": "Test notification", "level": "error" } # Get notification settings GET /log-management/api/notifications/settings # Update notification settings PUT /log-management/api/notifications/settings { "email": { "enabled": true, "recipients": ["admin@app.com"] } } # Get notification history GET /log-management/api/notifications/history
βοΈ System Management
# System health check GET /log-management/api/health # System information GET /log-management/api/system # Clear old logs DELETE /log-management/api/logs/cleanup ?days=7 # Older than X days &level=debug # Specific level only # Get configuration GET /log-management/api/config
π Response Examples
π Log List Response
{
"data": [
{
"id": 1,
"level": "error",
"message": "Database connection failed",
"channel": "database",
"context": {
"user_id": 123,
"connection": "mysql",
"query_time": 5.2
},
"extra": {
"file": "/app/Database/Connection.php",
"line": 157
},
"formatted": "[2024-01-15 10:30:15] production.ERROR: Database connection failed",
"timestamp": "2024-01-15T10:30:15.000000Z",
"created_at": "2024-01-15T10:30:15.000000Z"
}
],
"meta": {
"current_page": 1,
"last_page": 10,
"per_page": 20,
"total": 195,
"from": 1,
"to": 20
},
"links": {
"first": "/api/logs?page=1",
"last": "/api/logs?page=10",
"prev": null,
"next": "/api/logs?page=2"
}
}
π Statistics Response
{
"summary": {
"total_logs": 12450,
"total_today": 234,
"total_errors": 45,
"error_rate": 3.6,
"avg_response_time": 245.7
},
"level_breakdown": {
"emergency": 0,
"alert": 2,
"critical": 5,
"error": 38,
"warning": 156,
"notice": 234,
"info": 1890,
"debug": 10125
},
"hourly_stats": [
{
"hour": "2024-01-15T10:00:00Z",
"count": 45,
"errors": 3,
"avg_response_time": 234.5
}
],
"top_errors": [
{
"message": "Database connection timeout",
"count": 23,
"last_seen": "2024-01-15T10:25:00Z"
}
]
}
π Notification Channels
π§ Email Notifications
Rich HTML emails with detailed log information:
// Custom email template LogManagement::emailTemplate('critical-alert', function ($log) { return [ 'subject' => "π¨ Critical Error: {$log['message']}", 'template' => 'emails.critical-alert', 'data' => [ 'log' => $log, 'dashboard_url' => route('log-management.dashboard'), 'action_required' => true ] ]; });
π¬ Slack Integration
Beautiful Slack messages with contextual information:
{
"attachments": [
{
"color": "danger",
"title": "π¨ Critical Error Detected",
"text": "Database connection failed",
"fields": [
{"title": "Level", "value": "error", "short": true},
{"title": "Environment", "value": "production", "short": true},
{"title": "User ID", "value": "123", "short": true},
{"title": "Time", "value": "2024-01-15 10:30:15", "short": true}
],
"actions": [
{
"type": "button",
"text": "View Dashboard",
"url": "https://yourapp.com/log-management"
}
]
}
]
}
π Webhook Notifications
Flexible webhook payloads for external integrations:
{
"event": "log.error",
"timestamp": "2024-01-15T10:30:15Z",
"environment": "production",
"log": {
"id": 1,
"level": "error",
"message": "Database connection failed",
"context": {"user_id": 123},
"metadata": {
"file": "/app/Database/Connection.php",
"line": 157
}
},
"application": {
"name": "Your App",
"url": "https://yourapp.com",
"version": "1.2.3"
},
"signature": "sha256=calculated_signature"
}
π¨ Custom Channels
Create custom notification channels:
use Fulgid\LogManagement\Notifications\Contracts\NotificationChannelInterface; class DiscordChannel implements NotificationChannelInterface { public function send(array $logData): bool { $webhook = config('services.discord.webhook_url'); $payload = [ 'content' => "π¨ **{$logData['level']}**: {$logData['message']}", 'embeds' => [ [ 'color' => $this->getColorForLevel($logData['level']), 'timestamp' => $logData['timestamp'], 'fields' => $this->formatFields($logData) ] ] ]; return Http::post($webhook, $payload)->successful(); } private function getColorForLevel($level): int { return match($level) { 'emergency', 'alert', 'critical' => 0xFF0000, // Red 'error' => 0xFF4500, // Orange Red 'warning' => 0xFFA500, // Orange 'notice' => 0x0000FF, // Blue 'info' => 0x00FF00, // Green 'debug' => 0x808080, // Gray default => 0x000000 // Black }; } private function formatFields(array $logData): array { $fields = [ ['name' => 'Level', 'value' => ucfirst($logData['level']), 'inline' => true], ['name' => 'Environment', 'value' => $logData['environment'] ?? 'Unknown', 'inline' => true] ]; if (!empty($logData['context']['user_id'])) { $fields[] = ['name' => 'User ID', 'value' => $logData['context']['user_id'], 'inline' => true]; } return $fields; } } // Register in AppServiceProvider LogManagement::addChannel('discord', new DiscordChannel());
π‘ Real-time Streaming
π Server-Sent Events (SSE)
Connect to real-time log streams:
const eventSource = new EventSource('/log-management/stream?key=your-api-key'); eventSource.onmessage = function(event) { const logData = JSON.parse(event.data); // Handle different event types switch(logData.type) { case 'log': displayLog(logData); break; case 'stats': updateMetrics(logData); break; case 'heartbeat': updateConnectionStatus('connected'); break; } }; eventSource.onerror = function(event) { updateConnectionStatus('disconnected'); console.error('SSE connection error:', event); };
ποΈ Stream Filtering
Filter streams by various criteria:
// Filter by log levels const errorStream = new EventSource( '/log-management/stream?level[]=error&level[]=critical&key=your-api-key' ); // Filter by user or context const userStream = new EventSource( '/log-management/stream?user_id=123&key=your-api-key' ); // Include recent history const streamWithHistory = new EventSource( '/log-management/stream?include_recent=50&key=your-api-key' ); // Multiple filters const filteredStream = new EventSource( '/log-management/stream?level[]=error&channel=database&since=1h&key=your-api-key' );
π¨ Frontend Integration
π₯οΈ Complete Dashboard Example
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>π¨ Log Management Dashboard</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <style> .log-entry { @apply mb-2 p-3 rounded-lg border-l-4 transition-all duration-200 hover:shadow-md; } .log-emergency { @apply border-red-600 bg-red-50 text-red-900; } .log-alert { @apply border-red-500 bg-red-50 text-red-800; } .log-critical { @apply border-red-400 bg-red-50 text-red-700; } .log-error { @apply border-red-300 bg-red-50 text-red-600; } .log-warning { @apply border-yellow-400 bg-yellow-50 text-yellow-800; } .log-notice { @apply border-blue-400 bg-blue-50 text-blue-800; } .log-info { @apply border-blue-300 bg-blue-50 text-blue-700; } .log-debug { @apply border-gray-300 bg-gray-50 text-gray-700; } .status-indicator { @apply inline-block w-3 h-3 rounded-full mr-2; } .status-connected { @apply bg-green-500 animate-pulse; } .status-disconnected { @apply bg-red-500; } .fade-in { animation: fadeIn 0.5s ease-in; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .slide-out { animation: slideOut 0.3s ease-out forwards; } @keyframes slideOut { to { transform: translateX(100%); opacity: 0; } } </style> </head> <body class="bg-gray-100 font-sans"> <div class="container mx-auto px-4 py-8"> <!-- Header --> <div class="mb-8"> <h1 class="text-3xl font-bold text-gray-900 mb-2"> π¨ Log Management Dashboard </h1> <div class="flex items-center"> <span class="status-indicator status-connected" id="connection-status"></span> <span class="text-sm text-gray-600" id="connection-text">Connected</span> <span class="ml-4 text-sm text-gray-500" id="last-update">Last update: Never</span> </div> </div> <!-- Metrics Cards --> <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8"> <div class="bg-white p-6 rounded-lg shadow"> <div class="flex items-center"> <div class="text-3xl">π</div> <div class="ml-4"> <p class="text-sm font-medium text-gray-600">Total Logs</p> <p class="text-2xl font-bold text-gray-900" id="total-logs">0</p> </div> </div> </div> <div class="bg-white p-6 rounded-lg shadow"> <div class="flex items-center"> <div class="text-3xl">π¨</div> <div class="ml-4"> <p class="text-sm font-medium text-gray-600">Errors Today</p> <p class="text-2xl font-bold text-red-600" id="errors-today">0</p> </div> </div> </div> <div class="bg-white p-6 rounded-lg shadow"> <div class="flex items-center"> <div class="text-3xl">β‘</div> <div class="ml-4"> <p class="text-sm font-medium text-gray-600">Error Rate</p> <p class="text-2xl font-bold text-orange-600" id="error-rate">0%</p> </div> </div> </div> <div class="bg-white p-6 rounded-lg shadow"> <div class="flex items-center"> <div class="text-3xl">π</div> <div class="ml-4"> <p class="text-sm font-medium text-gray-600">Connections</p> <p class="text-2xl font-bold text-green-600" id="connections">1</p> </div> </div> </div> </div> <!-- Charts --> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> <div class="bg-white p-6 rounded-lg shadow"> <h3 class="text-lg font-semibold mb-4">π Log Volume (Last 24h)</h3> <canvas id="volume-chart" width="400" height="200"></canvas> </div> <div class="bg-white p-6 rounded-lg shadow"> <h3 class="text-lg font-semibold mb-4">π© Log Levels</h3> <canvas id="levels-chart" width="400" height="200"></canvas> </div> </div> <!-- Controls --> <div class="bg-white p-6 rounded-lg shadow mb-6"> <div class="flex flex-wrap items-center gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Filter Level:</label> <select id="level-filter" class="border border-gray-300 rounded-md px-3 py-2"> <option value="">All Levels</option> <option value="emergency">Emergency</option> <option value="alert">Alert</option> <option value="critical">Critical</option> <option value="error">Error</option> <option value="warning">Warning</option> <option value="notice">Notice</option> <option value="info">Info</option> <option value="debug">Debug</option> </select> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Search:</label> <input type="text" id="search-input" placeholder="Search logs..." class="border border-gray-300 rounded-md px-3 py-2"> </div> <div class="flex items-end gap-2"> <button onclick="clearLogs()" class="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600"> ποΈ Clear </button> <button onclick="exportLogs()" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600"> π₯ Export </button> <button onclick="toggleStream()" id="stream-toggle" class="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600"> βΈοΈ Pause </button> </div> </div> </div> <!-- Live Logs --> <div class="bg-white rounded-lg shadow"> <div class="p-6 border-b border-gray-200"> <h3 class="text-lg font-semibold">π Live Logs</h3> <p class="text-sm text-gray-600">Real-time log entries as they happen</p> </div> <div id="logs-container" class="p-6 max-h-96 overflow-y-auto"> <div class="text-center text-gray-500 py-8"> <div class="text-4xl mb-2">π</div> <p>Waiting for logs...</p> </div> </div> </div> </div> <script> // Configuration const API_KEY = 'your-api-key-here'; const BASE_URL = '/log-management'; // State let eventSource = null; let isStreamActive = true; let logs = []; let filteredLogs = []; let metrics = { totalLogs: 0, errorsToday: 0, errorRate: 0, connections: 1 }; // DOM Elements const logsContainer = document.getElementById('logs-container'); const connectionStatus = document.getElementById('connection-status'); const connectionText = document.getElementById('connection-text'); const lastUpdate = document.getElementById('last-update'); const levelFilter = document.getElementById('level-filter'); const searchInput = document.getElementById('search-input'); const streamToggle = document.getElementById('stream-toggle'); // Metrics Elements const totalLogsEl = document.getElementById('total-logs'); const errorsTodayEl = document.getElementById('errors-today'); const errorRateEl = document.getElementById('error-rate'); const connectionsEl = document.getElementById('connections'); // Charts let volumeChart, levelsChart; // Initialize document.addEventListener('DOMContentLoaded', function() { initializeCharts(); connectToStream(); loadInitialData(); setupEventListeners(); }); function initializeCharts() { // Volume Chart const volumeCtx = document.getElementById('volume-chart').getContext('2d'); volumeChart = new Chart(volumeCtx, { type: 'line', data: { labels: [], datasets: [{ label: 'Log Volume', data: [], borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', borderWidth: 2, fill: true, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, grid: { color: 'rgba(0, 0, 0, 0.1)' } }, x: { grid: { color: 'rgba(0, 0, 0, 0.1)' } } }, plugins: { legend: { display: false } } } }); // Levels Chart const levelsCtx = document.getElementById('levels-chart').getContext('2d'); levelsChart = new Chart(levelsCtx, { type: 'doughnut', data: { labels: ['Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Info', 'Debug'], datasets: [{ data: [0, 0, 0, 0, 0, 0, 0, 0], backgroundColor: [ '#DC2626', // Emergency - Red '#EF4444', // Alert - Light Red '#F87171', // Critical - Lighter Red '#FCA5A5', // Error - Pink '#FBBF24', // Warning - Yellow '#60A5FA', // Notice - Blue '#34D399', // Info - Green '#9CA3AF' // Debug - Gray ], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { usePointStyle: true, padding: 20, font: { size: 12 } } } } } }); } function connectToStream() { if (eventSource) { eventSource.close(); } const url = `${BASE_URL}/stream?key=${API_KEY}`; eventSource = new EventSource(url); eventSource.onopen = function() { updateConnectionStatus('connected'); console.log('β Connected to log stream'); }; eventSource.onmessage = function(event) { try { const data = JSON.parse(event.data); handleStreamMessage(data); } catch (error) { console.error('Error parsing stream data:', error); } }; eventSource.onerror = function(event) { updateConnectionStatus('disconnected'); console.error('β Stream connection error:', event); // Attempt to reconnect after 5 seconds setTimeout(() => { if (isStreamActive) { connectToStream(); } }, 5000); }; } function handleStreamMessage(data) { updateLastUpdate(); switch(data.type) { case 'log': addLogEntry(data); break; case 'stats': updateMetrics(data); break; case 'heartbeat': // Keep connection alive break; default: console.log('Unknown message type:', data.type); } } function addLogEntry(logData) { logs.unshift(logData); // Keep only last 100 logs in memory if (logs.length > 100) { logs = logs.slice(0, 100); } applyFilters(); renderLogs(); // Update volume chart updateVolumeChart(); // Update levels chart updateLevelsChart(); } function updateMetrics(data) { metrics = { ...metrics, ...data }; totalLogsEl.textContent = formatNumber(metrics.totalLogs); errorsTodayEl.textContent = formatNumber(metrics.errorsToday); errorRateEl.textContent = `${metrics.errorRate.toFixed(1)}%`; connectionsEl.textContent = formatNumber(metrics.connections); } function updateVolumeChart() { const now = new Date(); const timeLabel = now.toLocaleTimeString(); // Add new data point volumeChart.data.labels.push(timeLabel); volumeChart.data.datasets[0].data.push(logs.length); // Keep only last 20 data points if (volumeChart.data.labels.length > 20) { volumeChart.data.labels.shift(); volumeChart.data.datasets[0].data.shift(); } volumeChart.update('none'); } function updateLevelsChart() { const levelCounts = { emergency: 0, alert: 0, critical: 0, error: 0, warning: 0, notice: 0, info: 0, debug: 0 }; logs.forEach(log => { if (levelCounts.hasOwnProperty(log.level)) { levelCounts[log.level]++; } }); levelsChart.data.datasets[0].data = Object.values(levelCounts); levelsChart.update('none'); } function renderLogs() { if (filteredLogs.length === 0) { logsContainer.innerHTML = ` <div class="text-center text-gray-500 py-8"> <div class="text-4xl mb-2">π</div> <p>No logs match your filters</p> </div> `; return; } const logsHtml = filteredLogs.slice(0, 50).map(log => createLogEntryHtml(log)).join(''); logsContainer.innerHTML = logsHtml; } function createLogEntryHtml(log) { const timestamp = new Date(log.timestamp).toLocaleString(); const levelIcon = getLevelIcon(log.level); const contextJson = log.context ? JSON.stringify(log.context, null, 2) : '{}'; return ` <div class="log-entry log-${log.level} fade-in" data-log-id="${log.id}"> <div class="flex items-start justify-between"> <div class="flex-1"> <div class="flex items-center mb-2"> <span class="text-lg mr-2">${levelIcon}</span> <span class="font-semibold text-sm uppercase tracking-wide">${log.level}</span> <span class="ml-2 text-xs text-gray-500">${timestamp}</span> ${log.channel ? `<span class="ml-2 text-xs bg-gray-200 px-2 py-1 rounded">${log.channel}</span>` : ''} </div> <div class="mb-2"> <p class="font-medium">${escapeHtml(log.message)}</p> </div> ${Object.keys(log.context || {}).length > 0 ? ` <details class="mt-2"> <summary class="cursor-pointer text-xs text-gray-600 hover:text-gray-800"> View Context </summary> <pre class="mt-2 text-xs bg-gray-100 p-2 rounded overflow-x-auto"><code>${escapeHtml(contextJson)}</code></pre> </details> ` : ''} </div> <button onclick="removeLogEntry('${log.id}')" class="ml-4 text-gray-400 hover:text-red-500 transition-colors"> β </button> </div> </div> `; } function getLevelIcon(level) { const icons = { emergency: 'π', alert: 'π¨', critical: 'π₯', error: 'β', warning: 'β οΈ', notice: 'βΉοΈ', info: 'π‘', debug: 'π' }; return icons[level] || 'π'; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function applyFilters() { filteredLogs = logs.filter(log => { // Level filter const selectedLevel = levelFilter.value; if (selectedLevel && log.level !== selectedLevel) { return false; } // Search filter const searchTerm = searchInput.value.toLowerCase(); if (searchTerm && !log.message.toLowerCase().includes(searchTerm)) { return false; } return true; }); } function setupEventListeners() { levelFilter.addEventListener('change', () => { applyFilters(); renderLogs(); }); searchInput.addEventListener('input', debounce(() => { applyFilters(); renderLogs(); }, 300)); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey) { switch(e.key) { case 'k': e.preventDefault(); searchInput.focus(); break; case 'r': e.preventDefault(); location.reload(); break; } } }); } function updateConnectionStatus(status) { connectionStatus.className = `status-indicator status-${status}`; connectionText.textContent = status === 'connected' ? 'Connected' : 'Disconnected'; } function updateLastUpdate() { lastUpdate.textContent = `Last update: ${new Date().toLocaleTimeString()}`; } function formatNumber(num) { if (num >= 1000000) { return (num / 1000000).toFixed(1) + 'M'; } else if (num >= 1000) { return (num / 1000).toFixed(1) + 'K'; } return num.toString(); } function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function loadInitialData() { fetch(`${BASE_URL}/api/logs?key=${API_KEY}&per_page=20`) .then(response => response.json()) .then(data => { logs = data.data || []; applyFilters(); renderLogs(); }) .catch(error => console.error('Error loading initial data:', error)); fetch(`${BASE_URL}/api/stats?key=${API_KEY}`) .then(response => response.json()) .then(data => { updateMetrics(data.summary || {}); }) .catch(error => console.error('Error loading stats:', error)); } // Control Functions function clearLogs() { if (confirm('Are you sure you want to clear all displayed logs?')) { logs = []; filteredLogs = []; renderLogs(); } } function exportLogs() { const data = filteredLogs.map(log => ({ timestamp: log.timestamp, level: log.level, message: log.message, channel: log.channel, context: log.context })); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `logs-${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } function toggleStream() { isStreamActive = !isStreamActive; if (isStreamActive) { connectToStream(); streamToggle.innerHTML = 'βΈοΈ Pause'; streamToggle.className = 'bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600'; } else { if (eventSource) { eventSource.close(); } updateConnectionStatus('disconnected'); streamToggle.innerHTML = 'βΆοΈ Resume'; streamToggle.className = 'bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600'; } } function removeLogEntry(logId) { const logElement = document.querySelector(`[data-log-id="${logId}"]`); if (logElement) { logElement.classList.add('slide-out'); setTimeout(() => { logs = logs.filter(log => log.id !== logId); applyFilters(); renderLogs(); }, 300); } } // Auto-reconnect on page visibility change document.addEventListener('visibilitychange', () => { if (!document.hidden && isStreamActive && (!eventSource || eventSource.readyState !== EventSource.OPEN)) { connectToStream(); } }); // Cleanup on page unload window.addEventListener('beforeunload', () => { if (eventSource) { eventSource.close(); } }); </script> </body> </html>
π οΈ Commands & Tools
π Available Commands
π§ Installation & Setup Commands
# Run the complete installation wizard php artisan log-management:install # Install with custom options php artisan log-management:install --force --skip-migrations # Publish configuration files only php artisan vendor:publish --tag=log-management-config # Publish views for customization php artisan vendor:publish --tag=log-management-views # Run migrations manually php artisan migrate --path=vendor/fulgid/log-management/database/migrations
π§ͺ Testing & Debugging Commands
# Test all notification channels php artisan log-management:test --all # Test specific notification channel php artisan log-management:test --channel=slack php artisan log-management:test --channel=email php artisan log-management:test --channel=webhook # Generate test logs for development php artisan log-management:generate-test-logs --count=100 --level=error # Check system health and configuration php artisan log-management:health # Debug configuration issues php artisan log-management:debug --verbose
ποΈ Database Management Commands
# Clean up old log entries php artisan log-management:cleanup --days=30 # Clean up specific log levels php artisan log-management:cleanup --level=debug --days=7 # Optimize database performance php artisan log-management:optimize # Backup log data php artisan log-management:backup --path=/backups/logs # Restore log data php artisan log-management:restore --file=/backups/logs/backup.sql
π Statistics & Reports
# Generate log statistics report php artisan log-management:stats --period=7d --format=table # Export logs to various formats php artisan log-management:export --format=csv --from=2024-01-01 --to=2024-01-31 php artisan log-management:export --format=json --level=error php artisan log-management:export --format=excel --user-id=123 # Generate trending analysis php artisan log-management:trends --period=30d --group-by=level # Performance analysis php artisan log-management:performance --analyze-slow-queries
π Security & Maintenance
# Rotate API keys php artisan log-management:rotate-keys --backup-old # Verify security configuration php artisan log-management:security-check # Update package configurations php artisan log-management:update-config # Clear all caches php artisan log-management:clear-cache
π― Command Examples
# Complete health check with detailed output php artisan log-management:health --detailed # Generate 1000 test logs for load testing php artisan log-management:generate-test-logs \ --count=1000 \ --levels=error,warning,info \ --with-context \ --simulate-users # Export last week's error logs as Excel php artisan log-management:export \ --format=excel \ --level=error \ --from="1 week ago" \ --to=today \ --output=/reports/weekly-errors.xlsx # Test Slack integration with custom message php artisan log-management:test \ --channel=slack \ --message="π§ͺ Testing from production server" \ --level=warning # Clean up debug logs older than 3 days php artisan log-management:cleanup \ --level=debug \ --days=3 \ --confirm \ --verbose
π Deployment
π³ Docker Deployment
π¦ Docker Configuration
# Dockerfile FROM php:8.2-fpm-alpine # Install required extensions RUN apk add --no-cache \ nginx \ supervisor \ curl \ && docker-php-ext-install pdo pdo_mysql # Copy application COPY . /var/www/html WORKDIR /var/www/html # Install Composer dependencies RUN composer install --no-dev --optimize-autoloader # Set permissions RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache # Copy configuration files COPY docker/nginx.conf /etc/nginx/nginx.conf COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf EXPOSE 80 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
# docker-compose.yml version: '3.8' services: app: build: . ports: - "80:80" environment: - LOG_MANAGEMENT_ENABLED=true - LOG_MANAGEMENT_ENVIRONMENTS=production - DB_CONNECTION=mysql - DB_HOST=db - DB_DATABASE=laravel - DB_USERNAME=laravel - DB_PASSWORD=secret - REDIS_HOST=redis volumes: - ./storage/logs:/var/www/html/storage/logs depends_on: - db - redis restart: unless-stopped db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: laravel MYSQL_USER: laravel MYSQL_PASSWORD: secret volumes: - mysql_data:/var/lib/mysql restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped queue: build: . command: php artisan queue:work --sleep=3 --tries=3 environment: - LOG_MANAGEMENT_ENABLED=true - DB_CONNECTION=mysql - DB_HOST=db - REDIS_HOST=redis depends_on: - db - redis restart: unless-stopped volumes: mysql_data: redis_data:
βΈοΈ Kubernetes Deployment
βοΈ Kubernetes Manifests
# k8s/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: log-management-config data: .env: | LOG_MANAGEMENT_ENABLED=true LOG_MANAGEMENT_ENVIRONMENTS=production LOG_MANAGEMENT_SSE_ENABLED=true LOG_MANAGEMENT_RATE_LIMIT_MAX=100 LOG_MANAGEMENT_RETENTION_DAYS=30 --- # k8s/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: log-management-app spec: replicas: 3 selector: matchLabels: app: log-management template: metadata: labels: app: log-management spec: containers: - name: app image: your-registry/log-management:latest ports: - containerPort: 80 env: - name: LOG_MANAGEMENT_API_KEY_1 valueFrom: secretKeyRef: name: log-management-secrets key: api-key-1 volumeMounts: - name: config mountPath: /var/www/html/.env subPath: .env - name: storage mountPath: /var/www/html/storage resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" volumes: - name: config configMap: name: log-management-config - name: storage persistentVolumeClaim: claimName: log-management-storage --- # k8s/service.yaml apiVersion: v1 kind: Service metadata: name: log-management-service spec: selector: app: log-management ports: - port: 80 targetPort: 80 type: ClusterIP --- # k8s/ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: log-management-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - logs.yourapp.com secretName: log-management-tls rules: - host: logs.yourapp.com http: paths: - path: / pathType: Prefix backend: service: name: log-management-service port: number: 80
π Production Optimizations
β‘ Performance Optimizations
# Optimize Composer autoloader composer install --no-dev --optimize-autoloader --classmap-authoritative # Cache configuration php artisan config:cache php artisan route:cache php artisan view:cache # Optimize for production php artisan optimize # Set up log rotation echo "*/5 * * * * php /var/www/html/artisan log-management:cleanup --days=30" | crontab - # Configure Redis for sessions and cache # In .env: SESSION_DRIVER=redis CACHE_DRIVER=redis QUEUE_CONNECTION=redis
# nginx.conf optimization server { listen 80; server_name logs.yourapp.com; root /var/www/html/public; index index.php; # Enable gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; # SSE specific configuration location /log-management/stream { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_buffering off; proxy_read_timeout 86400; } # Static assets caching location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } # PHP processing location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; fastcgi_read_timeout 300; } }
π Troubleshooting
π¨ Common Issues
π§ Installation Issues
Issue: Package installation fails with dependency conflicts
# Solution: Update Composer and try again composer self-update composer update --with-dependencies # Or install with specific Laravel version composer require fulgid/log-management --with-laravel=^10.0
Issue: Migration fails with table already exists error
# Solution: Check and rollback if necessary
php artisan migrate:status
php artisan migrate:rollback --step=1
php artisan log-management:install --skip-migrations
Issue: API keys not generating during installation
# Solution: Generate manually
php artisan log-management:rotate-keys --force
π‘ Streaming Issues
Issue: SSE connection keeps dropping
// Solution: Implement robust reconnection logic function connectWithRetry(maxRetries = 5) { let retryCount = 0; function connect() { const eventSource = new EventSource('/log-management/stream?key=' + API_KEY); eventSource.onerror = function() { eventSource.close(); if (retryCount < maxRetries) { retryCount++; const delay = Math.pow(2, retryCount) * 1000; // Exponential backoff setTimeout(connect, delay); } }; return eventSource; } return connect(); }
Issue: High memory usage with many connections
# Solution: Adjust SSE settings LOG_MANAGEMENT_SSE_MAX_CONNECTIONS=50 LOG_MANAGEMENT_SSE_BUFFER_SIZE=512 LOG_MANAGEMENT_SSE_HEARTBEAT_INTERVAL=60
π Notification Issues
Issue: Slack notifications not working
# Debug Slack webhook php artisan log-management:test --channel=slack --debug # Check webhook URL format # Correct: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX # Incorrect: https://hooks.slack.com/workflows/...
Issue: Email notifications marked as spam
# Solution: Configure SPF, DKIM, and DMARC records # Use a reputable email service like SendGrid or Mailgun MAIL_MAILER=smtp MAIL_HOST=smtp.sendgrid.net MAIL_PORT=587 MAIL_USERNAME=apikey MAIL_PASSWORD=your_sendgrid_api_key
Issue: Rate limiting blocking notifications
# Solution: Adjust rate limits or implement smarter throttling LOG_MANAGEMENT_RATE_LIMIT_MAX=100 LOG_MANAGEMENT_RATE_LIMIT_WINDOW=60 # Or disable for critical errors LOG_MANAGEMENT_RATE_LIMIT_BYPASS_LEVELS=emergency,alert,critical
π Performance Issues
Issue: Dashboard loading slowly
# Solution: Optimize database queries php artisan log-management:optimize # Add database indexes php artisan make:migration add_indexes_to_logs_table
// In migration Schema::table('log_management_logs', function (Blueprint $table) { $table->index(['level', 'created_at']); $table->index(['user_id', 'created_at']); $table->index('channel'); });
Issue: High CPU usage during log processing
# Solution: Enable async processing and increase batch size LOG_MANAGEMENT_ASYNC_PROCESSING=true LOG_MANAGEMENT_BATCH_SIZE=500 LOG_MANAGEMENT_MEMORY_LIMIT=512M # Use Redis for queues QUEUE_CONNECTION=redis
Issue: Database growing too large
# Solution: Implement aggressive cleanup and archiving php artisan log-management:cleanup --days=7 --level=debug php artisan log-management:cleanup --days=30 --exclude-levels=error,critical # Set up automated cleanup */0 2 * * * php /var/www/html/artisan log-management:cleanup --days=30 --quiet
π οΈ Debug Mode
Enable comprehensive debugging:
LOG_MANAGEMENT_DEBUG=true LOG_MANAGEMENT_DEBUG_QUERIES=true LOG_MANAGEMENT_DEBUG_NOTIFICATIONS=true
# View debug logs tail -f storage/logs/log-management-debug.log # Get detailed system information php artisan log-management:debug --system-info # Test all components php artisan log-management:health --comprehensive
π Getting Help
Community Support:
- π¬ GitHub Discussions
- π Bug Reports
Professional Support:
- π§ Email: support@fulgid.in
- πΌ Enterprise: enterprise@fulgid.in
- π― Custom Development: dev@fulgid.in
π€ Contributing
We welcome contributions! Here's how you can help:
π οΈ Development Setup
# Clone the repository git clone https://github.com/navinnm/log-management.git cd log-management # Install dependencies composer install npm install # Set up test environment cp .env.testing.example .env.testing php artisan key:generate --env=testing # Run tests vendor/bin/phpunit npm run test
π Contribution Guidelines
π― Code Standards
# Format code with PHP CS Fixer vendor/bin/php-cs-fixer fix # Run static analysis with PHPStan vendor/bin/phpstan analyse # Check code quality with PHPMD vendor/bin/phpmd src text cleancode,codesize,controversial,design,naming,unusedcode
Code Style Requirements:
- Follow PSR-12 coding standards
- Write comprehensive PHPDoc comments
- Include unit tests for new features
- Maintain backward compatibility
- Use semantic versioning for releases
π§ͺ Testing Requirements
// Example test case class LogManagementTest extends TestCase { use RefreshDatabase; /** @test */ public function it_can_store_log_entries() { // Arrange $logData = [ 'level' => 'error', 'message' => 'Test error message', 'context' => ['user_id' => 123] ]; // Act LogManagement::log($logData); // Assert $this->assertDatabaseHas('log_management_logs', [ 'level' => 'error', 'message' => 'Test error message' ]); } /** @test */ public function it_sends_notifications_for_critical_errors() { // Arrange Notification::fake(); // Act LogManagement::log([ 'level' => 'critical', 'message' => 'Critical system error' ]); // Assert Notification::assertSent(CriticalErrorNotification::class); } }
Testing Checklist:
- β Unit tests for all new methods
- β Integration tests for complex features
- β Browser tests for dashboard functionality
- β Performance tests for high-load scenarios
- β Security tests for API endpoints
π Pull Request Process
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
composer test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
π― Areas for Contribution
High Priority:
- π§ Additional notification channels (Teams, Telegram, etc.)
- π Advanced analytics and reporting features
- π¨ Dashboard themes and customization options
- π Internationalization (i18n) support
- π± Mobile app for log monitoring
Medium Priority:
- π Advanced search with Elasticsearch integration
- π Machine learning for anomaly detection
- π Advanced security features (2FA, SSO)
- π Custom chart types and visualizations
- π Performance optimizations
Documentation:
- π Tutorial videos and guides
- π Translation of documentation
- π‘ Usage examples and recipes
- ποΈ Architecture deep-dives
- π Best practices guides
π License
This package is open-sourced software licensed under the MIT license.
π’ Commercial License
For commercial projects requiring additional features, support, or custom development:
Pro License Features:
- π Priority email support
- π Advanced analytics dashboard
- π Enhanced security features
- π¨ White-label customization
- π± Mobile companion app
Enterprise License Features:
- π Phone support & SLA guarantees
- ποΈ Custom integrations development
- π Team training sessions
- π§ On-premise deployment assistance
- π Custom reporting & analytics
Contact enterprise@fulgid.com for licensing information.
Integrations:
- π Laravel Telescope
- π Laravel Horizon
- π Bugsnag
- π New Relic
- π Elasticsearch
- π Grafana
π‘ Tips & Tricks
π Performance Tips
// Use log contexts for better filtering Log::error('Payment failed', [ 'user_id' => $user->id, 'payment_id' => $payment->id, 'gateway' => 'stripe', 'amount' => $payment->amount ]); // Batch log operations for better performance LogManagement::batch([ ['level' => 'info', 'message' => 'Batch operation 1'], ['level' => 'info', 'message' => 'Batch operation 2'], ['level' => 'info', 'message' => 'Batch operation 3'], ]); // Use conditional logging to reduce noise if (app()->environment('production')) { Log::channel('log-management')->info('Production-only log'); }
π§ Advanced Configuration
// Custom log processor LogManagement::addProcessor(function ($record) { $record['extra']['server_id'] = gethostname(); $record['extra']['memory_usage'] = memory_get_usage(true); return $record; }); // Custom alert rules LogManagement::addAlertRule('high_error_rate', function ($stats) { return $stats['error_rate'] > 5.0; // Alert if error rate > 5% }); // Dynamic notification routing LogManagement::addNotificationRouter(function ($logData) { if ($logData['level'] === 'critical') { return ['slack', 'email', 'webhook']; } elseif ($logData['level'] === 'error') { return ['slack']; } return []; });
π Conclusion
Thank you for choosing Laravel Log Management! This package represents hundreds of hours of development focused on providing the best possible logging experience for Laravel applications.
π What's Next?
- Get Started: Follow our Quick Start Guide to get up and running in minutes
- Customize: Explore our Configuration Options to tailor the package to your needs
- Integrate: Set up Real-time Streaming for live monitoring
- Optimize: Implement Performance Best Practices for production
- Contribute: Join our Community and help make the package even better
π¬ Stay Connected
- π¦ Follow us on Twitter
- πΌ Connect on LinkedIn
- π§ Subscribe to our Newsletter
- π£οΈ Join our Discord Community
β Show Your Support
If this package helps you build better applications, please consider:
- β Starring the GitHub repository
- π¦ Sharing on social media
- π Writing a blog post about your experience
- π¬ Recommending to colleagues
Built with β€οΈ by the Fulgid Team
π Website β’ π§ Contact β’ π GitHub
Making Laravel development more enjoyable, one package at a time.