├── config └── dcat-log-viewer.php ├── composer.json ├── LICENSE ├── src ├── DcatLogViewerServiceProvider.php ├── LogController.php └── LogViewer.php ├── README.md └── resources └── view └── log.blade.php /config/dcat-log-viewer.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'prefix' => 'dcat-logs', 6 | 'namespace' => 'Dcat\LogViewer', 7 | 'middleware' => [], 8 | ], 9 | 10 | 'directory' => storage_path('logs'), 11 | 12 | 'search_page_items' => 500, 13 | 14 | 'page_items' => 30, 15 | ]; 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dcat/laravel-log-viewer", 3 | "description": "Laravel Log Viewer", 4 | "type": "library", 5 | "keywords": ["laravel", "log viewer"], 6 | "homepage": "https://github.com/jqhph/laravel-log-viewer", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "jqh", 11 | "email": "841324345@qq.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Dcat\\LogViewer\\": "src/" 20 | } 21 | }, 22 | "extra": { 23 | "laravel": { 24 | "providers": [ 25 | "Dcat\\LogViewer\\DcatLogViewerServiceProvider" 26 | ] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jiang Qinghua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/DcatLogViewerServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom(__DIR__.'/../resources/view', 'dcat-log-viewer'); 12 | 13 | if ($this->app->runningInConsole()) { 14 | $this->publishes([__DIR__.'/../config' => config_path()], 'dcat-log-viewer'); 15 | } 16 | 17 | $this->registerRoutes(); 18 | } 19 | 20 | protected function registerRoutes() 21 | { 22 | app('router')->group([ 23 | 'prefix' => config('dcat-log-viewer.route.prefix', 'dcat-logs'), 24 | 'namespace' => config('dcat-log-viewer.route.namespace', 'Dcat\LogViewer'), 25 | 'middleware' => config('dcat-log-viewer.route.middleware'), 26 | ], function ($router) { 27 | $router->get('/', ['as' => 'dcat-log-viewer', 'uses' => 'LogController@index',]); 28 | $router->get('download', ['as' => 'dcat-log-viewer.download', 'uses' => 'LogController@download',]); 29 | $router->get('{file}', ['as' => 'dcat-log-viewer.file', 'uses' => 'LogController@index',]); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Dcat Laravel Log Viewer 4 | 5 |

6 | 7 | 8 | StyleCI 9 | 10 | 11 |

