From 741dc49d5371b3aa867d45c78974fe0049d6b346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Fri, 29 May 2026 15:10:50 +0200 Subject: [PATCH] fix(logging): keep laravel.log writable across container UIDs (#451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the largest production noise cluster — **8 duplicate issues**, all `UnexpectedValueException: The stream or file "/app/storage/logs/laravel.log" could not be opened in append mode: Failed to open stream: Permission denied`: `PHP-LARAVEL-G`, `PHP-LARAVEL-Z`, `PHP-LARAVEL-12`, `PHP-LARAVEL-2P`, `PHP-LARAVEL-2Q`, `PHP-LARAVEL-2R`, `PHP-LARAVEL-2S`, `PHP-LARAVEL-2T`. ## Root cause `docker/entrypoint.sh` runs `migrate` / `config:cache` / etc. **as root** before supervisor starts. With the default `umask 022`, the first log line written during boot creates `laravel.log` as `root:root 0644`. The persisted `whisper-storage` named volume (`docker-compose.production.yml`) keeps that stale, non-group-writable file across deploys — so the `www-data` php-fpm and queue workers can't append to it and every code path that logs throws. 8 code paths → 8 Sentry issues. ## Fix - **entrypoint**: `umask 0002`, pre-create `laravel.log` group-writable before any artisan command, and normalize it to `0664` in the post-artisan re-apply step. - **config/logging.php**: `permission => 0664` on the `single` and `daily` channels, so files Laravel creates itself (including daily rotation) stay group-writable. Net effect: whoever writes first (root at boot or www-data at runtime), the file is owned `www-data` and group-writable `0664` — no more `EACCES`. ## Tests - `single` and `daily` channels expose `permission => 0664`. - `bash -n docker/entrypoint.sh` passes. ## Follow-up Once deployed and confirmed quiet, the 8 issues should be merged into one canonical group in Sentry. Fixes PHP-LARAVEL-G, PHP-LARAVEL-Z, PHP-LARAVEL-12, PHP-LARAVEL-2P, PHP-LARAVEL-2Q, PHP-LARAVEL-2R, PHP-LARAVEL-2S, PHP-LARAVEL-2T. --- config/logging.php | 2 ++ docker/entrypoint.sh | 10 ++++++++++ tests/Feature/LoggingConfigTest.php | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 tests/Feature/LoggingConfigTest.php diff --git a/config/logging.php b/config/logging.php index 14067a29..8b79b9a9 100644 --- a/config/logging.php +++ b/config/logging.php @@ -62,6 +62,7 @@ return [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), + 'permission' => 0664, 'replace_placeholders' => true, ], @@ -70,6 +71,7 @@ return [ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => env('LOG_DAILY_DAYS', 14), + 'permission' => 0664, 'replace_placeholders' => true, ], diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 63b0fe2f..e1914625 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,6 +1,11 @@ #!/bin/bash set -e +# Boot-time artisan commands run as root; this keeps any file they create +# (including laravel.log) group-writable so the www-data fpm/queue workers +# can append to it afterwards. +umask 0002 + echo "=== Whisper Money Container Startup ===" # Ensure storage directories exist (volume may be empty on first deploy) @@ -10,8 +15,12 @@ mkdir -p /app/storage/framework/cache/data mkdir -p /app/storage/framework/sessions mkdir -p /app/storage/framework/views mkdir -p /app/storage/logs +# Pre-create the log file group-writable so a stale root-owned file from a +# persisted volume cannot block writes from the www-data workers. +touch /app/storage/logs/laravel.log chown -R www-data:www-data /app/storage chmod -R 775 /app/storage +chmod 664 /app/storage/logs/laravel.log # Auto-generate APP_KEY if not set or invalid (must start with "base64:") if [ -z "$APP_KEY" ] || [[ ! "$APP_KEY" =~ ^base64: ]]; then @@ -88,6 +97,7 @@ php artisan storage:link 2>/dev/null || true echo "Re-applying storage ownership..." chown -R www-data:www-data /app/storage /app/bootstrap/cache chmod -R 775 /app/storage /app/bootstrap/cache +chmod 664 /app/storage/logs/laravel.log echo "=== Startup complete, launching services ===" diff --git a/tests/Feature/LoggingConfigTest.php b/tests/Feature/LoggingConfigTest.php new file mode 100644 index 00000000..1a9047cf --- /dev/null +++ b/tests/Feature/LoggingConfigTest.php @@ -0,0 +1,5 @@ +toBe(0664); +})->with(['single', 'daily']);