diff --git a/app/Http/Controllers/Admin/Maintenance/FullTree.php b/app/Http/Controllers/Admin/Maintenance/FullTree.php index 8871efe5b6d..a5345bb29e7 100644 --- a/app/Http/Controllers/Admin/Maintenance/FullTree.php +++ b/app/Http/Controllers/Admin/Maintenance/FullTree.php @@ -15,6 +15,7 @@ use App\Http\Resources\Diagnostics\AlbumTree; use Illuminate\Routing\Controller; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; /** * Maybe the album tree is broken. @@ -28,9 +29,12 @@ class FullTree extends Controller */ public function do(FullTreeUpdateRequest $request): void { - $key_name = 'id'; - $album_instance = new Album(); - batch()->update($album_instance, $request->albums(), $key_name); + // Wrap this in a transaction to ensure atomicity + DB::transaction(function () use ($request): void { + $key_name = 'id'; + $album_instance = new Album(); + batch()->update($album_instance, $request->albums(), $key_name); + }); AlbumRouteCacheUpdated::dispatch(); } diff --git a/app/Jobs/ExtractColoursJob.php b/app/Jobs/ExtractColoursJob.php index 14e5d0e1d1c..3e841f52fdd 100644 --- a/app/Jobs/ExtractColoursJob.php +++ b/app/Jobs/ExtractColoursJob.php @@ -23,6 +23,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; /** @@ -62,6 +63,7 @@ public function __construct( */ public function handle(): Photo { + Log::channel('jobs')->info("Starting colour extraction job for photo ID {$this->photo_id}."); $this->history->status = JobStatus::STARTED; $this->history->save(); diff --git a/app/Jobs/ExtractZip.php b/app/Jobs/ExtractZip.php index aa1bddbbb21..b555e9d5d28 100644 --- a/app/Jobs/ExtractZip.php +++ b/app/Jobs/ExtractZip.php @@ -77,6 +77,7 @@ public function __construct( */ public function handle(): void { + Log::channel('jobs')->info("Starting extraction job for file {$this->original_base_name}."); $this->history->status = JobStatus::STARTED; $this->history->save(); @@ -130,7 +131,7 @@ public function handle(): void // @codeCoverageIgnoreStart } catch (\Throwable $e) { // Fail silently if dispatched sync. - Log::error(__LINE__ . ':' . __FILE__ . ' ' . $e->getMessage(), $e->getTrace()); + Log::channel('jobs')->error(__LINE__ . ':' . __FILE__ . ' ' . $e->getMessage(), $e->getTrace()); } // @codeCoverageIgnoreEnd } @@ -192,7 +193,7 @@ private function validate_zip(): void $zip->close(); if (count($unsafe_entries) > 0) { - Log::critical('Zip file ' . $this->file_path . ' contains unsafe entries.', $unsafe_entries); + Log::channel('jobs')->critical('Zip file ' . $this->file_path . ' contains unsafe entries.', $unsafe_entries); $this->history->status = JobStatus::FAILURE; $this->history->save(); diff --git a/app/Jobs/HasFailedTrait.php b/app/Jobs/HasFailedTrait.php index d91ad065665..de36e0bbd2d 100644 --- a/app/Jobs/HasFailedTrait.php +++ b/app/Jobs/HasFailedTrait.php @@ -28,7 +28,7 @@ public function failed(\Throwable $th): void if ($th->getCode() === 999) { $this->release(); } else { - Log::error(__LINE__ . ':' . __FILE__ . ' ' . $th->getMessage(), $th->getTrace()); + Log::channel('jobs')->error(__LINE__ . ':' . __FILE__ . ' ' . $th->getMessage(), $th->getTrace()); } } } \ No newline at end of file diff --git a/app/Jobs/ImportImageJob.php b/app/Jobs/ImportImageJob.php index f9f726af14a..4905bd9f289 100644 --- a/app/Jobs/ImportImageJob.php +++ b/app/Jobs/ImportImageJob.php @@ -21,6 +21,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; /** @@ -67,6 +68,7 @@ public function __construct( */ public function handle(AlbumFactory $album_factory): Photo { + Log::channel('jobs')->info($this->history->job); $this->history->status = JobStatus::STARTED; $this->history->save(); diff --git a/app/Jobs/ProcessImageJob.php b/app/Jobs/ProcessImageJob.php index 5a87e9e1fa3..7dbd82e6833 100644 --- a/app/Jobs/ProcessImageJob.php +++ b/app/Jobs/ProcessImageJob.php @@ -28,6 +28,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; /** @@ -102,6 +103,7 @@ public function handle(AlbumFactory $album_factory): Photo { $this->history->status = JobStatus::STARTED; $this->history->save(); + Log::channel('jobs')->info($this->history->job); $copied_file = new TemporaryJobFile($this->file_path, $this->original_base_name); diff --git a/app/Jobs/RecomputeAlbumSizeJob.php b/app/Jobs/RecomputeAlbumSizeJob.php index 9065eb07d74..bd18cf36f63 100644 --- a/app/Jobs/RecomputeAlbumSizeJob.php +++ b/app/Jobs/RecomputeAlbumSizeJob.php @@ -82,7 +82,7 @@ protected function hasNewerJobQueued(): bool // We skip if there is a newer job queued (latest job ID is different from this one) $has_newer_job = $latest_job_id !== null && $latest_job_id !== $this->jobId; if ($has_newer_job) { - Log::info("Skipping job {$this->jobId} for album {$this->album_id} due to newer job {$latest_job_id} queued."); + Log::channel('jobs')->debug("Skipping job {$this->jobId} for album {$this->album_id} due to newer job {$latest_job_id} queued."); } return $has_newer_job; @@ -95,7 +95,7 @@ protected function hasNewerJobQueued(): bool */ public function handle(): void { - Log::info("Recomputing sizes for album {$this->album_id} (job {$this->jobId})"); + Log::channel('jobs')->info("Recomputing sizes for album {$this->album_id} (job {$this->jobId})"); Cache::forget("album_size_latest_job:{$this->album_id}"); try { @@ -117,15 +117,15 @@ public function handle(): void $sizes ); - Log::debug("Updated size statistics for album {$album->id}"); + Log::channel('jobs')->debug("Updated size statistics for album {$album->id}"); // Propagate to parent if exists if ($album->parent_id !== null && $this->propagate_to_parent) { - Log::debug("Propagating to parent {$album->parent_id}"); + Log::channel('jobs')->debug("Propagating to parent {$album->parent_id}"); self::dispatch($album->parent_id); } } catch (\Exception $e) { - Log::error("Propagation stopped at album {$this->album_id} due to failure: " . $e->getMessage()); + Log::channel('jobs')->error("Propagation stopped at album {$this->album_id} due to failure: " . $e->getMessage()); throw $e; } @@ -188,7 +188,7 @@ private function computeSizes(Album $album): array */ public function failed(\Throwable $exception): void { - Log::error("Job failed permanently for album {$this->album_id}: " . $exception->getMessage()); + Log::channel('jobs')->error("Job failed permanently for album {$this->album_id}: " . $exception->getMessage()); // Do NOT dispatch parent job on failure - propagation stops here } } diff --git a/app/Jobs/RecomputeAlbumStatsJob.php b/app/Jobs/RecomputeAlbumStatsJob.php index 0d3cb4ae0a2..1d2b9a8095c 100644 --- a/app/Jobs/RecomputeAlbumStatsJob.php +++ b/app/Jobs/RecomputeAlbumStatsJob.php @@ -87,7 +87,7 @@ protected function hasNewerJobQueued(): bool // We skip if there is no newer job, or if the latest job is not this one $has_newer_job = $latest_job_id !== null && $latest_job_id !== $this->jobId; if ($has_newer_job) { - Log::info("Skipping job {$this->jobId} for album {$this->album_id} due to newer job {$latest_job_id} queued."); + Log::channel('jobs')->debug("Skipping job {$this->jobId} for album {$this->album_id} due to newer job {$latest_job_id} queued."); } return $has_newer_job; @@ -100,13 +100,13 @@ protected function hasNewerJobQueued(): bool */ public function handle(): void { - Log::info("Recomputing stats for album {$this->album_id}"); + Log::channel('jobs')->info("Recomputing stats for album {$this->album_id}"); Cache::forget("album_stats_latest_job:{$this->album_id}"); // This is a safety check to avoid recomputing albums // when no admin user exists. if (DB::table('users')->where('may_administrate', '=', true)->count() === 0) { - Log::error("No admin user exists, skipping recompute for album {$this->album_id}."); + Log::channel('jobs')->critical("No admin user exists, skipping recompute for album {$this->album_id}."); return; } @@ -115,7 +115,7 @@ public function handle(): void // Fetch the album. $album = Album::where('id', '=', $this->album_id)->first(); if ($album === null) { - Log::warning("Album {$this->album_id} not found, skipping recompute."); + Log::channel('jobs')->warning("Album {$this->album_id} not found, skipping recompute."); return; } @@ -139,16 +139,16 @@ public function handle(): void // Compute cover IDs (simplified for now - will be enhanced in I3) $album->auto_cover_id_max_privilege = $this->computeMaxPrivilegeCover($album, $is_nsfw_context); $album->auto_cover_id_least_privilege = $this->computeLeastPrivilegeCover($album, $is_nsfw_context); - Log::debug("Computed covers for album {$album->id}: max_privilege=" . ($album->auto_cover_id_max_privilege ?? 'null') . ', least_privilege=' . ($album->auto_cover_id_least_privilege ?? 'null')); + Log::channel('jobs')->debug("Computed covers for album {$album->id}: max_privilege=" . ($album->auto_cover_id_max_privilege ?? 'null') . ', least_privilege=' . ($album->auto_cover_id_least_privilege ?? 'null')); $album->save(); // Propagate to parent if exists if ($album->parent_id !== null && $this->propagate_to_parent) { - Log::debug("Propagating to parent {$album->parent_id}"); + Log::channel('jobs')->debug("Propagating to parent {$album->parent_id}"); self::dispatch($album->parent_id); } } catch (\Exception $e) { - Log::error("Propagation stopped at album {$this->album_id} due to failure: " . $e->getMessage()); + Log::channel('jobs')->error("Propagation stopped at album {$this->album_id} due to failure: " . $e->getMessage()); throw $e; } @@ -359,7 +359,7 @@ private function computeLeastPrivilegeCover(Album $album, bool $is_nsfw_context) */ public function failed(\Throwable $exception): void { - Log::error("Job failed permanently for album {$this->album_id}: " . $exception->getMessage()); + Log::channel('jobs')->error("Job failed permanently for album {$this->album_id}: " . $exception->getMessage()); // Do NOT dispatch parent job on failure - propagation stops here } } diff --git a/app/Jobs/UploadSizeVariantToS3Job.php b/app/Jobs/UploadSizeVariantToS3Job.php index a3ccde982fb..f7cc835ad42 100644 --- a/app/Jobs/UploadSizeVariantToS3Job.php +++ b/app/Jobs/UploadSizeVariantToS3Job.php @@ -72,8 +72,8 @@ public function failed(\Throwable $th): void if ($th->getCode() === 999) { $this->release(); } else { - Log::error(__LINE__ . ':' . __FILE__ . ' Upload failed for ' . $this->variant->short_path); - Log::error(__LINE__ . ':' . __FILE__ . ' ' . $th->getMessage(), $th->getTrace()); + Log::channel('jobs')->error(__LINE__ . ':' . __FILE__ . ' Upload failed for ' . $this->variant->short_path); + Log::channel('jobs')->error(__LINE__ . ':' . __FILE__ . ' ' . $th->getMessage(), $th->getTrace()); } } diff --git a/app/Jobs/WatermarkerJob.php b/app/Jobs/WatermarkerJob.php index c585596bc88..f408af5c9b0 100644 --- a/app/Jobs/WatermarkerJob.php +++ b/app/Jobs/WatermarkerJob.php @@ -101,7 +101,7 @@ public function failed(\Throwable $th): void if ($th->getCode() === 999) { $this->release(); } else { - Log::error(__LINE__ . ':' . __FILE__ . ' Watermark failed for ' . $this->variant->short_path, + Log::channel('jobs')->error(__LINE__ . ':' . __FILE__ . ' Watermark failed for ' . $this->variant->short_path, [ 'variant_id' => $this->variant->id, 'path' => $this->variant->short_path, diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index dd01e8604e9..2bed6d15ed2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -32,6 +32,7 @@ use App\Policies\SettingsPolicy; use App\Repositories\ConfigManager; use App\Services\MoneyService; +use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Http\Resources\Json\JsonResource; @@ -257,7 +258,7 @@ private function registerLoggerAccess(): void /** * @codeCoverageIgnore */ - private function logSQLStart(string $sql, array $bindings, string $connection): void + private function logSQLStart(string $sql, array $bindings, Connection $connection): void { $uri = request()?->getRequestUri() ?? ''; // Quick exit @@ -289,7 +290,7 @@ private function logSQLStart(string $sql, array $bindings, string $connection): $msg = 'START: ' . $sql . ' [' . implode(', ', $bindings) . ']'; $context = [ - 'connection' => $connection, + 'connection' => $connection->getDriverName(), 'url' => request()?->fullUrl() ?? 'N/A', ]; @@ -326,7 +327,7 @@ private function logSQL(QueryExecuted $query): void config('database.explain', false) === false || !Str::contains($query->sql, 'select') ) { - Log::debug($msg); + Log::warning($msg); return; } @@ -350,7 +351,7 @@ private function logSQL(QueryExecuted $query): void $msg .= Str::repeat('-', 20) . PHP_EOL; $msg .= $sql_with_bindings . PHP_EOL; $msg .= $renderer->getTable($explain); - Log::debug($msg); + Log::warning($msg); } /** diff --git a/config/logging.php b/config/logging.php index 83004705830..c461a28f8da 100644 --- a/config/logging.php +++ b/config/logging.php @@ -132,5 +132,22 @@ 'path' => storage_path('logs/deprecations.log'), 'level' => 'debug', ], + + // Specific channel for job logs (e.g., to debug queue issues) + 'jobs' => [ + 'driver' => 'stack', + 'channels' => [ + 'jobs-daily', + 'error', + 'warning', + ], + ], + + // Specific channel for job logs (e.g., to debug queue issues) + 'jobs-daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/jobs.log'), + 'level' => 'debug', + ], ], ];