php 53 lines · 9 steps

How a rotating file logger works in PHP

A logger that caps each file's size and shifts old logs into numbered archives, pruning the excess.

Explained by highlit
1<?php
2 
3final class RotatingFileLogger
4{
5 private const MAX_BYTES = 5 * 1024 * 1024;
6 private const MAX_FILES = 5;
7 
8 public function __construct(private readonly string $path)
9 {
10 }
11 
12 public function write(string $message): void
13 {
14 $this->rotateIfNeeded();
15 
16 $line = sprintf('[%s] %s%s', date('Y-m-d H:i:s'), $message, PHP_EOL);
17 file_put_contents($this->path, $line, FILE_APPEND | LOCK_EX);
18 }
19 
20 private function rotateIfNeeded(): void
21 {
22 if (!is_file($this->path) || filesize($this->path) < self::MAX_BYTES) {
23 return;
24 }
25 
26 $oldest = sprintf('%s.%d', $this->path, self::MAX_FILES);
27 if (is_file($oldest)) {
28 unlink($oldest);
29 }
30 
31 for ($i = self::MAX_FILES - 1; $i >= 1; $i--) {
32 $source = sprintf('%s.%d', $this->path, $i);
33 if (is_file($source)) {
34 rename($source, sprintf('%s.%d', $this->path, $i + 1));
35 }
36 }
37 
38 rename($this->path, $this->path . '.1');
39 
40 $this->pruneByCount();
41 }
42 
43 private function pruneByCount(): void
44 {
45 $archives = glob($this->path . '.*') ?: [];
46 
47 usort($archives, static fn (string $a, string $b): int => filemtime($b) <=> filemtime($a));
48 
49 foreach (array_slice($archives, self::MAX_FILES) as $stale) {
50 @unlink($stale);
51 }
52 }
53}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Rotation keeps log files bounded by size and count so disk usage never grows without limit.
  2. 2Renaming files in reverse index order avoids clobbering an archive before it has been shifted.
  3. 3LOCK_EX on appends guards against interleaved writes when multiple processes log concurrently.

Related explainers

Share this explainer

Here's the card — post it anywhere.

How a rotating file logger works in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code