12 | 13 | `Dcat Log Viewer`是一个`Laravel`日志查看工具,支持大文件日志的查看和搜索功能,更改自[laravel-admin-extensions/log-viewer](https://github.com/laravel-admin-extensions/log-viewer)。 14 | 15 |
16 | 17 | ![](https://cdn.learnku.com/uploads/images/202007/09/38389/5Ps3bfhdrR.png!large) 18 | 19 | ## 功能 20 | 21 | - [x] 支持多层级目录 22 | - [x] 支持查看大文件日志 23 | - [x] 支持日志关键词检索 24 | - [x] 支持多层级目录文件名称搜索 25 | - [x] 支持下载功能 26 | - [x] 支持分页 27 | - [x] 支持手机页面 28 | 29 | 30 | ## 环境 31 | 32 | - PHP >= 7 33 | - laravel >= 5.5 34 | 35 | 36 | ## 安装 37 | 38 | ```bash 39 | composer require dcat/laravel-log-viewer 40 | ``` 41 | 42 | 发布配置文件,此步骤可省略 43 | 44 | ```bash 45 | php artisan vendor:publish --tag=dcat-log-viewer 46 | ``` 47 | 48 | 然后访问 `http://hostname/dcat-logs` 即可 49 | 50 | 配置文件 51 | 52 | ```php 53 | 54 | return [ 55 | 'route' => [ 56 | // 路由前缀 57 | 'prefix' => 'dcat-logs', 58 | // 命名空间 59 | 'namespace' => 'Dcat\LogViewer', 60 | // 中间件 61 | 'middleware' => [], 62 | ], 63 | 64 | // 日志目录 65 | 'directory' => storage_path('logs'), 66 | 67 | // 搜索页显示条目数(搜索后不分页,所以这个参数可以设置大一些) 68 | 'search_page_items' => 500, 69 | 70 | // 默认每页条目数 71 | 'page_items' => 30, 72 | ]; 73 | ``` 74 | 75 | ## License 76 | [The MIT License (MIT)](LICENSE). 77 | -------------------------------------------------------------------------------- /src/LogController.php: -------------------------------------------------------------------------------- 1 | get('dir') ? trim($request->get('dir')) : ''; 14 | $filename = $request->get('filename') ? trim($request->get('filename')) : ''; 15 | $offset = $request->get('offset'); 16 | $keyword = $request->get('keyword') ? trim($request->get('keyword')) : ''; 17 | $lines = $keyword ? (config('dcat-log-viewer.search_page_items') ?: 500) : (config('dcat-log-viewer.page_items') ?: 30); 18 | 19 | $viewer = new LogViewer($this->getDirectory(), $dir, $file); 20 | 21 | $viewer->setKeyword($keyword); 22 | $viewer->setFilename($filename); 23 | 24 | return view('dcat-log-viewer::log', [ 25 | 'dir' => $dir, 26 | 'logs' => $viewer->fetch($offset, $lines), 27 | 'logFiles' => $this->formatLogFiles($viewer, $dir), 28 | 'logDirs' => $viewer->getLogDirectories(), 29 | 'fileName' => $viewer->file, 30 | 'end' => $viewer->getFilesize(), 31 | 'prevUrl' => $viewer->getPrevPageUrl(), 32 | 'nextUrl' => $viewer->getNextPageUrl(), 33 | 'filePath' => $viewer->getFilePath(), 34 | 'size' => static::bytesToHuman($viewer->getFilesize()), 35 | ]); 36 | } 37 | 38 | public function download() 39 | { 40 | $request = app('request'); 41 | 42 | $file = trim($request->get('file')); 43 | $dir = trim($request->get('dir')); 44 | $filename = trim($request->get('filename')); 45 | $keyword = trim($request->get('keyword')); 46 | 47 | $viewer = new LogViewer($this->getDirectory(), $dir, $file); 48 | 49 | $viewer->setKeyword($keyword); 50 | $viewer->setFilename($filename); 51 | 52 | return response()->download($viewer->getFilePath()); 53 | } 54 | 55 | protected function getDirectory() 56 | { 57 | return config('dcat-log-viewer.directory') ?: storage_path('logs'); 58 | } 59 | 60 | protected function formatLogFiles(LogViewer $logViewer, $currentDir) 61 | { 62 | return array_map(function ($value) use ($logViewer, $currentDir) { 63 | $file = $value; 64 | $dir = $currentDir; 65 | 66 | if (Str::contains($value, '/')) { 67 | $array = explode('/', $value); 68 | $file = end($array); 69 | 70 | array_pop($array); 71 | $dir = implode('/', $array); 72 | } 73 | 74 | return [ 75 | 'file' => $value, 76 | 'url' => route('dcat-log-viewer.file', ['file' => $file, 'dir' => $dir]), 77 | 'active' => $logViewer->isCurrentFile($value), 78 | ]; 79 | }, $logViewer->getLogFiles()); 80 | } 81 | 82 | protected static function bytesToHuman($bytes) 83 | { 84 | $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; 85 | 86 | for ($i = 0; $bytes > 1024; $i++) { 87 | $bytes /= 1024; 88 | } 89 | 90 | return round($bytes, 2).' '.$units[$i]; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/LogViewer.php: -------------------------------------------------------------------------------- 1 | 'black', 56 | 'ALERT' => 'navy', 57 | 'CRITICAL' => 'maroon', 58 | 'ERROR' => 'danger', 59 | 'WARNING' => 'orange', 60 | 'NOTICE' => 'light-blue', 61 | 'INFO' => 'primary', 62 | 'DEBUG' => 'light', 63 | '' => '', 64 | ]; 65 | 66 | protected $keyword; 67 | 68 | protected $filename; 69 | 70 | /** 71 | * LogViewer constructor. 72 | * 73 | * @param null $file 74 | */ 75 | public function __construct($basePath, $dir, $file = null) 76 | { 77 | $this->basePath = $this->getRealPath(rtrim($basePath, '/')); 78 | $this->currentDirectory = $this->formatPath(rtrim($dir, '/')); 79 | $this->file = $this->formatPath($file); 80 | $this->files = new Filesystem(); 81 | } 82 | 83 | protected function getRealPath($path) 84 | { 85 | try { 86 | $paths = explode('/', $path); 87 | 88 | $result = ''; 89 | foreach ($paths as $v) { 90 | $result .= $v.'/'; 91 | 92 | $current = rtrim($result, '/'); 93 | if (is_link($current)) { 94 | $result = readlink($current).'/'; 95 | } 96 | } 97 | 98 | return rtrim($result, '/'); 99 | } catch (\Throwable $e) { 100 | return $path; 101 | } 102 | } 103 | 104 | protected function formatPath($path) 105 | { 106 | return $path ? str_replace(['../'], '', $path) : ''; 107 | } 108 | 109 | /** 110 | * Get file path by giving log file name. 111 | * 112 | * @return string 113 | * 114 | */ 115 | public function getFilePath() 116 | { 117 | if (!$this->filePath) { 118 | $path = $this->mergeDirectory().'/'.$this->getFile(); 119 | 120 | $this->filePath = is_file($path) ? $path : false; 121 | } 122 | 123 | return $this->filePath; 124 | } 125 | 126 | public function setKeyword($value) 127 | { 128 | $this->keyword = strtolower($value); 129 | } 130 | 131 | public function setFilename($value) 132 | { 133 | $this->filename = $this->formatPath($value); 134 | } 135 | 136 | /** 137 | * Get size of log file. 138 | * 139 | * @return int 140 | */ 141 | public function getFilesize() 142 | { 143 | if (!$this->getFilePath()) { 144 | return 0; 145 | } 146 | 147 | return filesize($this->getFilePath()); 148 | } 149 | 150 | /** 151 | * Get log file list in storage. 152 | * 153 | * @return array 154 | */ 155 | public function getLogFiles() 156 | { 157 | if ($this->filename) { 158 | return collect($this->files->allFiles($this->mergeDirectory()))->map(function (\SplFileInfo $fileInfo) { 159 | return $this->replaceBasePath($fileInfo->getRealPath()); 160 | })->filter(function ($v) { 161 | return Str::contains($v, $this->filename); 162 | })->toArray(); 163 | } 164 | 165 | $files = glob($this->mergeDirectory().'/*.*'); 166 | //$files = array_combine($files, array_map('filemtime', $files)); 167 | rsort($files); 168 | 169 | return array_map('basename', $files); 170 | } 171 | 172 | public function getLogDirectories() 173 | { 174 | return array_map([$this, 'replaceBasePath'], $this->files->directories($this->mergeDirectory())); 175 | } 176 | 177 | protected function replaceBasePath($v) 178 | { 179 | $basePath = str_replace('\\', '/', $this->getLogBasePath()); 180 | 181 | return str_replace($basePath.'/', '', str_replace('\\', '/', $v)); 182 | } 183 | 184 | public function mergeDirectory() 185 | { 186 | if (!$this->currentDirectory) { 187 | return $this->getLogBasePath(); 188 | } 189 | 190 | return $this->getLogBasePath().'/'.$this->currentDirectory; 191 | } 192 | 193 | /** 194 | * @return string 195 | */ 196 | public function getLogBasePath() 197 | { 198 | return $this->basePath; 199 | } 200 | 201 | /** 202 | * Get the last modified log file. 203 | * 204 | * @return string 205 | */ 206 | public function getLastModifiedLog() 207 | { 208 | return current($this->getLogFiles()); 209 | } 210 | 211 | public function getFile() 212 | { 213 | if (! $this->file) { 214 | $this->file = $this->getLastModifiedLog(); 215 | } 216 | 217 | return $this->file; 218 | } 219 | 220 | public function isCurrentFile($file) 221 | { 222 | return $this->replaceBasePath($this->getFilePath()) === trim($this->currentDirectory.'/'.$file, '/'); 223 | } 224 | 225 | /** 226 | * Get previous page url. 227 | * 228 | * @return bool|string 229 | */ 230 | public function getPrevPageUrl() 231 | { 232 | if ( 233 | !$this->getFilePath() 234 | || $this->pageOffset['end'] >= $this->getFilesize() - 1 235 | || $this->keyword 236 | ) { 237 | return false; 238 | } 239 | 240 | return route('dcat-log-viewer.file', [ 241 | 'file' => $this->getFile(), 242 | 'offset' => $this->pageOffset['end'], 243 | 'keyword' => $this->keyword, 244 | 'dir' => $this->currentDirectory, 245 | 'filename' => $this->filename, 246 | ]); 247 | } 248 | 249 | /** 250 | * Get Next page url. 251 | * 252 | * @return bool|string 253 | */ 254 | public function getNextPageUrl() 255 | { 256 | if ( 257 | !$this->getFilePath() 258 | || $this->pageOffset['start'] == 0 259 | || $this->keyword 260 | ) { 261 | return false; 262 | } 263 | 264 | return route('dcat-log-viewer.file', [ 265 | 'file' => $this->getFile(), 266 | 'offset' => -$this->pageOffset['start'], 267 | 'keyword' => $this->keyword, 268 | 'dir' => $this->currentDirectory, 269 | 'filename' => $this->filename, 270 | ]); 271 | } 272 | 273 | /** 274 | * Fetch logs by giving offset. 275 | * 276 | * @param int $seek 277 | * @param int $lines 278 | * @param int $buffer 279 | * 280 | * @return array 281 | * 282 | * @see http://www.geekality.net/2011/05/28/php-tail-tackling-large-files/ 283 | */ 284 | public function fetch($seek = 0, $lines = 20, $buffer = 4096) 285 | { 286 | $logs = $this->read($seek, $lines, $buffer); 287 | 288 | if (!$this->keyword || !$logs) { 289 | return $logs; 290 | } 291 | 292 | $result = []; 293 | 294 | foreach ($logs as $log) { 295 | if (Str::contains(strtolower(implode(' ', $log)), $this->keyword)) { 296 | $result[] = $log; 297 | } 298 | } 299 | 300 | if (count($result) >= $lines || !$this->getNextOffset()) { 301 | return $result; 302 | } 303 | 304 | return array_merge($result, $this->fetch($this->getNextOffset(), $lines - count($result), $buffer)); 305 | } 306 | 307 | public function getNextOffset() 308 | { 309 | if ($this->pageOffset['start'] == 0) { 310 | return false; 311 | } 312 | 313 | return -$this->pageOffset['start']; 314 | } 315 | 316 | protected function read($seek = 0, $lines = 20, $buffer = 4096) 317 | { 318 | if (! $this->getFilePath()) { 319 | return []; 320 | } 321 | 322 | $f = fopen($this->getFilePath(), 'rb'); 323 | 324 | $type = (preg_match('/\[(\d{4}(?:-\d{2}){2} \d{2}(?::\d{2}){2})\] (\w+)*/', fread($f, 34)) == 0) ? 'txt' : ''; 325 | 326 | if ($seek) { 327 | fseek($f, abs($seek)); 328 | } else { 329 | fseek($f, 0, SEEK_END); 330 | } 331 | 332 | if (fread($f, 1) != "\n") { 333 | $lines -= 1; 334 | } 335 | fseek($f, -1, SEEK_CUR); 336 | 337 | // 从前往后读,上一页 338 | // Start reading 339 | if ($seek > 0) { 340 | $output = $this->readPrevPage($f, $lines, $buffer, $type); 341 | // 从后往前读,下一页 342 | } else { 343 | $output = $this->readNextPage($f, $lines, $buffer, $type); 344 | } 345 | 346 | fclose($f); 347 | 348 | return $this->parseLog($output, $type); 349 | } 350 | 351 | protected function readPrevPage($f, &$lines, $buffer, $type = '') 352 | { 353 | $rule = ($type == 'txt') ? "\n" : "\n[20"; 354 | $output = ''; 355 | 356 | $this->pageOffset['start'] = ftell($f); 357 | 358 | while (!feof($f) && $lines >= 0) { 359 | $output = $output . ($chunk = fread($f, $buffer)); 360 | $lines -= substr_count($chunk, $rule); 361 | } 362 | 363 | $this->pageOffset['end'] = ftell($f); 364 | 365 | while ($lines++ < 0) { 366 | $strpos = strrpos($output, $rule) + 1; 367 | $_ = mb_strlen($output, '8bit') - $strpos; 368 | $output = substr($output, 0, $strpos); 369 | $this->pageOffset['end'] -= $_; 370 | } 371 | 372 | return $output; 373 | } 374 | 375 | // @lila 376 | protected function readNextPage($f, &$lines, $buffer, $type = '') 377 | { 378 | $rule = ($type == 'txt') ? "\n" : "\n[20"; 379 | $output = ''; 380 | 381 | $this->pageOffset['end'] = ftell($f); 382 | 383 | while (ftell($f) > 0 && $lines >= 0) { 384 | $offset = min(ftell($f), $buffer); 385 | fseek($f, -$offset, SEEK_CUR); 386 | $output = ($chunk = fread($f, $offset)) . $output; 387 | fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR); 388 | $lines -= substr_count($chunk, $rule); 389 | } 390 | 391 | $this->pageOffset['start'] = ftell($f); 392 | 393 | while ($lines++ < 0) { 394 | $strpos = strpos($output, $rule) + 1; 395 | $output = substr($output, $strpos); 396 | $this->pageOffset['start'] += $strpos; 397 | } 398 | 399 | return $output; 400 | } 401 | 402 | /** 403 | * Get tail logs in log file. 404 | * 405 | * @param int $seek 406 | * 407 | * @return array 408 | */ 409 | public function tail($seek) 410 | { 411 | // Open the file 412 | $f = fopen($this->getFilePath(), 'rb'); 413 | 414 | if (!$seek) { 415 | // Jump to last character 416 | fseek($f, -1, SEEK_END); 417 | } else { 418 | fseek($f, abs($seek)); 419 | } 420 | 421 | $output = ''; 422 | 423 | while (!feof($f)) { 424 | $output .= fread($f, 4096); 425 | } 426 | 427 | $pos = ftell($f); 428 | 429 | fclose($f); 430 | 431 | $logs = []; 432 | 433 | foreach ($this->parseLog(trim($output)) as $log) { 434 | $logs[] = $this->renderTableRow($log); 435 | } 436 | 437 | return [$pos, $logs]; 438 | } 439 | 440 | /** 441 | * Render table row. 442 | * 443 | * @param $log 444 | * 445 | * @return string 446 | */ 447 | protected function renderTableRow($log) 448 | { 449 | $color = self::$levelColors[$log['level']] ?? 'black'; 450 | 451 | $index = uniqid(); 452 | 453 | $button = ''; 454 | 455 | if (!empty($log['trace'])) { 456 | $button = "  Exception"; 457 | } 458 | 459 | $trace = ''; 460 | 461 | if (!empty($log['trace'])) { 462 | $trace = " 463 |
{$log['trace']}
464 | "; 465 | } 466 | 467 | return << 469 | {$log['level']} 470 | {$log['env']} 471 | {$log['time']} 472 | {$log['info']} 473 | $button 474 | 475 | $trace 476 | TPL; 477 | } 478 | 479 | /** 480 | * Parse raw log text to array. 481 | * 482 | * @param $raw 483 | * 484 | * @return array 485 | */ 486 | protected function parseLog($raw, $type = '') 487 | { 488 | if ($type == 'txt') { 489 | $logs = preg_split('/(\r\n|\n)/', trim($raw), -1, PREG_SPLIT_NO_EMPTY); 490 | } else { 491 | $logs = preg_split('/\[(\d{4}(?:-\d{2}){2} \d{2}(?::\d{2}){2})\] (\w+)\.(\w+):((?:(?!{"exception").)*)?/', trim($raw), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 492 | 493 | foreach ($logs as $index => $log) { 494 | if (preg_match('/^\d{4}/', $log)) { 495 | break; 496 | } else { 497 | unset($logs[$index]); 498 | } 499 | } 500 | } 501 | 502 | if (empty($logs)) { 503 | return []; 504 | } 505 | 506 | $parsed = []; 507 | $logs = array_values($logs); 508 | 509 | if ($type == 'txt') { 510 | foreach ($logs as $log) { 511 | $parsed[] = [ 512 | 'time' => '', 513 | 'env' => '', 514 | 'level' => '', 515 | 'info' => $log, 516 | 'trace' => '', 517 | ]; 518 | } 519 | $parsed = array_reverse($parsed); 520 | } else { 521 | foreach (array_chunk($logs, 5) as $log) { 522 | $parsed[] = [ 523 | 'time' => $log[0] ?? '', 524 | 'env' => $log[1] ?? '', 525 | 'level' => $log[2] ?? '', 526 | 'info' => $log[3] ?? '', 527 | 'trace' => $this->replaceRootPath(trim($log[4] ?? '')), 528 | ]; 529 | } 530 | rsort($parsed); 531 | } 532 | 533 | unset($logs); 534 | 535 | return $parsed; 536 | } 537 | 538 | protected function replaceRootPath($content) 539 | { 540 | $basePath = str_replace('\\', '/', base_path() . '/'); 541 | 542 | return str_replace($basePath, '', str_replace(['\\\\', '\\'], '/', $content)); 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /resources/view/log.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Laravel log viewer 7 | 8 | 9 | 10 | 11 | 12 | 16 | 222 | 223 | 224 | 225 |
226 | 227 |
228 | 229 |
230 | 233 | 234 |
235 |
236 |

237 | logs 238 | @if($dir) 239 | @php($tmp = '') 240 | @foreach(explode('/', $dir) as $v) 241 | @php($tmp .= '/'.$v) 242 | / 243 | {{ $v }} 244 | @endforeach 245 | @endif 246 |

247 |
248 | 249 |
250 |
251 | 252 |
253 |
254 | 255 |
256 | 275 |
276 | 277 |
278 | 279 | 280 |
281 | 282 | 283 | 284 |
285 |
286 |
287 | {{ trans('Download') }} 288 | 289 | {{-- --}} 290 |   291 |
292 |
293 | 294 | 295 | 296 |
297 |
298 |
299 | Size: {{ $size }}   Updated at: 300 | {{ date('Y-m-d H:i:s', filectime($filePath)) }} 301 |   302 |
303 | @if ($prevUrl) 304 | Previous 305 | @endif 306 | @if ($nextUrl) 307 | Next 308 | @endif 309 |
310 | 311 |
312 | 313 |
314 | 315 |
316 | 317 |
318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | @foreach($logs as $index => $log) 334 | 335 | 336 | 337 | 338 | 339 | 340 | 345 | 346 | 347 | @if (!empty($log['trace'])) 348 | 349 | 350 | 351 | @endif 352 | 353 | @endforeach 354 | 355 | 356 |
LevelEnvTimeMessage
{{ $index + 1 }}{{ $log['level'] }}{{ $log['env'] }}{{ $log['time'] }}
{{ $log['info'] }}
341 | @if(!empty($log['trace'])) 342 | 343 | @endif 344 |
{{ $log['trace'] }}
357 | 358 |
359 | 360 | 361 |
362 | 380 |
381 | 382 |
383 | 384 |
385 |
386 | 387 | 388 | 389 | 390 | {{----}} 391 | 392 | 393 | 394 | 395 | --------------------------------------------------------------------------------