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
-
-
-
-
- | ID |
- URL |
- Created At |
-
-
-
- @foreach($articles as $article)
-
- | {{ $article->id }} |
- {{ $article->url }} |
- {{ $article->created_at->format('Y-m-d H:i') }} |
-
- @endforeach
-
-
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
+
+
+
+
+ | ID |
+ URL |
+ Created At |
+
+
+
+ @foreach($articles as $article)
+
+ | {{ $article->id }} |
+ {{ $article->url }} |
+ {{ $article->created_at->format('Y-m-d H:i') }} |
+
+ @endforeach
+
+
+@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
+
+
+
+
+ | ID |
+ Level |
+ Message |
+ Created At |
+
+
+
+ @foreach($logs as $log)
+
+ | {{ $log->id }} |
+ {{ ucfirst($log->level->value) }} |
+ {{ $log->message }} |
+ {{ $log->created_at->format('Y-m-d H:i') }} |
+
+ @endforeach
+
+
+@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');