76
common/Logging/Schedule/MonitorsSchedule.php
Executable file
76
common/Logging/Schedule/MonitorsSchedule.php
Executable file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Logging\Schedule;
|
||||
|
||||
use Illuminate\Console\Scheduling\Event;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
|
||||
trait MonitorsSchedule
|
||||
{
|
||||
protected function monitorSchedule(Schedule $schedule): void
|
||||
{
|
||||
collect($schedule->events())->each(function (Event $event) {
|
||||
$logItem = new ScheduleLogItem();
|
||||
$stopwatch = new Stopwatch(true);
|
||||
|
||||
$event->before(function () use ($event, $stopwatch, $logItem) {
|
||||
$logItem->ran_at = now();
|
||||
$stopwatch->start($event->command);
|
||||
});
|
||||
|
||||
$event->after(function (Stringable $output) use (
|
||||
$event,
|
||||
$stopwatch,
|
||||
$logItem,
|
||||
) {
|
||||
$stopwatch->stop($event->command);
|
||||
$commandParts = collect(explode(' ', $event->command));
|
||||
$artisanIndex = $commandParts->search(
|
||||
fn($str) => trim($str, '\'"') === 'artisan',
|
||||
);
|
||||
$signature = $commandParts->get($artisanIndex + 1);
|
||||
|
||||
$namespace = get_class(Artisan::all()[$signature]);
|
||||
|
||||
// check if command already ran with the same signature and exit code in the last hour
|
||||
$lastLogItem = ScheduleLogItem::query()
|
||||
->where('command', $namespace)
|
||||
->when(
|
||||
// only keep one log item of ScheduleHealthCommand
|
||||
$namespace !== ScheduleHealthCommand::class,
|
||||
function ($query) use ($event) {
|
||||
$query
|
||||
->where('exit_code', $event->exitCode)
|
||||
->where('ran_at', '>=', now()->subHour());
|
||||
},
|
||||
)
|
||||
->first();
|
||||
|
||||
$data = [
|
||||
'command' => $namespace,
|
||||
'output' => trim($output->limit(1000)->toString(), "\n"),
|
||||
'exit_code' => $event->exitCode,
|
||||
'duration' => $stopwatch
|
||||
->getEvent($event->command)
|
||||
->getDuration(),
|
||||
];
|
||||
|
||||
if ($lastLogItem) {
|
||||
$lastLogItem
|
||||
->fill([
|
||||
...$data,
|
||||
'ran_at' => $logItem->ran_at,
|
||||
'count_in_last_hour' =>
|
||||
$lastLogItem->count_in_last_hour + 1,
|
||||
])
|
||||
->save();
|
||||
} else {
|
||||
$logItem->fill($data)->save();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
17
common/Logging/Schedule/ScheduleHealthCommand.php
Executable file
17
common/Logging/Schedule/ScheduleHealthCommand.php
Executable file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Logging\Schedule;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ScheduleHealthCommand extends Command
|
||||
{
|
||||
protected $signature = 'schedule:be-health';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('CRON schedule is running properly.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
58
common/Logging/Schedule/ScheduleLogController.php
Executable file
58
common/Logging/Schedule/ScheduleLogController.php
Executable file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Logging\Schedule;
|
||||
|
||||
use Common\Core\BaseController;
|
||||
use Common\Database\Datasource\Datasource;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class ScheduleLogController extends BaseController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('isAdmin');
|
||||
}
|
||||
|
||||
public function index(): mixed
|
||||
{
|
||||
$params = request()->all();
|
||||
if (!isset($params['orderBy'])) {
|
||||
$params['orderBy'] = 'ran_at';
|
||||
}
|
||||
|
||||
$pagination = (new Datasource(
|
||||
ScheduleLogItem::query(),
|
||||
$params,
|
||||
))->paginate();
|
||||
|
||||
return $this->success([
|
||||
'pagination' => $pagination,
|
||||
]);
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$log = json_encode(
|
||||
ScheduleLogItem::limit(1000)->get(),
|
||||
JSON_PRETTY_PRINT,
|
||||
);
|
||||
|
||||
return response($log)
|
||||
->header('Content-Type', 'application/json')
|
||||
->header(
|
||||
'Content-Disposition',
|
||||
'attachment; filename="schedule-log.json"',
|
||||
);
|
||||
}
|
||||
|
||||
public function rerun(int $id): mixed
|
||||
{
|
||||
$logItem = ScheduleLogItem::findOrFail($id);
|
||||
|
||||
Artisan::call($logItem->command);
|
||||
|
||||
$logItem->increment('count_in_last_hour');
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
}
|
||||
61
common/Logging/Schedule/ScheduleLogItem.php
Executable file
61
common/Logging/Schedule/ScheduleLogItem.php
Executable file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Common\Logging\Schedule;
|
||||
|
||||
use Common\Core\BaseModel;
|
||||
|
||||
class ScheduleLogItem extends BaseModel
|
||||
{
|
||||
const MODEL_TYPE = 'schedule_log_item';
|
||||
|
||||
protected $table = 'schedule_log';
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'ran_at' => 'datetime',
|
||||
'duration' => 'integer',
|
||||
'count_in_last_hour' => 'integer',
|
||||
'exit_code' => 'integer',
|
||||
];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public static function scheduleRanInLast30Minutes(): bool
|
||||
{
|
||||
return (new self())
|
||||
->where('command', ScheduleHealthCommand::class)
|
||||
->whereBetween('ran_at', [now()->subMinutes(30), now()])
|
||||
->exists();
|
||||
}
|
||||
|
||||
public static function filterableFields(): array
|
||||
{
|
||||
return ['id', 'ran_at', 'duration', 'count_in_last_hour', 'exit_code'];
|
||||
}
|
||||
|
||||
public function toNormalizedArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->command,
|
||||
'description' => $this->output,
|
||||
'model_type' => self::MODEL_TYPE,
|
||||
];
|
||||
}
|
||||
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'command' => $this->command,
|
||||
'ran_at' => $this->ran_at->timestamp ?? '_null',
|
||||
];
|
||||
}
|
||||
|
||||
public static function getModelTypeAttribute(): string
|
||||
{
|
||||
return self::MODEL_TYPE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user