Appending means adding to the end of a file without touching what is already there. It is the standard way to write logs.
With fopen and mode a
<?php
file_put_contents("log.txt", "first\n");
$handle = fopen("log.txt", "a");
fwrite($handle, "second\n");
fclose($handle);
echo file_get_contents("log.txt");
first
second
With file_put_contents and FILE_APPEND
This is shorter for one-off lines.
<?php
file_put_contents("log.txt", "first\n");
file_put_contents("log.txt", "second\n", FILE_APPEND);
echo file_get_contents("log.txt");
first
second
Lock the file: LOCK_EX
On a busy site, two visitors may write at the same moment. Their lines can get mixed together. LOCK_EX asks PHP for an exclusive lock, so one writer finishes before the next starts.
<?php
file_put_contents("log.txt", "", LOCK_EX);
file_put_contents("log.txt", "visitor 1\n", FILE_APPEND | LOCK_EX);
file_put_contents("log.txt", "visitor 2\n", FILE_APPEND | LOCK_EX);
echo file_get_contents("log.txt");
visitor 1
visitor 2
Combine flags with the pipe |.
A tiny logger function
<?php
function logMessage(string $message, string $file = "app.log"): void
{
$line = date("Y-m-d H:i:s") . " " . str_replace(["\r", "\n"], " ", $message) . PHP_EOL;
file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
}
logMessage("User signed in");
logMessage("Line one\nforged line two");
echo count(file("app.log")) . " lines";
2 lines
Notice str_replace(["\r", "\n"], " ", ...). If a visitor sends text that contains a line break, they could forge a fake log line. Replace new lines before logging user text. This is called log injection.
Writing at a position
Mode r+ or c lets you write somewhere other than the end, with fseek(). That is rare in everyday PHP. Appending covers most needs.
Log files get big
A log that only ever grows will fill your disk. Rotate it (start a new file each day) or delete old ones. Many hosts use the logrotate tool, and many frameworks have a log library that does this for you.
<?php
$file = "log-" . date("Y-m-d") . ".txt";
echo preg_match('/^log-\d{4}-\d{2}-\d{2}\.txt$/', $file) === 1 ? "daily file name ok" : "bad";
daily file name ok