diff --git a/src/Console/Commands/SummaryLogCommand.php b/src/Console/Commands/SummaryLogCommand.php new file mode 100644 index 00000000..edb91ac5 --- /dev/null +++ b/src/Console/Commands/SummaryLogCommand.php @@ -0,0 +1,107 @@ +option('logs'); + + static::deleteSummaryFile(); + $this->comment('Deleted error log summary file'); + $this->comment('Generating error log summary file'); + static::generateSummaryFile($number); + $this->comment('Done'); + } + + protected static function deleteSummaryFile(): void + { + Storage::disk('logs') + ->delete('summary.log'); + } + + protected static function generateSummaryFile(int $number): void + { + $summary = []; + + /** @var LogFileCollection $files */ + $files = LogViewer::getFiles(); + + foreach ($files as $file) { + if (! Str::endsWith($file->name, 'laravel.log')) { + continue; + } + + $logs = collect($file->logs()->get()) + ->reverse() + ->take($number); + + foreach ($logs as $log) { + $level = $log->level; + $message = $log->message; + $context = $log->context; + $env = $log->extra['environment']; + $ts = $log->datetime->format('Y-m-d H:i:s'); + + if (! isset($summary[trim($message)])) { + $summary[$message] = [ + 'first' => $ts, + 'last' => $ts, + 'count' => 1, + 'level' => $level, + 'env' => $env, + 'message' => $message, + 'context' => $context, + ]; + } else { + $summary[trim($message)]['count']++; + $summary[trim($message)]['last'] = max( + $ts, + $summary[trim($message)]['last'] + ); + $summary[trim($message)]['first'] = min( + $ts, + $summary[trim($message)]['first'] + ); + } + } + } + + $lines = array_map(function (array $data) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + }, array_values($summary)); + + $uniqueLines = []; + foreach ($lines as $json) { + $uniqueLines[$json] = true; + } + + Storage::disk('logs') + ->put('summary.log', implode("\n", array_reverse(array_keys($uniqueLines)))); + } +} diff --git a/src/LogViewerServiceProvider.php b/src/LogViewerServiceProvider.php index 8e69eb82..e75077e6 100644 --- a/src/LogViewerServiceProvider.php +++ b/src/LogViewerServiceProvider.php @@ -14,6 +14,7 @@ use Illuminate\Support\Str; use Laravel\Octane\Events\RequestTerminated; use Opcodes\LogViewer\Console\Commands\GenerateDummyLogsCommand; +use Opcodes\LogViewer\Console\Commands\SummaryLogCommand; use Opcodes\LogViewer\Console\Commands\PublishCommand; use Opcodes\LogViewer\Events\LogFileDeleted; use Opcodes\LogViewer\Facades\LogViewer; @@ -60,6 +61,7 @@ public function boot() $this->commands([ PublishCommand::class, GenerateDummyLogsCommand::class, + SummaryLogCommand::class, ]); } diff --git a/src/Logs/SummaryLog.php b/src/Logs/SummaryLog.php new file mode 100644 index 00000000..c59a1a3e --- /dev/null +++ b/src/Logs/SummaryLog.php @@ -0,0 +1,55 @@ + 'Severity', 'data_path' => 'level'], + ['label' => 'First', 'data_path' => 'extra.first_datetime'], + ['label' => 'Last', 'data_path' => 'datetime'], + ['label' => 'Env', 'data_path' => 'extra.environment'], + ['label' => 'Count', 'data_path' => 'extra.count'], + ['label' => 'Message', 'data_path' => 'message'], + ]; + protected static string $regexContextKey = 'context'; + + protected function parseText(array &$matches = []): void + { + $data = json_decode($this->text, true) ?: []; + + $matches[static::$regexDatetimeKey] = $data['last'] ?? ''; + $matches[static::$regexLevelKey] = $data['level'] ?? ''; + $matches[static::$regexMessageKey] = $data['message'] ?? ''; + $matches[static::$regexContextKey] = $data['context'] ?? []; + + $this->extra = [ + 'first_datetime' => Carbon::parse($data['first'] ?? null) + ->setTimezone(LogViewer::timezone()) + ->format("Y\u{2011}m\u{2011}d\u{00A0}H:i:s"), + 'environment' => $data['env'] ?? null, + 'count' => $data['count'] ?? null, + 'context' => $data['context'] ?? [], + ]; + + $this->text = $data['message'] ?? ''; + } + + protected function fillMatches(array $matches = []): void + { + parent::fillMatches($matches); + + $this->context = $matches[static::$regexContextKey]; + } +}