├── README.md ├── config ├── general.php └── permissions.php ├── helpers ├── constants.php └── helpers.php ├── plugin.json ├── public ├── css │ └── log-viewer.css └── images │ └── screenshot.png ├── resources ├── assets │ └── sass │ │ └── log-viewer.scss ├── lang │ └── en │ │ ├── general.php │ │ ├── levels.php │ │ └── log-viewer.php └── views │ ├── logs.blade.php │ ├── partials │ ├── menu.blade.php │ └── style.blade.php │ └── show.blade.php ├── routes └── web.php ├── src ├── Bases │ └── Table.php ├── Contracts │ ├── LogViewer.php │ ├── Patternable.php │ ├── Table.php │ └── Utilities │ │ ├── Factory.php │ │ ├── Filesystem.php │ │ ├── LogChecker.php │ │ ├── LogLevels.php │ │ ├── LogMenu.php │ │ └── LogStyler.php ├── Entities │ ├── Log.php │ ├── LogCollection.php │ ├── LogEntry.php │ └── LogEntryCollection.php ├── Exceptions │ ├── FilesystemException.php │ ├── LogNotFoundException.php │ └── LogViewerException.php ├── Helpers │ └── LogParser.php ├── Http │ ├── Controllers │ │ └── LogViewerController.php │ └── Requests │ │ └── LogViewerRequest.php ├── LogViewer.php ├── Plugin.php ├── Providers │ └── LogViewerServiceProvider.php ├── Tables │ └── StatsTable.php └── Utilities │ ├── Factory.php │ ├── Filesystem.php │ ├── LogChecker.php │ ├── LogLevels.php │ ├── LogMenu.php │ └── LogStyler.php └── webpack.mix.js /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This is a plugin for Botble CMS so you have to purchase Botble CMS first to use this plugin. 3 | Purchase it here: [https://codecanyon.net/item/botble-cms-php-platform-based-on-laravel-framework/16928182](https://1.envato.market/LWRBY) 4 | 5 | # Installation 6 | - Download and rename folder `log-viewer-master` to `log-viewer`. 7 | - Copy folder `log-viewer` into `/platform/plugins`. 8 | - Go to Admin -> Plugins then activate plugin Log Viewer. 9 | 10 | # Screenshots 11 | 12 | ![Single log](https://raw.githubusercontent.com/botble/log-viewer/master/public/images/screenshot.png) 13 | 14 | # Credits 15 | This plugin is using source code from https://github.com/ARCANEDEV/LogViewer by [ARCANEDEV©](http://www.arcanedev.net) 16 | 17 | # Contact us 18 | - Website: [https://botble.com](https://botble.com) 19 | - Email: [contact@botble.com](mailto:contact@botble.com) 20 | -------------------------------------------------------------------------------- /config/general.php: -------------------------------------------------------------------------------- 1 | storage_path('logs'), 9 | 10 | /* ------------------------------------------------------------------------------------------------ 11 | | Log files pattern 12 | | ------------------------------------------------------------------------------------------------ 13 | */ 14 | 'pattern' => [ 15 | 'prefix' => 'laravel-', 16 | 'date' => '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]', 17 | 'extension' => '.log', 18 | ], 19 | 20 | /* ------------------------------------------------------------------------------------------------ 21 | | Locale 22 | | ------------------------------------------------------------------------------------------------ 23 | | Supported locales : 24 | | 'auto', 'ar', 'de', 'en', 'es', 'fa', 'fr', 'hu', 'hy', 'it', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 25 | | 'sv', 'th', 'tr', 'zh-TW', 'zh' 26 | */ 27 | 'locale' => 'auto', 28 | 29 | /* ------------------------------------------------------------------------------------------------ 30 | | Route settings 31 | | ------------------------------------------------------------------------------------------------ 32 | */ 33 | 'route' => [ 34 | 'enabled' => true, 35 | 36 | 'attributes' => [ 37 | 'prefix' => 'log-viewer', 38 | 39 | 'middleware' => null, 40 | ], 41 | ], 42 | 43 | /* ------------------------------------------------------------------------------------------------ 44 | | Log entries per page 45 | | ------------------------------------------------------------------------------------------------ 46 | | This defines how many log entries are displayed per page. 47 | */ 48 | 'per-page' => 30, 49 | 50 | /* ------------------------------------------------------------------------------------------------ 51 | | LogViewer's Facade 52 | | ------------------------------------------------------------------------------------------------ 53 | */ 54 | 'facade' => 'LogViewer', 55 | 56 | /* ------------------------------------------------------------------------------------------------ 57 | | Download settings 58 | | ------------------------------------------------------------------------------------------------ 59 | */ 60 | 'download' => [ 61 | 'prefix' => 'laravel-', 62 | 63 | 'extension' => 'log', 64 | ], 65 | 66 | /* ------------------------------------------------------------------------------------------------ 67 | | Menu settings 68 | | ------------------------------------------------------------------------------------------------ 69 | */ 70 | 'menu' => [ 71 | 'filter-route' => 'log-viewer::logs.filter', 72 | 73 | 'icons-enabled' => true, 74 | ], 75 | 76 | /* ------------------------------------------------------------------------------------------------ 77 | | Icons 78 | | ------------------------------------------------------------------------------------------------ 79 | */ 80 | 'icons' => [ 81 | /** 82 | * Font awesome >= 4.3 83 | * http://fontawesome.io/icons/ 84 | */ 85 | 'all' => 'fa fa-fw fa-list', // http://fontawesome.io/icon/list/ 86 | 'emergency' => 'fa fa-fw fa-bug', // http://fontawesome.io/icon/bug/ 87 | 'alert' => 'fa fa-fw fa-bullhorn', // http://fontawesome.io/icon/bullhorn/ 88 | 'critical' => 'fa fa-fw fa-heartbeat', // http://fontawesome.io/icon/heartbeat/ 89 | 'error' => 'fa fa-fw fa-times-circle', // http://fontawesome.io/icon/times-circle/ 90 | 'warning' => 'fa fa-fw fa-exclamation-triangle', // http://fontawesome.io/icon/exclamation-triangle/ 91 | 'notice' => 'fa fa-fw fa-exclamation-circle', // http://fontawesome.io/icon/exclamation-circle/ 92 | 'info' => 'fa fa-fw fa-info-circle', // http://fontawesome.io/icon/info-circle/ 93 | 'debug' => 'fa fa-fw fa-life-ring', // http://fontawesome.io/icon/life-ring/ 94 | ], 95 | 96 | /* ------------------------------------------------------------------------------------------------ 97 | | Colors 98 | | ------------------------------------------------------------------------------------------------ 99 | */ 100 | 'colors' => [ 101 | 'levels' => [ 102 | 'empty' => '#D1D1D1', 103 | 'all' => '#8A8A8A', 104 | 'emergency' => '#B71C1C', 105 | 'alert' => '#D32F2F', 106 | 'critical' => '#F44336', 107 | 'error' => '#FF5722', 108 | 'warning' => '#FF9100', 109 | 'notice' => '#4CAF50', 110 | 'info' => '#1976D2', 111 | 'debug' => '#90CAF9', 112 | ], 113 | ], 114 | ]; 115 | -------------------------------------------------------------------------------- /config/permissions.php: -------------------------------------------------------------------------------- 1 | 'Logs list', 6 | 'flag' => 'logs.index', 7 | 'parent_flag' => 'core.system', 8 | ], 9 | [ 10 | 'name' => 'Delete', 11 | 'flag' => 'logs.destroy', 12 | 'parent_flag' => 'logs.index', 13 | ], 14 | ]; -------------------------------------------------------------------------------- /helpers/constants.php: -------------------------------------------------------------------------------- 1 | 'All', 5 | 'date' => 'Date', 6 | 'name' => 'System logs', 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/en/levels.php: -------------------------------------------------------------------------------- 1 | 'All', 5 | 'emergency' => 'Emergency', 6 | 'alert' => 'Alert', 7 | 'critical' => 'Critical', 8 | 'error' => 'Error', 9 | 'warning' => 'Warning', 10 | 'notice' => 'Notice', 11 | 'info' => 'Info', 12 | 'debug' => 'Debug', 13 | ]; 14 | -------------------------------------------------------------------------------- /resources/lang/en/log-viewer.php: -------------------------------------------------------------------------------- 1 | 'System Logs', 5 | 'system_logs_description' => 'View system errors log.', 6 | 'name' => 'LogViewers', 7 | 'list' => 'List LogViewer', 8 | 'levels' => 'Levels', 9 | 'no_error' => 'There is no error now.', 10 | 'entries' => 'entries', 11 | 'actions' => 'Actions', 12 | 'delete_log_file' => 'Delete log file', 13 | 'loading' => 'Loading...', 14 | 'delete_button' => 'Delete file', 15 | 'confirm_delete_msg' => 'Are you sure you want to DELETE this log file :date ?', 16 | 'download' => 'Download', 17 | 'delete' => 'Delete', 18 | 'file_path' => 'File path', 19 | 'log_entries' => 'Log entries', 20 | 'size' => 'Size', 21 | 'page' => 'Page', 22 | 'of' => 'of', 23 | 'env' => 'Env', 24 | 'level' => 'Level', 25 | 'time' => 'Time', 26 | 'header' => 'Header', 27 | 'stack' => 'Stack', 28 | 'log_info' => 'Log info', 29 | 'menu_name' => 'System logs', 30 | ]; 31 | -------------------------------------------------------------------------------- /resources/views/logs.blade.php: -------------------------------------------------------------------------------- 1 | @extends(BaseHelper::getAdminMasterLayoutTemplate()) 2 | 3 | @section('content') 4 | @include('plugins/log-viewer::partials.style') 5 |
6 | 7 | {!! $rows->render() !!} 8 | 9 |
10 | 11 | 12 | 13 | @foreach($headers as $key => $header) 14 | 23 | @endforeach 24 | 25 | 26 | 27 | 28 | @if (count($rows) > 0) 29 | @foreach($rows as $date => $item) 30 | 31 | @foreach($item as $key => $value) 32 | 43 | @endforeach 44 | 55 | 56 | @endforeach 57 | @else 58 | 59 | 60 | 61 | @endif 62 | 63 |
15 | @if ($key == 'date') 16 | {{ $header }} 17 | @else 18 | 19 | {!! log_styler()->icon($key) . ' ' . $header !!} 20 | 21 | @endif 22 | {{ trans('plugins/log-viewer::log-viewer.actions') }}
33 | @if ($key == 'date') 34 | {{ $value }} 35 | @elseif ($value == 0) 36 | {{ $value }} 37 | @else 38 | 39 | {{ $value }} 40 | 41 | @endif 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
{{ trans('plugins/log-viewer::log-viewer.no_error') }}
64 |
65 | 66 | {!! $rows->render() !!} 67 | 68 | 91 |
92 | @stop 93 | 94 | @section('javascript') 95 | 147 | @stop 148 | -------------------------------------------------------------------------------- /resources/views/partials/menu.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
{{ trans('plugins/log-viewer::log-viewer.levels') }}
3 | 24 |
25 | -------------------------------------------------------------------------------- /resources/views/partials/style.blade.php: -------------------------------------------------------------------------------- 1 | 185 | -------------------------------------------------------------------------------- /resources/views/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends(BaseHelper::getAdminMasterLayoutTemplate()) 2 | 3 | @section('content') 4 | @include('plugins/log-viewer::partials.style') 5 |
6 |
7 |
8 | @include('plugins/log-viewer::partials.menu') 9 |
10 |
11 |
12 |
13 | {{ trans('plugins/log-viewer::log-viewer.log_info') }} : 14 | 22 |
23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 42 | 43 | 46 | 47 | 50 | 51 | 52 |
{{ trans('plugins/log-viewer::log-viewer.file_path') }} :{{ $log->getPath() }}
{{ trans('plugins/log-viewer::log-viewer.log_entries') }} : 36 | {{ $entries->total() }} 37 | {{ trans('plugins/log-viewer::log-viewer.size') }} : 40 | {{ $log->size() }} 41 | {{ trans('core/base::tables.created_at') }} : 44 | {{ $log->createdAt() }} 45 | {{ trans('core/base::tables.updated_at') }} : 48 | {{ $log->updatedAt() }} 49 |
53 |
54 |
55 | 56 |
57 | @if ($entries->hasPages()) 58 |
59 | {!! $entries->render() !!} 60 | 61 | {{ trans('plugins/log-viewer::log-viewer.page') }} {!! $entries->currentPage() !!} {{ trans('plugins/log-viewer::log-viewer.of') }} {!! $entries->lastPage() !!} 62 | 63 |
64 | @endif 65 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | @foreach($entries as $key => $entry) 79 | 80 | 83 | 86 | 89 | 92 | 101 | 102 | @if ($entry->hasStack()) 103 | 104 | 109 | 110 | @endif 111 | @endforeach 112 | 113 |
{{ trans('plugins/log-viewer::log-viewer.env') }}{{ trans('plugins/log-viewer::log-viewer.level') }}{{ trans('plugins/log-viewer::log-viewer.time') }}{{ trans('plugins/log-viewer::log-viewer.header') }}{{ trans('plugins/log-viewer::log-viewer.actions') }}
81 | {{ $entry->env }} 82 | 84 | {!! $entry->level() !!} 85 | 87 | {{ $entry->datetime->format('H:i:s') }} 88 | 90 |

{{ $entry->header }}

91 |
93 | @if ($entry->hasStack()) 94 | 99 | @endif 100 |
105 |
106 | {!! preg_replace("/\n/", '
', $entry->stack) !!} 107 |
108 |
114 |
115 | 116 | @if ($entries->hasPages()) 117 | 123 | @endif 124 |
125 |
126 |
127 | 128 | 151 | @endsection 152 | @section('javascript') 153 | 188 | @endsection 189 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | 'Botble\LogViewer\Http\Controllers', 'middleware' => 'web'], function () { 4 | Route::group(['prefix' => config('core.base.general.admin_dir'), 'middleware' => 'auth'], function () { 5 | Route::group(['prefix' => 'system/logs'], function () { 6 | 7 | Route::get('list', [ 8 | 'as' => 'log-viewer::logs.index', 9 | 'uses' => 'LogViewerController@listLogs', 10 | 'permission' => 'logs.index', 11 | ]); 12 | 13 | Route::delete('delete', [ 14 | 'as' => 'log-viewer::logs.destroy', 15 | 'uses' => 'LogViewerController@delete', 16 | 'permission' => 'logs.destroy', 17 | ]); 18 | 19 | Route::group(['prefix' => '{date}'], function () { 20 | Route::get('', [ 21 | 'as' => 'log-viewer::logs.show', 22 | 'uses' => 'LogViewerController@show', 23 | 'middleware' => 'preventDemo', 24 | 'permission' => 'logs.index', 25 | ]); 26 | 27 | Route::get('download', [ 28 | 'as' => 'log-viewer::logs.download', 29 | 'uses' => 'LogViewerController@download', 30 | 'middleware' => 'preventDemo', 31 | 'permission' => 'logs.index', 32 | ]); 33 | 34 | Route::get('{level}', [ 35 | 'as' => 'log-viewer::logs.filter', 36 | 'uses' => 'LogViewerController@showByLevel', 37 | 'permission' => 'logs.index', 38 | ]); 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/Bases/Table.php: -------------------------------------------------------------------------------- 1 | setLevels($levels); 50 | $this->setLocale(empty($locale) ? config('plugins.log-viewer.general.locale') : $locale); 51 | $this->setData($data); 52 | $this->init(); 53 | } 54 | 55 | /** 56 | * Set LogLevels instance. 57 | * 58 | * @param LogLevelsContract $levels 59 | * 60 | * @return Table 61 | */ 62 | protected function setLevels(LogLevelsContract $levels) 63 | { 64 | $this->levels = $levels; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * Set table locale. 71 | * 72 | * @param string|null $locale 73 | * 74 | * @return Table 75 | */ 76 | protected function setLocale($locale) 77 | { 78 | if (empty($locale) || $locale === 'auto') { 79 | $locale = app()->getLocale(); 80 | } 81 | 82 | $this->locale = $locale; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Set table data. 89 | * 90 | * @param array $data 91 | * 92 | * @return self 93 | */ 94 | private function setData(array $data) 95 | { 96 | $this->data = $data; 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Prepare the table. 103 | */ 104 | private function init() 105 | { 106 | $this->header = $this->prepareHeader($this->data); 107 | $this->rows = $this->prepareRows($this->data); 108 | $this->footer = $this->prepareFooter($this->data); 109 | } 110 | 111 | /** 112 | * Prepare table header. 113 | * 114 | * @param array $data 115 | * 116 | * @return array 117 | */ 118 | abstract protected function prepareHeader(array $data); 119 | 120 | /** 121 | * Prepare table rows. 122 | * 123 | * @param array $data 124 | * 125 | * @return array 126 | */ 127 | abstract protected function prepareRows(array $data); 128 | 129 | /** 130 | * Prepare table footer. 131 | * 132 | * @param array $data 133 | * 134 | * @return array 135 | */ 136 | abstract protected function prepareFooter(array $data); 137 | 138 | /** 139 | * Get table header. 140 | * 141 | * @return array 142 | */ 143 | public function header() 144 | { 145 | return $this->header; 146 | } 147 | 148 | /** 149 | * Get table rows. 150 | * 151 | * @return array 152 | */ 153 | public function rows() 154 | { 155 | return $this->rows; 156 | } 157 | 158 | /** 159 | * Get table footer. 160 | * 161 | * @return array 162 | */ 163 | public function footer() 164 | { 165 | return $this->footer; 166 | } 167 | 168 | /** 169 | * Get raw data. 170 | * 171 | * @return array 172 | */ 173 | public function data() 174 | { 175 | return $this->data; 176 | } 177 | 178 | /** 179 | * Translate. 180 | * 181 | * @param string $key 182 | * 183 | * @return string 184 | */ 185 | protected function translate($key) 186 | { 187 | return trans('plugins/log-viewer::' . $key, [], $this->locale); 188 | } 189 | 190 | /** 191 | * Get log level color. 192 | * 193 | * @param string $level 194 | * 195 | * @return string 196 | */ 197 | protected function color($level) 198 | { 199 | return log_styler()->color($level); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Contracts/LogViewer.php: -------------------------------------------------------------------------------- 1 | entries = new LogEntryCollection; 49 | $this->date = $date; 50 | $this->path = $path; 51 | $this->file = new SplFileInfo($path); 52 | $this->raw = $raw; 53 | 54 | $this->entries->load($raw); 55 | } 56 | 57 | /** 58 | * Get log path. 59 | * 60 | * @return string 61 | */ 62 | public function getPath() 63 | { 64 | return $this->path; 65 | } 66 | 67 | /** 68 | * Get raw log content. 69 | * 70 | * @return string 71 | */ 72 | public function getRaw() 73 | { 74 | return $this->raw; 75 | } 76 | 77 | /** 78 | * Get file info. 79 | * 80 | * @return SplFileInfo 81 | */ 82 | public function file() 83 | { 84 | return $this->file; 85 | } 86 | 87 | /** 88 | * Get file size. 89 | * 90 | * @return string 91 | */ 92 | public function size() 93 | { 94 | return $this->formatSize($this->file->getSize()); 95 | } 96 | 97 | /** 98 | * Get file creation date. 99 | * 100 | * @return Carbon 101 | */ 102 | public function createdAt() 103 | { 104 | return Carbon::createFromTimestamp($this->file()->getATime()); 105 | } 106 | 107 | /** 108 | * Get file modification date. 109 | * 110 | * @return Carbon 111 | */ 112 | public function updatedAt() 113 | { 114 | return Carbon::createFromTimestamp($this->file()->getMTime()); 115 | } 116 | 117 | /** 118 | * Make a log object. 119 | * 120 | * @param string $date 121 | * @param string $path 122 | * @param string $raw 123 | * 124 | * @return self 125 | */ 126 | public static function make($date, $path, $raw) 127 | { 128 | return new self($date, $path, $raw); 129 | } 130 | 131 | /** 132 | * Get log entries. 133 | * 134 | * @param string $level 135 | * 136 | * @return LogEntryCollection 137 | */ 138 | public function entries($level = 'all') 139 | { 140 | if ($level === 'all') { 141 | return $this->entries; 142 | } 143 | 144 | return $this->getByLevel($level); 145 | } 146 | 147 | /** 148 | * Get filtered log entries by level. 149 | * 150 | * @param string $level 151 | * 152 | * @return LogEntryCollection 153 | */ 154 | public function getByLevel($level) 155 | { 156 | return $this->entries->filterByLevel($level); 157 | } 158 | 159 | /** 160 | * Get log stats. 161 | * 162 | * @return array 163 | */ 164 | public function stats() 165 | { 166 | return $this->entries->stats(); 167 | } 168 | 169 | /** 170 | * Get the log navigation tree. 171 | * 172 | * @param bool $trans 173 | * 174 | * @return array 175 | */ 176 | public function tree($trans = false) 177 | { 178 | return $this->entries->tree($trans); 179 | } 180 | 181 | /** 182 | * Get log entries menu. 183 | * 184 | * @param bool $trans 185 | * 186 | * @return array 187 | */ 188 | public function menu($trans = true) 189 | { 190 | return log_menu()->make($this, $trans); 191 | } 192 | 193 | /** 194 | * Get the log as a plain array. 195 | * 196 | * @return array 197 | */ 198 | public function toArray() 199 | { 200 | return [ 201 | 'date' => $this->date, 202 | 'path' => $this->path, 203 | 'entries' => $this->entries->toArray(), 204 | ]; 205 | } 206 | 207 | /** 208 | * Convert the object to its JSON representation. 209 | * 210 | * @param int $options 211 | * 212 | * @return string 213 | */ 214 | public function toJson($options = 0) 215 | { 216 | return json_encode($this->toArray(), $options); 217 | } 218 | 219 | /** 220 | * Serialize the log object to json data. 221 | * 222 | * @return array 223 | */ 224 | public function jsonSerialize() 225 | { 226 | return $this->toArray(); 227 | } 228 | 229 | /** 230 | * Format the file size. 231 | * 232 | * @param int $bytes 233 | * @param int $precision 234 | * @return string 235 | */ 236 | protected function formatSize($bytes, $precision = 2) 237 | { 238 | $units = ['B', 'KB', 'MB', 'GB', 'TB']; 239 | 240 | $bytes = max($bytes, 0); 241 | $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); 242 | $pow = min($pow, count($units) - 1); 243 | 244 | return round($bytes / pow(1024, $pow), $precision) . ' ' . $units[$pow]; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/Entities/LogCollection.php: -------------------------------------------------------------------------------- 1 | setFilesystem(app('botble::log-viewer.filesystem')); 26 | parent::__construct($items); 27 | 28 | if (empty($items)) { 29 | $this->load(); 30 | } 31 | } 32 | 33 | /** 34 | * Set the filesystem instance. 35 | * 36 | * @param Filesystem $filesystem 37 | * 38 | * @return LogCollection 39 | */ 40 | public function setFilesystem(FilesystemContract $filesystem) 41 | { 42 | $this->filesystem = $filesystem; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * Load all logs. 49 | * 50 | * @return LogCollection 51 | */ 52 | private function load() 53 | { 54 | foreach ($this->filesystem->dates(true) as $date => $path) { 55 | $raw = $this->filesystem->read($date); 56 | 57 | $this->put($date, Log::make($date, $path, $raw)); 58 | } 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * Get a log. 65 | * 66 | * @param string $date 67 | * @param mixed|null $default 68 | * 69 | * @return Log 70 | * 71 | * @throws LogNotFoundException 72 | */ 73 | public function get($date, $default = null) 74 | { 75 | if (!$this->has($date)) { 76 | throw new LogNotFoundException('Log not found in this date [' . $date . ']'); 77 | } 78 | 79 | return parent::get($date, $default); 80 | } 81 | 82 | /** 83 | * Paginate logs. 84 | * 85 | * @param int $perPage 86 | * 87 | * @return LengthAwarePaginator 88 | */ 89 | public function paginate($perPage = 30) 90 | { 91 | $request = request(); 92 | $currentPage = $request->input('page', 1); 93 | $paginator = new LengthAwarePaginator( 94 | $this->slice(($currentPage * $perPage) - $perPage, $perPage), 95 | $this->count(), 96 | $perPage, 97 | $currentPage 98 | ); 99 | 100 | return $paginator->setPath($request->url()); 101 | } 102 | 103 | /** 104 | * Get a log (alias). 105 | * 106 | * @param string $date 107 | * 108 | * @return Log 109 | * @throws LogNotFoundException 110 | * @see get() 111 | * 112 | */ 113 | public function log($date) 114 | { 115 | return $this->get($date); 116 | } 117 | 118 | 119 | /** 120 | * Get log entries. 121 | * 122 | * @param string $date 123 | * @param string $level 124 | * 125 | * @return LogEntryCollection 126 | * @throws LogNotFoundException 127 | */ 128 | public function entries($date, $level = 'all') 129 | { 130 | return $this->get($date)->entries($level); 131 | } 132 | 133 | /** 134 | * Get logs statistics. 135 | * 136 | * @return array 137 | */ 138 | public function stats() 139 | { 140 | $stats = []; 141 | 142 | foreach ($this->items as $date => $log) { 143 | $stats[$date] = $log->stats(); 144 | } 145 | 146 | return $stats; 147 | } 148 | 149 | /** 150 | * List the log files (dates). 151 | * 152 | * @return array 153 | */ 154 | public function dates() 155 | { 156 | return $this->keys()->toArray(); 157 | } 158 | 159 | /** 160 | * Get entries total. 161 | * 162 | * @param string $level 163 | * 164 | * @return int 165 | */ 166 | public function total($level = 'all') 167 | { 168 | return (int)$this->sum(function (Log $log) use ($level) { 169 | return $log->entries($level)->count(); 170 | }); 171 | } 172 | 173 | /** 174 | * Get logs tree. 175 | * 176 | * @param bool $trans 177 | * 178 | * @return array 179 | */ 180 | public function tree($trans = false) 181 | { 182 | $tree = []; 183 | 184 | foreach ($this->items as $date => $log) { 185 | $tree[$date] = $log->tree($trans); 186 | } 187 | 188 | return $tree; 189 | } 190 | 191 | /** 192 | * Get logs menu. 193 | * 194 | * @param bool $trans 195 | * 196 | * @return array 197 | */ 198 | public function menu($trans = true) 199 | { 200 | $menu = []; 201 | 202 | foreach ($this->items as $date => $log) { 203 | $menu[$date] = $log->menu($trans); 204 | } 205 | 206 | return $menu; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Entities/LogEntry.php: -------------------------------------------------------------------------------- 1 | setLevel($level); 47 | $this->setHeader($header); 48 | $this->setStack($stack); 49 | } 50 | 51 | /** 52 | * Set the entry level. 53 | * 54 | * @param string $level 55 | * 56 | * @return self 57 | */ 58 | private function setLevel($level) 59 | { 60 | $this->level = $level; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Set the entry header. 67 | * 68 | * @param string $header 69 | * 70 | * @return self 71 | */ 72 | private function setHeader($header) 73 | { 74 | $this->setDatetime($this->extractDatetime($header)); 75 | 76 | $header = $this->cleanHeader($header); 77 | 78 | if (preg_match('/^[a-z]+.[A-Z]+:/', $header, $out)) { 79 | $this->setEnv($out[0]); 80 | $header = trim(str_replace($out[0], '', $header)); 81 | } 82 | 83 | $this->header = $header; 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Set entry environment. 90 | * 91 | * @param string $env 92 | * 93 | * @return self 94 | */ 95 | private function setEnv($env) 96 | { 97 | $this->env = head(explode('.', $env)); 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Set the entry date time. 104 | * 105 | * @param string $datetime 106 | * 107 | * @return LogEntry 108 | */ 109 | private function setDatetime($datetime) 110 | { 111 | $this->datetime = Carbon::createFromFormat('Y-m-d H:i:s', $datetime); 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Set the entry stack. 118 | * 119 | * @param string $stack 120 | * 121 | * @return self 122 | */ 123 | private function setStack($stack) 124 | { 125 | $this->stack = $stack; 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Get translated level name with icon. 132 | * 133 | * @return string 134 | */ 135 | public function level() 136 | { 137 | return $this->icon() . ' ' . $this->name(); 138 | } 139 | 140 | /** 141 | * Get translated level name. 142 | * 143 | * @return string 144 | */ 145 | public function name() 146 | { 147 | return log_levels()->get($this->level); 148 | } 149 | 150 | /** 151 | * Get level icon. 152 | * 153 | * @return string 154 | */ 155 | public function icon() 156 | { 157 | return log_styler()->icon($this->level); 158 | } 159 | 160 | /** 161 | * Check if same log level. 162 | * 163 | * @param string $level 164 | * 165 | * @return bool 166 | */ 167 | public function isSameLevel($level) 168 | { 169 | return $this->level === $level; 170 | } 171 | 172 | /** 173 | * Get the log entry as an array. 174 | * 175 | * @return array 176 | */ 177 | public function toArray() 178 | { 179 | return [ 180 | 'level' => $this->level, 181 | 'datetime' => $this->datetime->format('Y-m-d H:i:s'), 182 | 'header' => $this->header, 183 | 'stack' => $this->stack, 184 | ]; 185 | } 186 | 187 | /** 188 | * Convert the log entry to its JSON representation. 189 | * 190 | * @param int $options 191 | * 192 | * @return string 193 | */ 194 | public function toJson($options = 0) 195 | { 196 | return json_encode($this->toArray(), $options); 197 | } 198 | 199 | /** 200 | * Serialize the log entry object to json data. 201 | * 202 | * @return array 203 | */ 204 | public function jsonSerialize() 205 | { 206 | return $this->toArray(); 207 | } 208 | 209 | /** 210 | * Check if the entry has a stack. 211 | * 212 | * @return bool 213 | */ 214 | public function hasStack() 215 | { 216 | return $this->stack !== "\n"; 217 | } 218 | 219 | /** 220 | * Clean the entry header. 221 | * 222 | * @param string $header 223 | * 224 | * @return string 225 | */ 226 | private function cleanHeader($header) 227 | { 228 | return preg_replace('/\[' . REGEX_DATETIME_PATTERN . '\][ ]/', '', $header); 229 | } 230 | 231 | /** 232 | * Extract datetime from the header. 233 | * 234 | * @param string $header 235 | * 236 | * @return string 237 | */ 238 | private function extractDatetime($header) 239 | { 240 | return preg_replace('/^\[(' . REGEX_DATETIME_PATTERN . ')\].*/', '$1', $header); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/Entities/LogEntryCollection.php: -------------------------------------------------------------------------------- 1 | push(new LogEntry($level, $header, $stack)); 24 | } 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * Paginate log entries. 31 | * 32 | * @param int $perPage 33 | * 34 | * @return LengthAwarePaginator 35 | */ 36 | public function paginate($perPage = 20) 37 | { 38 | $request = request(); 39 | $page = $request->input('page', 1); 40 | $paginator = new LengthAwarePaginator( 41 | $this->slice(($page * $perPage) - $perPage, $perPage), 42 | $this->count(), 43 | $perPage, 44 | $page 45 | ); 46 | 47 | return $paginator->setPath($request->url()); 48 | } 49 | 50 | /** 51 | * Get filtered log entries by level. 52 | * 53 | * @param string $level 54 | * 55 | * @return LogEntryCollection 56 | */ 57 | public function filterByLevel($level) 58 | { 59 | return $this->filter(function (LogEntry $entry) use ($level) { 60 | return $entry->isSameLevel($level); 61 | }); 62 | } 63 | 64 | /** 65 | * Get log entries stats. 66 | * 67 | * @return array 68 | */ 69 | public function stats() 70 | { 71 | $counters = $this->initStats(); 72 | 73 | foreach ($this->groupBy('level') as $level => $entries) { 74 | $counters[$level] = $count = count($entries); 75 | $counters['all'] += $count; 76 | } 77 | 78 | return $counters; 79 | } 80 | 81 | /** 82 | * Get the log entries navigation tree. 83 | * 84 | * @param bool|false $trans 85 | * 86 | * @return array 87 | */ 88 | public function tree($trans = false) 89 | { 90 | $tree = $this->stats(); 91 | 92 | array_walk($tree, function (&$count, $level) use ($trans) { 93 | $count = [ 94 | 'name' => $trans ? log_levels()->get($level) : $level, 95 | 'count' => $count, 96 | ]; 97 | }); 98 | 99 | return $tree; 100 | } 101 | 102 | /** 103 | * Init stats counters. 104 | * 105 | * @return array 106 | */ 107 | protected function initStats() 108 | { 109 | $levels = array_merge_recursive( 110 | ['all'], 111 | array_keys(log_viewer()->levels(true)) 112 | ); 113 | 114 | return array_map(function () { 115 | return 0; 116 | }, array_flip($levels)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Exceptions/FilesystemException.php: -------------------------------------------------------------------------------- 1 | $level, 76 | 'header' => $heading[$key], 77 | 'stack' => $data[$key], 78 | ]; 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * Check if header has a log level. 85 | * 86 | * @param string $heading 87 | * @param string $level 88 | * @return bool 89 | */ 90 | protected static function hasLogLevel($heading, $level) 91 | { 92 | return Str::contains(strtolower($heading), strtolower('.' . $level)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Http/Controllers/LogViewerController.php: -------------------------------------------------------------------------------- 1 | logViewer = $logViewer; 43 | $this->perPage = config('plugins.log-viewer.general.per-page', $this->perPage); 44 | Assets::addScripts(['moment', 'datetimepicker']) 45 | ->addStyles(['datetimepicker']); 46 | } 47 | 48 | /** 49 | * List all logs. 50 | * 51 | * @param Request $request 52 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|View 53 | */ 54 | public function listLogs(Request $request) 55 | { 56 | page_title()->setTitle(trans('plugins/log-viewer::general.name')); 57 | 58 | Assets::addStylesDirectly(['/vendor/core/plugins/log-viewer/css/log-viewer.css']); 59 | 60 | $stats = $this->logViewer->statsTable(); 61 | $headers = $stats->header(); 62 | $rows = $this->paginate($stats->rows(), $request); 63 | 64 | return view('plugins/log-viewer::logs', compact('headers', 'rows')); 65 | } 66 | 67 | /** 68 | * Paginate logs. 69 | * 70 | * @param array $data 71 | * @param Request $request 72 | * @return LengthAwarePaginator 73 | */ 74 | protected function paginate(array $data, Request $request) 75 | { 76 | $page = $request->get('page', 1); 77 | $offset = ($page * $this->perPage) - $this->perPage; 78 | $items = array_slice($data, $offset, $this->perPage, true); 79 | $rows = new LengthAwarePaginator($items, count($data), $this->perPage, $page); 80 | 81 | $rows->setPath($request->url()); 82 | 83 | return $rows; 84 | } 85 | 86 | /** 87 | * Show the log. 88 | * 89 | * @param string $date 90 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|View 91 | */ 92 | public function show($date) 93 | { 94 | page_title()->setTitle(trans('plugins/log-viewer::general.name') . ' ' . $date); 95 | 96 | $log = $this->getLogOrFail($date); 97 | $levels = $this->logViewer->levelsNames(); 98 | $entries = $log->entries()->paginate($this->perPage); 99 | 100 | return view('plugins/log-viewer::show', compact('log', 'levels', 'entries')); 101 | } 102 | 103 | /** 104 | * @param string|null $date 105 | * @return Log|null 106 | */ 107 | protected function getLogOrFail($date) 108 | { 109 | try { 110 | return $this->logViewer->get($date); 111 | } catch (LogNotFoundException $ex) { 112 | abort(404, $ex->getMessage()); 113 | } 114 | } 115 | 116 | /** 117 | * Filter the log entries by level. 118 | * 119 | * @param string $date 120 | * @param string $level 121 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|RedirectResponse|View 122 | */ 123 | public function showByLevel($date, $level) 124 | { 125 | page_title()->setTitle(trans('plugins/log-viewer::general.name') . ' ' . $date); 126 | 127 | $log = $this->getLogOrFail($date); 128 | 129 | if ($level === 'all') { 130 | return redirect()->route($this->showRoute, [$date]); 131 | } 132 | 133 | $levels = $this->logViewer->levelsNames(); 134 | $entries = $this->logViewer->entries($date, $level)->paginate($this->perPage); 135 | 136 | return view('plugins/log-viewer::show', compact('log', 'levels', 'entries')); 137 | } 138 | 139 | /** 140 | * Download the log 141 | * 142 | * @param string $date 143 | * @return BinaryFileResponse 144 | */ 145 | public function download($date) 146 | { 147 | return $this->logViewer->download($date); 148 | } 149 | 150 | /** 151 | * Delete a log. 152 | * 153 | * @param Request $request 154 | * @return JsonResponse 155 | * @throws FilesystemException 156 | */ 157 | public function delete(Request $request) 158 | { 159 | if (!$request->ajax()) { 160 | abort(405, 'Method Not Allowed'); 161 | } 162 | 163 | $date = $request->get('date'); 164 | 165 | return response()->json([ 166 | 'result' => $this->logViewer->delete($date) ? 'success' : 'error', 167 | ]); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Http/Requests/LogViewerRequest.php: -------------------------------------------------------------------------------- 1 | 'required', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LogViewer.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 53 | $this->filesystem = $filesystem; 54 | $this->levels = $levels; 55 | } 56 | 57 | /** 58 | * Get the log levels. 59 | * 60 | * @param bool $flip 61 | * 62 | * @return array 63 | */ 64 | public function levels($flip = false) 65 | { 66 | return $this->levels->lists($flip); 67 | } 68 | 69 | /** 70 | * Get the translated log levels. 71 | * 72 | * @param string|null $locale 73 | * 74 | * @return array 75 | */ 76 | public function levelsNames($locale = null) 77 | { 78 | return $this->levels->names($locale); 79 | } 80 | 81 | /** 82 | * Set the log storage path. 83 | * 84 | * @param string $path 85 | * 86 | * @return LogViewer 87 | */ 88 | public function setPath($path) 89 | { 90 | $this->factory->setPath($path); 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Get the log pattern. 97 | * 98 | * @return string 99 | */ 100 | public function getPattern() 101 | { 102 | return $this->factory->getPattern(); 103 | } 104 | 105 | /** 106 | * Set the log pattern. 107 | * 108 | * @param string $date 109 | * @param string $prefix 110 | * @param string $extension 111 | * @return LogViewer 112 | */ 113 | public function setPattern( 114 | $prefix = FilesystemContract::PATTERN_PREFIX, 115 | $date = FilesystemContract::PATTERN_DATE, 116 | $extension = FilesystemContract::PATTERN_EXTENSION 117 | ) { 118 | $this->factory->setPattern($prefix, $date, $extension); 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Get all logs. 125 | * 126 | * @return LogCollection 127 | */ 128 | public function all() 129 | { 130 | return $this->factory->all(); 131 | } 132 | 133 | /** 134 | * Paginate all logs. 135 | * 136 | * @param int $perPage 137 | * 138 | * @return LengthAwarePaginator 139 | */ 140 | public function paginate($perPage = 30) 141 | { 142 | return $this->factory->paginate($perPage); 143 | } 144 | 145 | /** 146 | * Get a log. 147 | * 148 | * @param string $date 149 | * 150 | * @return Log 151 | */ 152 | public function get($date) 153 | { 154 | return $this->factory->log($date); 155 | } 156 | 157 | /** 158 | * Get the log entries. 159 | * 160 | * @param string $date 161 | * @param string $level 162 | * 163 | * @return LogEntryCollection 164 | */ 165 | public function entries($date, $level = 'all') 166 | { 167 | return $this->factory->entries($date, $level); 168 | } 169 | 170 | /** 171 | * Download a log file. 172 | * 173 | * @param string $date 174 | * @param string|null $filename 175 | * @param array $headers 176 | * @return BinaryFileResponse 177 | */ 178 | public function download($date, $filename = null, $headers = []) 179 | { 180 | if (empty($filename)) { 181 | $filename = 'laravel-' . $date . '.log'; 182 | } 183 | 184 | $path = $this->filesystem->path($date); 185 | 186 | return response()->download($path, $filename, $headers); 187 | } 188 | 189 | /** 190 | * Get logs statistics. 191 | * 192 | * @return array 193 | */ 194 | public function stats() 195 | { 196 | return $this->factory->stats(); 197 | } 198 | 199 | /** 200 | * Get logs statistics table. 201 | * 202 | * @param string|null $locale 203 | * 204 | * @return StatsTable 205 | */ 206 | public function statsTable($locale = null) 207 | { 208 | return $this->factory->statsTable($locale); 209 | } 210 | 211 | /** 212 | * Delete the log. 213 | * 214 | * @param string $date 215 | * 216 | * @return bool 217 | * @throws Exceptions\FilesystemException 218 | */ 219 | public function delete($date) 220 | { 221 | return $this->filesystem->delete($date); 222 | } 223 | 224 | /** 225 | * Get all valid log files. 226 | * 227 | * @return array 228 | */ 229 | public function files() 230 | { 231 | return $this->filesystem->logs(); 232 | } 233 | 234 | /** 235 | * List the log files (only dates). 236 | * 237 | * @return array 238 | */ 239 | public function dates() 240 | { 241 | return $this->factory->dates(); 242 | } 243 | 244 | /** 245 | * Get logs count. 246 | * 247 | * @return int 248 | */ 249 | public function count() 250 | { 251 | return $this->factory->count(); 252 | } 253 | 254 | /** 255 | * Get entries total from all logs. 256 | * 257 | * @param string $level 258 | * 259 | * @return int 260 | */ 261 | public function total($level = 'all') 262 | { 263 | return $this->factory->total($level); 264 | } 265 | 266 | /** 267 | * Get logs tree. 268 | * 269 | * @param bool $trans 270 | * 271 | * @return array 272 | */ 273 | public function tree($trans = false) 274 | { 275 | return $this->factory->tree($trans); 276 | } 277 | 278 | /** 279 | * Get logs menu. 280 | * 281 | * @param bool $trans 282 | * 283 | * @return array 284 | */ 285 | public function menu($trans = true) 286 | { 287 | return $this->factory->menu($trans); 288 | } 289 | 290 | /** 291 | * Determine if the log folder is empty or not. 292 | * 293 | * @return bool 294 | */ 295 | public function isEmpty() 296 | { 297 | return $this->factory->isEmpty(); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/Plugin.php: -------------------------------------------------------------------------------- 1 | app->bind('botble::log-viewer', LogViewer::class); 25 | 26 | Helper::autoload(__DIR__ . '/../../helpers'); 27 | 28 | $this->app->singleton('botble::log-viewer.levels', function ($app) { 29 | return new Utilities\LogLevels($app['translator'], config('plugins.log-viewer.general.locale')); 30 | }); 31 | $this->app->bind(Contracts\Utilities\LogLevels::class, 'botble::log-viewer.levels'); 32 | 33 | $this->app->singleton('botble::log-viewer.styler', Utilities\LogStyler::class); 34 | $this->app->bind(Contracts\Utilities\LogStyler::class, 'botble::log-viewer.styler'); 35 | 36 | $this->app->singleton('botble::log-viewer.menu', Utilities\LogMenu::class); 37 | $this->app->bind(Contracts\Utilities\LogMenu::class, 'botble::log-viewer.menu'); 38 | 39 | $this->app->singleton('botble::log-viewer.filesystem', function ($app) { 40 | $filesystem = new Utilities\Filesystem($app['files'], config('plugins.log-viewer.general.storage-path')); 41 | 42 | $filesystem->setPattern( 43 | config('plugins.log-viewer.general.pattern.prefix', Utilities\Filesystem::PATTERN_PREFIX), 44 | config('plugins.log-viewer.general.pattern.date', Utilities\Filesystem::PATTERN_DATE), 45 | config('plugins.log-viewer.general.pattern.extension', Utilities\Filesystem::PATTERN_EXTENSION) 46 | ); 47 | 48 | return $filesystem; 49 | }); 50 | $this->app->bind(Contracts\Utilities\Filesystem::class, 'botble::log-viewer.filesystem'); 51 | 52 | $this->app->singleton('botble::log-viewer.factory', Utilities\Factory::class); 53 | $this->app->bind(Contracts\Utilities\Factory::class, 'botble::log-viewer.factory'); 54 | 55 | $this->app->singleton('botble::log-viewer.checker', Utilities\LogChecker::class); 56 | $this->app->bind(Contracts\Utilities\LogChecker::class, 'botble::log-viewer.checker'); 57 | } 58 | 59 | public function boot() 60 | { 61 | $this->setNamespace('plugins/log-viewer') 62 | ->loadAndPublishConfigurations(['general', 'permissions']) 63 | ->loadRoutes(['web']) 64 | ->loadAndPublishViews() 65 | ->loadAndPublishTranslations() 66 | ->loadMigrations() 67 | ->publishAssets(); 68 | 69 | if (version_compare('7.0.0', get_core_version(), '>=')) { 70 | $this->app['events']->listen(RouteMatched::class, function () { 71 | DashboardMenu::registerItem([ 72 | 'id' => 'cms-plugin-log-viewer', 73 | 'priority' => 7, 74 | 'parent_id' => 'cms-core-platform-administration', 75 | 'name' => 'plugins/log-viewer::log-viewer.system_logs', 76 | 'icon' => null, 77 | 'url' => route('log-viewer::logs.index'), 78 | 'permissions' => ['log-viewer::logs.index'], 79 | ]); 80 | }); 81 | } else { 82 | PanelSectionManager::group('system')->beforeRendering(function (Manager $manager) { 83 | $manager 84 | ->registerItem( 85 | SystemPanelSection::class, 86 | fn() => PanelSectionItem::make('system.log-viewer') 87 | ->setTitle(trans('plugins/log-viewer::log-viewer.system_logs')) 88 | ->withDescription(trans('plugins/log-viewer::log-viewer.system_logs_description')) 89 | ->withIcon('ti ti-report') 90 | ->withPriority(9980) 91 | ->withRoute('log-viewer::logs.index') 92 | ); 93 | }); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Tables/StatsTable.php: -------------------------------------------------------------------------------- 1 | setLocale($locale); 19 | 20 | $json = []; 21 | $levels = Arr::except($this->footer(), 'all'); 22 | 23 | foreach ($levels as $level => $count) { 24 | $json[] = [ 25 | 'label' => $this->translate('levels.' . $level), 26 | 'value' => $count, 27 | 'color' => $this->color($level), 28 | 'highlight' => $this->color($level), 29 | ]; 30 | } 31 | 32 | return json_encode(array_values($json), JSON_PRETTY_PRINT); 33 | } 34 | 35 | protected function prepareHeader(array $data): array 36 | { 37 | return array_merge_recursive( 38 | [ 39 | 'date' => $this->translate('general.date'), 40 | 'all' => $this->translate('general.all'), 41 | ], 42 | $this->levels->names($this->locale) 43 | ); 44 | } 45 | 46 | protected function prepareRows(array $data): array 47 | { 48 | $rows = []; 49 | 50 | foreach ($data as $date => $levels) { 51 | $rows[$date] = array_merge(compact('date'), $levels); 52 | } 53 | 54 | return $rows; 55 | } 56 | 57 | protected function prepareFooter(array $data): array 58 | { 59 | $footer = []; 60 | 61 | foreach ($data as $levels) { 62 | foreach ($levels as $level => $count) { 63 | if (!isset($footer[$level])) { 64 | $footer[$level] = 0; 65 | } 66 | 67 | $footer[$level] += $count; 68 | } 69 | } 70 | 71 | return $footer; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Utilities/Factory.php: -------------------------------------------------------------------------------- 1 | setFilesystem($filesystem); 37 | $this->setLevels($levels); 38 | } 39 | 40 | /** 41 | * Get the filesystem instance. 42 | * 43 | * @return FilesystemContract 44 | */ 45 | public function getFilesystem() 46 | { 47 | return $this->filesystem; 48 | } 49 | 50 | /** 51 | * Set the filesystem instance. 52 | * 53 | * @param FilesystemContract $filesystem 54 | * 55 | * @return Factory 56 | */ 57 | public function setFilesystem(FilesystemContract $filesystem) 58 | { 59 | $this->filesystem = $filesystem; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Get the log levels instance. 66 | * 67 | * @return LogLevelsContract 68 | */ 69 | public function getLevels() 70 | { 71 | return $this->levels; 72 | } 73 | 74 | /** 75 | * Set the log levels instance. 76 | * 77 | * @param LogLevelsContract $levels 78 | * 79 | * @return Factory 80 | */ 81 | public function setLevels(LogLevelsContract $levels) 82 | { 83 | $this->levels = $levels; 84 | 85 | return $this; 86 | } 87 | 88 | /** 89 | * Set the log storage path. 90 | * 91 | * @param string $storagePath 92 | * 93 | * @return Factory 94 | */ 95 | public function setPath($storagePath) 96 | { 97 | $this->filesystem->setPath($storagePath); 98 | 99 | return $this; 100 | } 101 | 102 | /** 103 | * Get the log pattern. 104 | * 105 | * @return string 106 | */ 107 | public function getPattern() 108 | { 109 | return $this->filesystem->getPattern(); 110 | } 111 | 112 | /** 113 | * Set the log pattern. 114 | * 115 | * @param string $date 116 | * @param string $prefix 117 | * @param string $extension 118 | * 119 | * @return Factory 120 | */ 121 | public function setPattern( 122 | $prefix = FilesystemContract::PATTERN_PREFIX, 123 | $date = FilesystemContract::PATTERN_DATE, 124 | $extension = FilesystemContract::PATTERN_EXTENSION 125 | ) { 126 | $this->filesystem->setPattern($prefix, $date, $extension); 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Get all logs. 133 | * 134 | * @return LogCollection 135 | */ 136 | public function logs() 137 | { 138 | return LogCollection::make()->setFilesystem($this->filesystem); 139 | } 140 | 141 | /** 142 | * Get all logs (alias). 143 | * 144 | * @return LogCollection 145 | */ 146 | public function all() 147 | { 148 | return $this->logs(); 149 | } 150 | 151 | /** 152 | * Paginate all logs. 153 | * 154 | * @param int $perPage 155 | * 156 | * @return LengthAwarePaginator 157 | */ 158 | public function paginate($perPage = 30) 159 | { 160 | return $this->logs()->paginate($perPage); 161 | } 162 | 163 | /** 164 | * Get a log by date. 165 | * 166 | * @param string $date 167 | * 168 | * @return Log 169 | */ 170 | public function log($date) 171 | { 172 | return $this->logs()->log($date); 173 | } 174 | 175 | /** 176 | * Get a log by date (alias). 177 | * 178 | * @param string $date 179 | * 180 | * @return Log 181 | */ 182 | public function get($date) 183 | { 184 | return $this->log($date); 185 | } 186 | 187 | /** 188 | * Get log entries. 189 | * 190 | * @param string $date 191 | * @param string $level 192 | * 193 | * @return LogEntryCollection 194 | */ 195 | public function entries($date, $level = 'all') 196 | { 197 | return $this->logs()->entries($date, $level); 198 | } 199 | 200 | /** 201 | * Get logs statistics. 202 | * 203 | * @return array 204 | */ 205 | public function stats() 206 | { 207 | return $this->logs()->stats(); 208 | } 209 | 210 | /** 211 | * Get logs statistics table. 212 | * 213 | * @param string|null $locale 214 | * 215 | * @return StatsTable 216 | */ 217 | public function statsTable($locale = null) 218 | { 219 | return StatsTable::make($this->stats(), $this->levels, $locale); 220 | } 221 | 222 | /** 223 | * List the log files (dates). 224 | * 225 | * @return array 226 | */ 227 | public function dates() 228 | { 229 | return $this->logs()->dates(); 230 | } 231 | 232 | /** 233 | * Get logs count. 234 | * 235 | * @return int 236 | */ 237 | public function count() 238 | { 239 | return $this->logs()->count(); 240 | } 241 | 242 | /** 243 | * Get total log entries. 244 | * 245 | * @param string $level 246 | * 247 | * @return int 248 | */ 249 | public function total($level = 'all') 250 | { 251 | return $this->logs()->total($level); 252 | } 253 | 254 | /** 255 | * Get tree menu. 256 | * 257 | * @param bool $trans 258 | * 259 | * @return array 260 | */ 261 | public function tree($trans = false) 262 | { 263 | return $this->logs()->tree($trans); 264 | } 265 | 266 | /** 267 | * Get tree menu. 268 | * 269 | * @param bool $trans 270 | * 271 | * @return array 272 | */ 273 | public function menu($trans = true) 274 | { 275 | return $this->logs()->menu($trans); 276 | } 277 | 278 | /** 279 | * Determine if the log folder is empty or not. 280 | * 281 | * @return bool 282 | */ 283 | public function isEmpty() 284 | { 285 | return $this->logs()->isEmpty(); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/Utilities/Filesystem.php: -------------------------------------------------------------------------------- 1 | filesystem = $files; 56 | $this->setPath($storagePath); 57 | $this->setPattern(); 58 | } 59 | 60 | /** 61 | * Get the files instance. 62 | * 63 | * @return IlluminateFilesystem 64 | */ 65 | public function getInstance() 66 | { 67 | return $this->filesystem; 68 | } 69 | 70 | /** 71 | * Set the log storage path. 72 | * 73 | * @param string $storagePath 74 | * 75 | * @return Filesystem 76 | */ 77 | public function setPath($storagePath) 78 | { 79 | $this->storagePath = $storagePath; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Get the log pattern. 86 | * 87 | * @return string 88 | */ 89 | public function getPattern() 90 | { 91 | return $this->prefixPattern . $this->datePattern . $this->extension; 92 | } 93 | 94 | /** 95 | * Set the log pattern. 96 | * 97 | * @param string $date 98 | * @param string $prefix 99 | * @param string $extension 100 | * 101 | * @return Filesystem 102 | */ 103 | public function setPattern( 104 | $prefix = self::PATTERN_PREFIX, 105 | $date = self::PATTERN_DATE, 106 | $extension = self::PATTERN_EXTENSION 107 | ) { 108 | $this->setPrefixPattern($prefix); 109 | $this->setDatePattern($date); 110 | $this->setExtension($extension); 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * Set the log date pattern. 117 | * 118 | * @param string $datePattern 119 | * 120 | * @return Filesystem 121 | */ 122 | public function setDatePattern($datePattern) 123 | { 124 | $this->datePattern = $datePattern; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * Set the log prefix pattern. 131 | * 132 | * @param string $prefixPattern 133 | * 134 | * @return Filesystem 135 | */ 136 | public function setPrefixPattern($prefixPattern) 137 | { 138 | $this->prefixPattern = $prefixPattern; 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Set the log extension. 145 | * 146 | * @param string $extension 147 | * 148 | * @return Filesystem 149 | */ 150 | public function setExtension($extension) 151 | { 152 | $this->extension = $extension; 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * Get all log files. 159 | * 160 | * @return array 161 | */ 162 | public function all() 163 | { 164 | return $this->getFiles('*' . $this->extension); 165 | } 166 | 167 | /** 168 | * Get all valid log files. 169 | * 170 | * @return array 171 | */ 172 | public function logs() 173 | { 174 | return $this->getFiles($this->getPattern()); 175 | } 176 | 177 | /** 178 | * List the log files (Only dates). 179 | * 180 | * @param bool $withPaths 181 | * 182 | * @return array 183 | */ 184 | public function dates($withPaths = false) 185 | { 186 | $files = array_reverse($this->logs()); 187 | $dates = $this->extractDates($files); 188 | 189 | if ($withPaths) { 190 | $dates = array_combine($dates, $files); // [date => file] 191 | } 192 | 193 | return $dates; 194 | } 195 | 196 | /** 197 | * Read the log. 198 | * 199 | * @param string $date 200 | * 201 | * @return string 202 | * 203 | * @throws FilesystemException 204 | */ 205 | public function read($date) 206 | { 207 | try { 208 | $path = $this->getLogPath($date); 209 | 210 | return $this->filesystem->get($path); 211 | } catch (Exception $exception) { 212 | throw new FilesystemException($exception->getMessage()); 213 | } 214 | } 215 | 216 | /** 217 | * Delete the log. 218 | * 219 | * @param string $date 220 | * 221 | * @return bool 222 | * 223 | * @throws FilesystemException 224 | */ 225 | public function delete($date) 226 | { 227 | $path = $this->getLogPath($date); 228 | 229 | // @codeCoverageIgnoreStart 230 | if (!$this->filesystem->delete($path)) { 231 | throw new FilesystemException('There was an error deleting the log.'); 232 | } 233 | // @codeCoverageIgnoreEnd 234 | 235 | return true; 236 | } 237 | 238 | /** 239 | * Get the log file path. 240 | * 241 | * @param string $date 242 | * 243 | * @return string 244 | * @throws FilesystemException 245 | */ 246 | public function path($date) 247 | { 248 | return $this->getLogPath($date); 249 | } 250 | 251 | /** 252 | * Get all files. 253 | * 254 | * @param string $pattern 255 | * 256 | * @return array 257 | */ 258 | private function getFiles($pattern) 259 | { 260 | $files = $this->filesystem->glob( 261 | $this->storagePath . DIRECTORY_SEPARATOR . $pattern, 262 | GLOB_BRACE 263 | ); 264 | 265 | return array_filter(array_map('realpath', $files)); 266 | } 267 | 268 | /** 269 | * Get the log file path. 270 | * 271 | * @param string $date 272 | * 273 | * @return string 274 | * 275 | * @throws FilesystemException 276 | */ 277 | private function getLogPath($date) 278 | { 279 | $path = $this->storagePath . DIRECTORY_SEPARATOR . $this->prefixPattern . $date . $this->extension; 280 | 281 | if (!$this->filesystem->exists($path)) { 282 | throw new FilesystemException('The log(s) could not be located at : ' . $path); 283 | } 284 | 285 | return realpath($path); 286 | } 287 | 288 | /** 289 | * Extract dates from files. 290 | * 291 | * @param array $files 292 | * 293 | * @return array 294 | */ 295 | private function extractDates(array $files) 296 | { 297 | return array_map(function ($file) { 298 | return extract_date(basename($file)); 299 | }, $files); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/Utilities/LogChecker.php: -------------------------------------------------------------------------------- 1 | files = []; 62 | $this->setConfig($config); 63 | $this->setFilesystem($filesystem); 64 | $this->refresh(); 65 | } 66 | 67 | /** 68 | * Set the config instance. 69 | * 70 | * @param ConfigContract $config 71 | * 72 | * @return LogChecker 73 | */ 74 | public function setConfig(ConfigContract $config) 75 | { 76 | $this->config = $config; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Set the Filesystem instance. 83 | * 84 | * @param FilesystemContract $filesystem 85 | * 86 | * @return LogChecker 87 | */ 88 | public function setFilesystem(FilesystemContract $filesystem) 89 | { 90 | $this->filesystem = $filesystem; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Refresh the checks. 97 | * 98 | * @return LogChecker 99 | */ 100 | private function refresh() 101 | { 102 | $this->setHandler($this->config->get('app.log', 'single')); 103 | 104 | $this->messages = [ 105 | 'handler' => '', 106 | 'files' => [], 107 | ]; 108 | $this->files = []; 109 | 110 | $this->checkHandler(); 111 | $this->checkLogFiles(); 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * Set the log handler mode. 118 | * 119 | * @param string $handler 120 | * 121 | * @return LogChecker 122 | */ 123 | protected function setHandler($handler) 124 | { 125 | $this->handler = strtolower($handler); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * Check the handler mode. 132 | */ 133 | private function checkHandler() 134 | { 135 | if ($this->isDaily()) { 136 | return; 137 | } 138 | 139 | $this->messages['handler'] = 'You should set the log handler to `daily` mode. Please check the LogViewer wiki page (Requirements) for more details.'; 140 | } 141 | 142 | /** 143 | * Is a daily handler mode ? 144 | * 145 | * @return bool 146 | */ 147 | protected function isDaily() 148 | { 149 | return $this->isSameHandler(self::HANDLER_DAILY); 150 | } 151 | 152 | /** 153 | * Is the handler is the same as the application log handler. 154 | * 155 | * @param string $handler 156 | * 157 | * @return bool 158 | */ 159 | protected function isSameHandler($handler) 160 | { 161 | return $this->handler === $handler; 162 | } 163 | 164 | /** 165 | * Check all log files. 166 | */ 167 | protected function checkLogFiles() 168 | { 169 | foreach ($this->filesystem->all() as $path) { 170 | $this->checkLogFile($path); 171 | } 172 | } 173 | 174 | /** 175 | * Check a log file. 176 | * 177 | * @param string $path 178 | */ 179 | protected function checkLogFile($path) 180 | { 181 | $status = true; 182 | $file = basename($path); 183 | $message = 'The log file [' . $file . '] is valid.'; 184 | 185 | if ($this->isSingleLogFile($file)) { 186 | $this->status = $status = false; 187 | $this->messages['files'][$file] = $message = 188 | 'You have a single log file in your application, you should split the [' . $file . '] into seperate log files.'; 189 | } elseif ($this->isInvalidLogDate($file)) { 190 | $this->status = $status = false; 191 | $this->messages['files'][$file] = $message = 192 | 'The log file [' . $file . '] has an invalid date, the format must be like laravel-YYYY-MM-DD.log.'; 193 | } 194 | 195 | $this->files[$file] = compact('status', 'message', 'path'); 196 | } 197 | 198 | /** 199 | * Check if it's not a single log file. 200 | * 201 | * @param string $file 202 | * 203 | * @return bool 204 | */ 205 | protected function isSingleLogFile($file) 206 | { 207 | return $file === 'laravel.log'; 208 | } 209 | 210 | /** 211 | * Check the date of the log file. 212 | * 213 | * @param string $file 214 | * 215 | * @return bool 216 | */ 217 | protected function isInvalidLogDate($file) 218 | { 219 | $pattern = '/laravel-(\d){4}-(\d){2}-(\d){2}.log/'; 220 | 221 | if ((bool)preg_match($pattern, $file) === false) { 222 | return true; 223 | } 224 | 225 | return false; 226 | } 227 | 228 | /** 229 | * Get messages. 230 | * 231 | * @return array 232 | */ 233 | public function messages() 234 | { 235 | $this->refresh(); 236 | 237 | return $this->messages; 238 | } 239 | 240 | /** 241 | * Check if the checker fails. 242 | * 243 | * @return bool 244 | */ 245 | public function fails() 246 | { 247 | return !$this->passes(); 248 | } 249 | 250 | /** 251 | * Check if the checker passes. 252 | * 253 | * @return bool 254 | */ 255 | public function passes() 256 | { 257 | $this->refresh(); 258 | 259 | return $this->status; 260 | } 261 | 262 | /** 263 | * Get the requirements. 264 | * 265 | * @return array 266 | */ 267 | public function requirements() 268 | { 269 | $this->refresh(); 270 | 271 | return $this->isDaily() ? [ 272 | 'status' => 'success', 273 | 'header' => 'Application requirements fulfilled.', 274 | 'message' => 'Are you ready to rock ?', 275 | ] : [ 276 | 'status' => 'failed', 277 | 'header' => 'Application requirements failed.', 278 | 'message' => $this->messages['handler'], 279 | ]; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/Utilities/LogLevels.php: -------------------------------------------------------------------------------- 1 | setTranslator($translator); 42 | $this->setLocale($locale); 43 | } 44 | 45 | /** 46 | * Set the Translator instance. 47 | * 48 | * @param Translator $translator 49 | * 50 | * @return LogLevels 51 | */ 52 | public function setTranslator(Translator $translator) 53 | { 54 | $this->translator = $translator; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * Get the selected locale. 61 | * 62 | * @return string 63 | */ 64 | public function getLocale() 65 | { 66 | return $this->locale === 'auto' 67 | ? $this->translator->getLocale() 68 | : $this->locale; 69 | } 70 | 71 | /** 72 | * Set the selected locale. 73 | * 74 | * @param string $locale 75 | * 76 | * @return LogLevels 77 | */ 78 | public function setLocale($locale) 79 | { 80 | $this->locale = empty($locale) ? 'auto' : $locale; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Get the log levels. 87 | * 88 | * @param bool $flip 89 | * 90 | * @return array 91 | */ 92 | public function lists($flip = false) 93 | { 94 | return self::all($flip); 95 | } 96 | 97 | /** 98 | * Get translated levels. 99 | * 100 | * @param string|null $locale 101 | * 102 | * @return array 103 | */ 104 | public function names($locale = null) 105 | { 106 | $levels = self::all(true); 107 | 108 | array_walk($levels, function (&$name, $level) use ($locale) { 109 | $name = $this->get($level, $locale); 110 | }); 111 | 112 | return $levels; 113 | } 114 | 115 | /** 116 | * Get PSR log levels. 117 | * 118 | * @param bool $flip 119 | * 120 | * @return array 121 | */ 122 | public static function all($flip = false) 123 | { 124 | if (empty(self::$levels)) { 125 | self::$levels = (new ReflectionClass(LogLevel::class)) 126 | ->getConstants(); 127 | } 128 | 129 | return $flip ? array_flip(self::$levels) : self::$levels; 130 | } 131 | 132 | /** 133 | * Get the translated level. 134 | * 135 | * @param string $key 136 | * @param string|null $locale 137 | * 138 | * @return string 139 | */ 140 | public function get($key, $locale = null) 141 | { 142 | if (empty($locale) || $locale === 'auto') { 143 | $locale = $this->getLocale(); 144 | } 145 | 146 | return $this->translator->get('plugins/log-viewer::levels.' . $key, [], $locale); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Utilities/LogMenu.php: -------------------------------------------------------------------------------- 1 | setConfig($config); 35 | $this->setLogStyler($styler); 36 | } 37 | 38 | /** 39 | * Set the config instance. 40 | * 41 | * @param ConfigContract $config 42 | * 43 | * @return LogMenu 44 | */ 45 | public function setConfig(ConfigContract $config) 46 | { 47 | $this->config = $config; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Set the log styler instance. 54 | * 55 | * @param LogStylerContract $styler 56 | * 57 | * @return LogMenu 58 | */ 59 | public function setLogStyler(LogStylerContract $styler) 60 | { 61 | $this->styler = $styler; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Make log menu. 68 | * 69 | * @param Log $log 70 | * @param bool $trans 71 | * 72 | * @return array 73 | */ 74 | public function make(Log $log, $trans = true) 75 | { 76 | $items = []; 77 | $route = $this->config('menu.filter-route'); 78 | 79 | foreach ($log->tree($trans) as $level => $item) { 80 | $items[$level] = array_merge($item, [ 81 | 'url' => route($route, [$log->date, $level]), 82 | 'icon' => $this->isIconsEnabled() ? $this->styler->icon($level) : '', 83 | ]); 84 | } 85 | 86 | return $items; 87 | } 88 | 89 | /** 90 | * Check if the icons are enabled. 91 | * 92 | * @return bool 93 | */ 94 | private function isIconsEnabled() 95 | { 96 | return (bool)$this->config('menu.icons-enabled', false); 97 | } 98 | 99 | /** 100 | * Get config. 101 | * 102 | * @param string $key 103 | * @param mixed $default 104 | * 105 | * @return mixed 106 | */ 107 | private function config($key, $default = null) 108 | { 109 | return $this->config->get('plugins.log-viewer.general.' . $key, $default); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Utilities/LogStyler.php: -------------------------------------------------------------------------------- 1 | config = $config; 25 | } 26 | 27 | /** 28 | * Get config. 29 | * 30 | * @param string $key 31 | * @param mixed $default 32 | * 33 | * @return mixed 34 | */ 35 | private function get($key, $default = null) 36 | { 37 | return $this->config->get('plugins.log-viewer.general.' . $key, $default); 38 | } 39 | 40 | /** 41 | * Make level icon. 42 | * 43 | * @param string $level 44 | * @param string|null $default 45 | * 46 | * @return string 47 | */ 48 | public function icon($level, $default = null) 49 | { 50 | return ''; 51 | } 52 | 53 | /** 54 | * Get level color. 55 | * 56 | * @param string $level 57 | * @param string|null $default 58 | * 59 | * @return string 60 | */ 61 | public function color($level, $default = null) 62 | { 63 | return $this->get('colors.levels.' . $level, $default); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | const publicPath = 'public/vendor/core/plugins/log-viewer'; 4 | const resourcePath = './platform/plugins/log-viewer'; 5 | 6 | mix 7 | .sass(resourcePath + '/resources/assets/sass/log-viewer.scss', publicPath + '/css') 8 | .copy(publicPath + '/css/log-viewer.css', resourcePath + '/public/css'); --------------------------------------------------------------------------------