Every Log::info() opens a file. 10,000 logs = 10,000 I/O operations.
Every call to Log::info(), Log::error(), or any logging method triggers a file write operation. For the 'daily' and 'single' log drivers, this means opening, writing, and closing a file. In a loop with 10,000 iterations, that's 10,000 file I/O operations. Your disk bandwidth is exhausted. Your application slows down. Your users suffer.
| Number of Logs | Without Batching | With Batching | Improvement |
|---|---|---|---|
| 100 logs | ~10-20ms | ~0.5ms | 20-40x faster |
| 1,000 logs | ~100-200ms | ~1ms | 100-200x faster |
| 10,000 logs | ~1-2 seconds | ~5-10ms | 100-200x faster | 100,000 logs | ~10-20 seconds | ~50-100ms | 100-200x faster |
A batch job processing 50,000 records:
Never call Log::*() inside a loop. Collect logs in an array and write them once.
// Process 10,000 users
foreach ($users as $user) {
try {
$user->process();
Log::info("User {$user->id} processed successfully");
} catch (\Exception $e) {
Log::error("User {$user->id} failed: " . $e->getMessage());
}
}
// 10,000 logs = 10,000 I/O operations
// Time: 1-2 seconds just for logging
$infoLogs = [];
$errorLogs = [];
foreach ($users as $user) {
try {
$user->process();
$infoLogs[] = "User {$user->id} processed successfully";
} catch (\Exception $e) {
$errorLogs[] = "User {$user->id} failed: " . $e->getMessage();
}
}
if ($infoLogs) {
Log::info(implode("\n", $infoLogs)); // One write
}
if ($errorLogs) {
Log::error(implode("\n", $errorLogs)); // One write
}
// 2 I/O operations total
// Time: 0.002 seconds
| Driver | I/O per Log | Notes |
|---|---|---|
single (default)
| Open, write, close (3-5 I/O calls) | Worst for high-volume logging |
daily
| Open, write, close + date check | Better than single, still bad for loops |
stack
| Multiple drivers = multiple I/O | Even worse for performance |
syslog
| Pass through to syslog daemon | Better, but still overhead |
errorlog
| PHP error log (usually less I/O) | Good for simple logging |
redis (custom)
| Network I/O to Redis | Fast, but requires Redis |
For high-volume applications, use redis or syslog drivers, or batch your logs. Never use single or daily for high-frequency logging.
Queue workers can process thousands of jobs per minute. Each job that logs = I/O operations. If 100 jobs log 100 times each = 10,000 log writes.
class ProcessUserJob implements ShouldQueue
{
private static array $batchLogs = [];
public function handle(): void
{
try {
$this->user->process();
self::$batchLogs[] = "User {$this->user->id} processed";
} catch (\Exception $e) {
self::$batchLogs[] = "User {$this->user->id} failed: {$e->getMessage()}";
}
}
public function __destruct()
{
// Write all logs when the job finishes (or batch across jobs)
if (!empty(self::$batchLogs)) {
Log::info(implode("\n", self::$batchLogs));
self::$batchLogs = [];
}
}
}
// Laravel 8+ allows adding context to logs
Log::withContext(['user_id' => $userId, 'batch' => $batchId]);
// Now every log in this request/queue job includes this context
Log::info("Processing started"); // Includes user_id and batch
Log::info("Processing completed"); // Includes user_id and batch
// Still batching needed! This doesn't reduce I/O, just adds metadata
emergency
alert
critical
error
warning
notice
info
debug
# .env - Production
LOG_LEVEL=error # Only log errors and above
# .env - Development
LOG_LEVEL=debug # Log everything
# In config/logging.php
'level' => env('LOG_LEVEL', 'error'),
// Custom Redis logger
class RedisLogger
{
public function log($level, $message)
{
Redis::lpush('logs', json_encode([
'level' => $level,
'message' => $message,
'time' => microtime(true),
]));
}
}
// Consumer process writes to file in batches
// Write once per second instead of per log
// Batch insert logs to database
class DbLogger
{
private static array $buffer = [];
public function log($level, $message)
{
self::$buffer[] = compact('level', 'message');
if (count(self::$buffer) >= 100) {
DB::table('logs')->insert(self::$buffer);
self::$buffer = [];
}
}
}
| Pattern | I/O Operations | Time for 10k Logs | Recommendation |
|---|---|---|---|
| Log inside loop | 10,000 | 1-2 seconds | โ NEVER |
| Batch logs in array | 1-2 | 0.001-0.002 seconds | โ For loops |
| Log::info() in production | Per request | Variable | โ ๏ธ Limit usage |
| Redis logging + consumer | Minimal | Very fast | โ For high volume |
Topic 26: Blade View Cache โ Every Blade template is compiled to PHP. Without caching, this happens on every request. How view:cache saves 10-30ms per request.