diff --git a/app/Events/ExceptionLogged.php b/app/Events/ExceptionLogged.php new file mode 100644 index 0000000..702612a --- /dev/null +++ b/app/Events/ExceptionLogged.php @@ -0,0 +1,17 @@ +sortByDesc('created_at'); - return view('articles.index', compact('articles')); + return view('pages.articles.index', compact('articles')); } } diff --git a/app/Http/Controllers/LogsController.php b/app/Http/Controllers/LogsController.php new file mode 100644 index 0000000..0522f00 --- /dev/null +++ b/app/Http/Controllers/LogsController.php @@ -0,0 +1,17 @@ +sortByDesc('created_at'); + + return view('pages.logs.index', compact('logs')); + } +} diff --git a/app/Listeners/LogExceptionToDatabase.php b/app/Listeners/LogExceptionToDatabase.php new file mode 100644 index 0000000..06eb5a7 --- /dev/null +++ b/app/Listeners/LogExceptionToDatabase.php @@ -0,0 +1,31 @@ + $event->level, + 'message' => $event->message, + 'context' => [ + 'exception_class' => get_class($event->exception), + 'file' => $event->exception->getFile(), + 'line' => $event->exception->getLine(), + 'trace' => $event->exception->getTraceAsString(), + ...$event->context + ] + ]); + + ExceptionLogged::dispatch($log); + } +} diff --git a/app/LogLevelEnum.php b/app/LogLevelEnum.php new file mode 100644 index 0000000..7897106 --- /dev/null +++ b/app/LogLevelEnum.php @@ -0,0 +1,23 @@ +value, + self::INFO->value, + self::WARNING->value, + self::ERROR->value, + self::CRITICAL->value, + ]; + } +} diff --git a/app/Models/Log.php b/app/Models/Log.php new file mode 100644 index 0000000..4f3dea5 --- /dev/null +++ b/app/Models/Log.php @@ -0,0 +1,24 @@ + LogLevelEnum::class, + 'context' => 'array', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + ]; +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6bf1434..a8c3aeb 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,10 +4,15 @@ use App\Events\ArticleFetched; use App\Events\ArticleReadyToPublish; +use App\Events\ExceptionOccurred; use App\Listeners\CheckArticleKeywords; +use App\Listeners\LogExceptionToDatabase; use App\Listeners\PublishArticle; +use App\LogLevelEnum; +use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Support\Facades\Event; use Illuminate\Support\ServiceProvider; +use Throwable; class AppServiceProvider extends ServiceProvider { @@ -22,10 +27,32 @@ public function boot(): void ArticleFetched::class, CheckArticleKeywords::class, ); - + Event::listen( ArticleReadyToPublish::class, PublishArticle::class, ); + + Event::listen( + ExceptionOccurred::class, + LogExceptionToDatabase::class, + ); + + app()->make(ExceptionHandler::class) + ->reportable(function (Throwable $e) { + $level = $this->mapExceptionToLogLevel($e); + + ExceptionOccurred::dispatch($e, $level, $e->getMessage(), []); + }); + } + + private function mapExceptionToLogLevel(Throwable $exception): LogLevelEnum + { + return match (true) { + $exception instanceof \Error => LogLevelEnum::CRITICAL, + $exception instanceof \RuntimeException => LogLevelEnum::ERROR, + $exception instanceof \InvalidArgumentException => LogLevelEnum::WARNING, + default => LogLevelEnum::ERROR, + }; } } diff --git a/app/Services/Article/ArticleFetcher.php b/app/Services/Article/ArticleFetcher.php index 42881e3..8e13ba1 100644 --- a/app/Services/Article/ArticleFetcher.php +++ b/app/Services/Article/ArticleFetcher.php @@ -37,6 +37,10 @@ private static function fetchArticles(): Collection return collect($responses) ->map(function ($response, $index) use ($urls) { + if (!isset($urls[$index])) { + return null; + } + $url = $urls[$index]; try { @@ -51,7 +55,7 @@ private static function fetchArticles(): Collection }) ->filter(fn($article) => !empty($article)); } catch (Exception $e) { - logger('article_fetcher')->error("Failed to fetch VRT homepage", ['error' => $e->getMessage()]); + logger()->error("Failed to fetch VRT homepage", ['error' => $e->getMessage()]); return new Collection([]); } } diff --git a/app/Services/Log/LogSaver.php b/app/Services/Log/LogSaver.php new file mode 100644 index 0000000..c1a4e4d --- /dev/null +++ b/app/Services/Log/LogSaver.php @@ -0,0 +1,11 @@ +withExceptions(function (Exceptions $exceptions) { - // + $exceptions->reportable(function (Throwable $e) { + $level = match (true) { + $e instanceof Error => App\LogLevelEnum::CRITICAL, + $e instanceof RuntimeException => App\LogLevelEnum::ERROR, + $e instanceof InvalidArgumentException => App\LogLevelEnum::WARNING, + default => App\LogLevelEnum::ERROR, + }; + + App\Events\ExceptionOccurred::dispatch( + $e, + $level, + $e->getMessage(), + [] + ); + }); })->create(); diff --git a/database/migrations/2025_06_29_154705_create_logs_table.php b/database/migrations/2025_06_29_154705_create_logs_table.php new file mode 100644 index 0000000..9f4c0ae --- /dev/null +++ b/database/migrations/2025_06_29_154705_create_logs_table.php @@ -0,0 +1,25 @@ +id(); + $table->enum('level', LogLevelEnum::toArray()); + $table->string('message'); + $table->json('context')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('logs'); + } +}; diff --git a/resources/views/articles/index.blade.php b/resources/views/articles/index.blade.php deleted file mode 100644 index 6213b4d..0000000 --- a/resources/views/articles/index.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -

Articles

- - - - - - - - - - - @foreach($articles as $article) - - - - - - @endforeach - -
IDURLCreated At
{{ $article->id }}{{ $article->url }}{{ $article->created_at->format('Y-m-d H:i') }}
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 0000000..9685ee1 --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,10 @@ + + + Lemmy Poster + + + @include('partials.navbar') + + @yield('content') + + diff --git a/resources/views/pages/articles/index.blade.php b/resources/views/pages/articles/index.blade.php new file mode 100644 index 0000000..700876e --- /dev/null +++ b/resources/views/pages/articles/index.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.app') + +@section('content') +

Articles

+ + + + + + + + + + + @foreach($articles as $article) + + + + + + @endforeach + +
IDURLCreated At
{{ $article->id }}{{ $article->url }}{{ $article->created_at->format('Y-m-d H:i') }}
+@endsection diff --git a/resources/views/pages/logs/index.blade.php b/resources/views/pages/logs/index.blade.php new file mode 100644 index 0000000..3827b03 --- /dev/null +++ b/resources/views/pages/logs/index.blade.php @@ -0,0 +1,26 @@ +@extends('layouts.app') + +@section('content') +

Logs

+ + + + + + + + + + + + @foreach($logs as $log) + + + + + + + @endforeach + +
IDLevelMessageCreated At
{{ $log->id }}{{ ucfirst($log->level->value) }}{{ $log->message }}{{ $log->created_at->format('Y-m-d H:i') }}
+@endsection diff --git a/resources/views/partials/navbar.blade.php b/resources/views/partials/navbar.blade.php new file mode 100644 index 0000000..845964a --- /dev/null +++ b/resources/views/partials/navbar.blade.php @@ -0,0 +1,10 @@ + diff --git a/routes/web.php b/routes/web.php index 3f280e4..3c7b998 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,7 @@ name('articles'); +Route::get('/logs', LogsController::class)->name('logs');