Add database logging
This commit is contained in:
parent
4453c882e6
commit
6e1affeda3
18 changed files with 290 additions and 24 deletions
17
app/Events/ExceptionLogged.php
Normal file
17
app/Events/ExceptionLogged.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Log;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ExceptionLogged
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Log $log
|
||||
) {
|
||||
}
|
||||
}
|
||||
21
app/Events/ExceptionOccurred.php
Normal file
21
app/Events/ExceptionOccurred.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\LogLevelEnum;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Throwable;
|
||||
|
||||
class ExceptionOccurred
|
||||
{
|
||||
use Dispatchable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Throwable $exception,
|
||||
public LogLevelEnum $level,
|
||||
public string $message,
|
||||
public array $context = []
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,6 @@ public function __invoke(Request $request): View
|
|||
{
|
||||
$articles = Article::all()->sortByDesc('created_at');
|
||||
|
||||
return view('articles.index', compact('articles'));
|
||||
return view('pages.articles.index', compact('articles'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
app/Http/Controllers/LogsController.php
Normal file
17
app/Http/Controllers/LogsController.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Log;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LogsController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request): View
|
||||
{
|
||||
$logs = Log::all()->sortByDesc('created_at');
|
||||
|
||||
return view('pages.logs.index', compact('logs'));
|
||||
}
|
||||
}
|
||||
31
app/Listeners/LogExceptionToDatabase.php
Normal file
31
app/Listeners/LogExceptionToDatabase.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ExceptionLogged;
|
||||
use App\Events\ExceptionOccurred;
|
||||
use App\Models\Log;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class LogExceptionToDatabase implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue;
|
||||
|
||||
public function handle(ExceptionOccurred $event): void
|
||||
{
|
||||
$log = Log::create([
|
||||
'level' => $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);
|
||||
}
|
||||
}
|
||||
23
app/LogLevelEnum.php
Normal file
23
app/LogLevelEnum.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
enum LogLevelEnum: string
|
||||
{
|
||||
case DEBUG = 'debug';
|
||||
case INFO = 'info';
|
||||
case WARNING = 'warning';
|
||||
case ERROR = 'error';
|
||||
case CRITICAL = 'critical';
|
||||
|
||||
public static function toArray(): array
|
||||
{
|
||||
return [
|
||||
self::DEBUG->value,
|
||||
self::INFO->value,
|
||||
self::WARNING->value,
|
||||
self::ERROR->value,
|
||||
self::CRITICAL->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Models/Log.php
Normal file
24
app/Models/Log.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\LogLevelEnum;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Log extends Model
|
||||
{
|
||||
protected $table = 'logs';
|
||||
|
||||
protected $fillable = [
|
||||
'level',
|
||||
'message',
|
||||
'context',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'level' => LogLevelEnum::class,
|
||||
'context' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -27,5 +32,27 @@ public function boot(): void
|
|||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
app/Services/Log/LogSaver.php
Normal file
11
app/Services/Log/LogSaver.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Log;
|
||||
|
||||
class LogSaver
|
||||
{
|
||||
public function log()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -23,5 +23,19 @@
|
|||
]);
|
||||
})
|
||||
->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();
|
||||
|
|
|
|||
25
database/migrations/2025_06_29_154705_create_logs_table.php
Normal file
25
database/migrations/2025_06_29_154705_create_logs_table.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use App\LogLevelEnum;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->enum('level', LogLevelEnum::toArray());
|
||||
$table->string('message');
|
||||
$table->json('context')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('logs');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<h1>Articles</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>URL</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($articles as $article)
|
||||
<tr>
|
||||
<td>{{ $article->id }}</td>
|
||||
<td>{{ $article->url }}</td>
|
||||
<td>{{ $article->created_at->format('Y-m-d H:i') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
10
resources/views/layouts/app.blade.php
Normal file
10
resources/views/layouts/app.blade.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Lemmy Poster</title>
|
||||
</head>
|
||||
<body>
|
||||
@include('partials.navbar')
|
||||
|
||||
@yield('content')
|
||||
</body>
|
||||
</html>
|
||||
24
resources/views/pages/articles/index.blade.php
Normal file
24
resources/views/pages/articles/index.blade.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<h1>Articles</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>URL</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($articles as $article)
|
||||
<tr>
|
||||
<td>{{ $article->id }}</td>
|
||||
<td>{{ $article->url }}</td>
|
||||
<td>{{ $article->created_at->format('Y-m-d H:i') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endsection
|
||||
26
resources/views/pages/logs/index.blade.php
Normal file
26
resources/views/pages/logs/index.blade.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<h1>Logs</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Level</th>
|
||||
<th>Message</th>
|
||||
<th>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($logs as $log)
|
||||
<tr>
|
||||
<td>{{ $log->id }}</td>
|
||||
<td>{{ ucfirst($log->level->value) }}</td>
|
||||
<td>{{ $log->message }}</td>
|
||||
<td>{{ $log->created_at->format('Y-m-d H:i') }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endsection
|
||||
10
resources/views/partials/navbar.blade.php
Normal file
10
resources/views/partials/navbar.blade.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/articles">Articles</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/logs">Logs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\ArticlesController;
|
||||
use App\Http\Controllers\LogsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Inertia\Inertia;
|
||||
|
||||
|
|
@ -18,3 +19,4 @@
|
|||
require __DIR__.'/auth.php';
|
||||
|
||||
Route::get('/articles', ArticlesController::class)->name('articles');
|
||||
Route::get('/logs', LogsController::class)->name('logs');
|
||||
|
|
|
|||
Loading…
Reference in a new issue