Есть вещи, которые нужны редко, но когда нужны — ищешь их где попало. Одна из них — дописать строку в конец файла на PHP. Чаще всего это самодельный журнал: что пришло в форму, какой ответ вернул чужой API, на каком шаге упал скрипт.
Делается это одной функцией, но у неё есть пара подвохов, на которых я сам спотыкался: файл появляется не там, где ждёшь, и вместо переноса строки в журнале стоят буквы \n. Ниже — команда, подвохи и готовый пример журнала. Всё проверено на PHP 8.5.
file_put_contents( __DIR__ . '/app.log', $line . "\n", FILE_APPEND | LOCK_EX ); — путь через __DIR__, перенос строки в двойных кавычках, файл не в публичной папке сайта.Команда
file_put_contents( $filename, $text, FILE_APPEND | LOCK_EX );
$filename— путь к файлу. Если файла нет, PHP создаст его сам. Папку — нет.$text— что дописать.FILE_APPEND— дописать в конец, а не перезаписать файл. Без этого флага каждый вызов стирает всё, что было.LOCK_EX— на время записи заблокировать файл, чтобы два одновременных запроса не перемешали строки. Необязательный, но лишним не будет.
Функция возвращает число записанных байт или false, если записать не удалось.
Готовый пример: журнал в формате JSON
Каждая запись — одна строка JSON. Такой журнал удобно читать глазами и легко разбирать скриптом: построчно, без регулярных выражений.
function app_log( string $message, array $data = [] ): void {
$line = json_encode(
[ 'time' => date( 'c' ), 'msg' => $message ] + $data,
JSON_UNESCAPED_UNICODE
);
file_put_contents( __DIR__ . '/../logs/app.log', $line . "\n", FILE_APPEND | LOCK_EX );
}
app_log( 'Заявка с формы', [ 'name' => $_POST['name'] ?? '' ] );
В файле получится:
{"time":"2026-10-25T13:32:16+03:00","msg":"Заявка с формы","name":"Алексей"}
JSON_UNESCAPED_UNICODE нужен, чтобы русские буквы остались буквами, а не превратились в За….
Грабли
'errors.log' считается не от папки скрипта, а от текущей рабочей папки PHP. При запуске из браузера она обычно совпадает с папкой скрипта, но в WordPress, в cron и в консоли — нет. Проверил: скрипт лежит в папке sub, запускаю его из папки уровнем выше — и файл с относительным путём появляется не в sub, а там, откуда запускал. Решение — всегда собирать путь от __DIR__, это папка текущего файла: __DIR__ . '/errors.log'.\n: 'текст\n' запишет обратную косую черту и букву n, и весь журнал окажется одной строкой. Перенос — только в двойных кавычках: "\n", или константа PHP_EOL.Failed to open stream: No such file or directory и false вместо числа байт. Если предупреждения скрыты, журнал просто молча не пишется. Создайте папку заранее или в коде: is_dir( $dir ) || mkdir( $dir, 0755, true );__DIR__ . '/../logs/'), а если хостинг не даёт, — закройте папку от веба: в Apache файл .htaccess со строкой Require all denied.Если нужен просто журнал ошибок
У PHP для этого есть своя функция: error_log( $text . "\n", 3, $filename ). Тройка значит «дописать в указанный файл». Делает то же самое, что file_put_contents с FILE_APPEND, только без блокировки. А в WordPress проще включить встроенный журнал WP_DEBUG_LOG — как это сделать, я писал в посте про почту с сайта.
И не забывайте, что журнал растёт. Для самодельного хватит проверки размера: если filesize() больше нескольких мегабайт, переименовать файл в app.log.1 и начать новый.