├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src
├── Rap2hpoutre
│ └── LaravelLogViewer
│ │ ├── LaravelLogViewer.php
│ │ ├── LaravelLogViewerServiceProvider.php
│ │ ├── Level.php
│ │ └── Pattern.php
├── config
│ └── logviewer.php
├── controllers
│ └── LogViewerController.php
└── views
│ └── log.blade.php
└── tests
├── LaravelLogViewerTest.php
└── laravel.log
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | tests:
9 | strategy:
10 | fail-fast: true
11 | matrix:
12 | php: ["7.2", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4"]
13 | laravel: ["^6.0", "^7.0", "^8.0", "^9.0", "^10.0", "^11.0", "^12.0"]
14 | exclude:
15 | - php: "8.0"
16 | laravel: "^10.0"
17 | - php: "7.4"
18 | laravel: "^10.0"
19 | - php: "7.2"
20 | laravel: "^10.0"
21 | - php: "7.4"
22 | laravel: "^9.0"
23 | - php: "7.2"
24 | laravel: "^9.0"
25 | - php: "8.4"
26 | laravel: "^8.0"
27 | - php: "8.3"
28 | laravel: "^8.0"
29 | - php: "8.2"
30 | laravel: "^8.0"
31 | - php: "7.2"
32 | laravel: "^8.0"
33 | - php: "8.4"
34 | laravel: "^7.0"
35 | - php: "8.3"
36 | laravel: "^7.0"
37 | - php: "8.2"
38 | laravel: "^7.0"
39 | - php: "8.1"
40 | laravel: "^7.0"
41 | - php: "8.4"
42 | laravel: "^6.0"
43 | - php: "8.3"
44 | laravel: "^6.0"
45 | - php: "8.2"
46 | laravel: "^6.0"
47 | - php: "8.1"
48 | laravel: "^6.0"
49 | - php: "7.2"
50 | laravel: "^11.0"
51 | - php: "7.4"
52 | laravel: "^11.0"
53 | - php: "8.0"
54 | laravel: "^11.0"
55 | - php: "8.1"
56 | laravel: "^11.0"
57 | - php: "7.2"
58 | laravel: "^12.0"
59 | - php: "7.4"
60 | laravel: "^12.0"
61 | - php: "8.0"
62 | laravel: "^12.0"
63 | - php: "8.1"
64 | laravel: "^12.0"
65 | name: "PHP${{ matrix.php }} - Laravel${{ matrix.laravel }}"
66 |
67 | runs-on: "ubuntu-latest"
68 |
69 | steps:
70 | - name: "Checkout code"
71 | uses: "actions/checkout@v3"
72 |
73 | - name: "Setup PHP"
74 | uses: "shivammathur/setup-php@v2"
75 | with:
76 | php-version: "${{ matrix.php }}"
77 | extensions: "dom, curl, libxml, mbstring, zip, fileinfo"
78 | tools: "composer:v2"
79 | coverage: "none"
80 |
81 | - name: "Check Composer configuration"
82 | run: "composer validate --strict"
83 |
84 | - name: "Install dependencies from composer.json"
85 | run: "composer update --with='laravel/framework:${{ matrix.laravel }}' --no-interaction --no-progress"
86 |
87 | - name: "Check PSR-4 mapping"
88 | run: "composer dump-autoload --optimize --strict-psr"
89 |
90 | - name: "Execute unit tests"
91 | run: "vendor/bin/phpunit"
92 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | /.idea
4 | /build
5 | .phpunit.result.cache
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-present rap2hpoutre
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 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Laravel log viewer
2 | ==================
3 |
4 | [](https://packagist.org/packages/rap2hpoutre/laravel-log-viewer)
5 | [](https://packagist.org/packages/rap2hpoutre/laravel-log-viewer)
6 | [](https://packagist.org/packages/rap2hpoutre/laravel-log-viewer)
7 | [](https://scrutinizer-ci.com/g/rap2hpoutre/laravel-log-viewer/?branch=master)
8 | [](https://twitter.com/rap2h)
9 |
10 |
11 | ## TL;DR
12 | Log Viewer for Laravel 6, 7, 8, 9, 10, 11 & 12 and Lumen. **Install with composer, create a route to `LogViewerController`**. No public assets, no vendor routes, works with and/or without log rotate. Inspired by Micheal Mand's [Laravel 4 log viewer](https://github.com/mikemand/logviewer) (works only with laravel 4.1)
13 |
14 | ## What ?
15 | Small log viewer for laravel. Looks like this:
16 |
17 | 
18 |
19 | ## Install (Laravel)
20 | Install via composer
21 | ```bash
22 | composer require rap2hpoutre/laravel-log-viewer
23 | ```
24 |
25 | Add Service Provider to `config/app.php` in `providers` section
26 | ```php
27 | Rap2hpoutre\LaravelLogViewer\LaravelLogViewerServiceProvider::class,
28 | ```
29 |
30 | Add a route in your web routes file:
31 | ```php
32 | Route::get('logs', [\Rap2hpoutre\LaravelLogViewer\LogViewerController::class, 'index']);
33 | ```
34 |
35 | Go to `http://myapp/logs` or some other route
36 |
37 | ### Install (Lumen)
38 | Install via composer
39 | ```bash
40 | composer require rap2hpoutre/laravel-log-viewer
41 | ```
42 |
43 | Add the following in `bootstrap/app.php`:
44 | ```php
45 | $app->register(\Rap2hpoutre\LaravelLogViewer\LaravelLogViewerServiceProvider::class);
46 | ```
47 |
48 | Explicitly set the namespace in `app/Http/routes.php`:
49 | ```php
50 | $router->group(['namespace' => '\Rap2hpoutre\LaravelLogViewer'], function() use ($router) {
51 | $router->get('logs', 'LogViewerController@index');
52 | });
53 | ```
54 |
55 | ## Advanced usage
56 | ### Customize view
57 | Publish `log.blade.php` into `/resources/views/vendor/laravel-log-viewer/` for view customization:
58 |
59 | ```bash
60 | php artisan vendor:publish \
61 | --provider="Rap2hpoutre\LaravelLogViewer\LaravelLogViewerServiceProvider" \
62 | --tag=views
63 | ```
64 |
65 | ### Edit configuration
66 | Publish `logviewer.php` configuration file into `/config/` for configuration customization:
67 |
68 | ```bash
69 | php artisan vendor:publish \
70 | --provider="Rap2hpoutre\LaravelLogViewer\LaravelLogViewerServiceProvider"
71 | ```
72 |
73 | ### Troubleshooting
74 | If you got a `InvalidArgumentException in FileViewFinder.php` error, it may be a problem with config caching. Double check installation, then run `php artisan config:clear`.
75 |
76 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rap2hpoutre/laravel-log-viewer",
3 | "description": "A Laravel log reader",
4 | "license": "MIT",
5 | "keywords": [
6 | "log",
7 | "log-reader",
8 | "log-viewer",
9 | "logging",
10 | "laravel",
11 | "lumen"
12 | ],
13 | "type": "laravel-package",
14 | "authors": [
15 | {
16 | "name": "rap2hpoutre",
17 | "email": "raphaelht@gmail.com"
18 | }
19 | ],
20 | "require": {
21 | "php": "^7.2|^8.0",
22 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0"
23 | },
24 | "require-dev": {
25 | "phpunit/phpunit": "^7||^8.4|^9.3.3|^10.1|^11.0",
26 | "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0"
27 | },
28 | "autoload": {
29 | "classmap": [
30 | "src/controllers"
31 | ],
32 | "psr-0": {
33 | "Rap2hpoutre\\LaravelLogViewer\\": "src/"
34 | }
35 | },
36 | "autoload-dev": {
37 | "psr-4": {
38 | "Rap2hpoutre\\LaravelLogViewer\\Tests\\": "tests/"
39 | }
40 | },
41 | "extra": {
42 | "laravel": {
43 | "providers": [
44 | "Rap2hpoutre\\LaravelLogViewer\\LaravelLogViewerServiceProvider"
45 | ]
46 | }
47 | },
48 | "minimum-stability": "stable"
49 | }
50 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests/
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewer.php:
--------------------------------------------------------------------------------
1 | level = new Level();
43 | $this->pattern = new Pattern();
44 | $this->storage_path = function_exists('config') ? config('logviewer.storage_path', storage_path('logs')) : storage_path('logs');
45 |
46 | }
47 |
48 | /**
49 | * @param string $folder
50 | */
51 | public function setFolder($folder)
52 | {
53 | if (app('files')->exists($folder)) {
54 |
55 | $this->folder = $folder;
56 | } else if (is_array($this->storage_path)) {
57 |
58 | foreach ($this->storage_path as $value) {
59 |
60 | $logsPath = $value . '/' . $folder;
61 |
62 | if (app('files')->exists($logsPath)) {
63 | $this->folder = $folder;
64 | break;
65 | }
66 | }
67 | } else {
68 |
69 | $logsPath = $this->storage_path . '/' . $folder;
70 | if (app('files')->exists($logsPath)) {
71 | $this->folder = $folder;
72 | }
73 |
74 | }
75 | }
76 |
77 | /**
78 | * @param string $file
79 | * @throws \Exception
80 | */
81 | public function setFile($file)
82 | {
83 | $file = $this->pathToLogFile($file);
84 |
85 | if (app('files')->exists($file)) {
86 | $this->file = $file;
87 | }
88 | }
89 |
90 | /**
91 | * @param string $file
92 | * @return string
93 | * @throws \Exception
94 | */
95 | public function pathToLogFile($file)
96 | {
97 |
98 | if (app('files')->exists($file)) { // try the absolute path
99 |
100 | return $file;
101 | }
102 | if (is_array($this->storage_path)) {
103 |
104 | foreach ($this->storage_path as $folder) {
105 | if (app('files')->exists($folder . '/' . $file)) { // try the absolute path
106 | $file = $folder . '/' . $file;
107 | break;
108 | }
109 | }
110 | return $file;
111 | }
112 |
113 | $logsPath = $this->storage_path;
114 | $logsPath .= ($this->folder) ? '/' . $this->folder : '';
115 | $file = $logsPath . '/' . $file;
116 | // check if requested file is really in the logs directory
117 | if (dirname($file) !== $logsPath) {
118 | throw new \Exception('No such log file: ' . $file);
119 | }
120 |
121 | return $file;
122 | }
123 |
124 | /**
125 | * @return string
126 | */
127 | public function getFolderName()
128 | {
129 | return $this->folder;
130 | }
131 |
132 | /**
133 | * @return string
134 | */
135 | public function getFileName()
136 | {
137 | return basename($this->file);
138 | }
139 |
140 | /**
141 | * @return array
142 | */
143 | public function all()
144 | {
145 | $log = array();
146 |
147 | if (!$this->file) {
148 | $log_file = (!$this->folder) ? $this->getFiles() : $this->getFolderFiles();
149 | if (!count($log_file)) {
150 | return [];
151 | }
152 | $this->file = $log_file[0];
153 | }
154 |
155 | $max_file_size = function_exists('config') ? config('logviewer.max_file_size', self::MAX_FILE_SIZE) : self::MAX_FILE_SIZE;
156 | if (app('files')->size($this->file) > $max_file_size) {
157 | return null;
158 | }
159 |
160 | if (!is_readable($this->file)) {
161 | return [[
162 | 'context' => '',
163 | 'level' => '',
164 | 'date' => null,
165 | 'text' => 'Log file "' . $this->file . '" not readable',
166 | 'stack' => '',
167 | ]];
168 | }
169 |
170 | $file = app('files')->get($this->file);
171 |
172 | preg_match_all($this->pattern->getPattern('logs'), $file, $headings);
173 |
174 | if (!is_array($headings)) {
175 | return $log;
176 | }
177 |
178 | $log_data = preg_split($this->pattern->getPattern('logs'), $file);
179 |
180 | if ($log_data[0] < 1) {
181 | array_shift($log_data);
182 | }
183 |
184 | foreach ($headings as $h) {
185 | for ($i = 0, $j = count($h); $i < $j; $i++) {
186 | foreach ($this->level->all() as $level) {
187 | if (strpos(strtolower($h[$i]), '.' . $level) || strpos(strtolower($h[$i]), $level . ':')) {
188 |
189 | preg_match($this->pattern->getPattern('current_log', 0) . $level . $this->pattern->getPattern('current_log', 1), $h[$i], $current);
190 | if (!isset($current[4])) {
191 | continue;
192 | }
193 |
194 | $log[] = array(
195 | 'context' => $current[3],
196 | 'level' => $level,
197 | 'folder' => $this->folder,
198 | 'level_class' => $this->level->cssClass($level),
199 | 'level_img' => $this->level->img($level),
200 | 'date' => $current[1],
201 | 'text' => $current[4],
202 | 'in_file' => isset($current[5]) ? $current[5] : null,
203 | 'stack' => preg_replace("/^\n*/", '', $log_data[$i])
204 | );
205 | }
206 | }
207 | }
208 | }
209 |
210 | if (empty($log)) {
211 |
212 | $lines = explode(PHP_EOL, $file);
213 | $log = [];
214 |
215 | foreach ($lines as $key => $line) {
216 | $log[] = [
217 | 'context' => '',
218 | 'level' => '',
219 | 'folder' => '',
220 | 'level_class' => '',
221 | 'level_img' => '',
222 | 'date' => $key + 1,
223 | 'text' => $line,
224 | 'in_file' => null,
225 | 'stack' => '',
226 | ];
227 | }
228 | }
229 |
230 | return array_reverse($log);
231 | }
232 |
233 | /**Creates a multidimensional array
234 | * of subdirectories and files
235 | *
236 | * @param null $path
237 | *
238 | * @return array
239 | */
240 | public function foldersAndFiles($path = null)
241 | {
242 | $contents = array();
243 | $dir = $path ? $path : $this->storage_path;
244 | foreach (scandir($dir) as $node) {
245 | if ($node == '.' || $node == '..') continue;
246 | $path = $dir . '\\' . $node;
247 | if (is_dir($path)) {
248 | $contents[$path] = $this->foldersAndFiles($path);
249 | } else {
250 | $contents[] = $path;
251 | }
252 | }
253 |
254 | return $contents;
255 | }
256 |
257 | /**Returns an array of
258 | * all subdirectories of specified directory
259 | *
260 | * @param string $folder
261 | *
262 | * @return array
263 | */
264 | public function getFolders($folder = '')
265 | {
266 | $folders = [];
267 | $listObject = new \RecursiveIteratorIterator(
268 | new \RecursiveDirectoryIterator($this->storage_path . '/' . $folder, \RecursiveDirectoryIterator::SKIP_DOTS),
269 | \RecursiveIteratorIterator::CHILD_FIRST
270 | );
271 | foreach ($listObject as $fileinfo) {
272 | if ($fileinfo->isDir()) $folders[] = $fileinfo->getRealPath();
273 | }
274 | return $folders;
275 | }
276 |
277 |
278 | /**
279 | * @param bool $basename
280 | * @return array
281 | */
282 | public function getFolderFiles($basename = false)
283 | {
284 | return $this->getFiles($basename, $this->folder);
285 | }
286 |
287 | /**
288 | * @param bool $basename
289 | * @param string $folder
290 | * @return array
291 | */
292 | public function getFiles($basename = false, $folder = '')
293 | {
294 | $files = [];
295 | $pattern = function_exists('config') ? config('logviewer.pattern', '*.log') : '*.log';
296 | $fullPath = $this->storage_path . '/' . $folder;
297 |
298 | $listObject = new \RecursiveIteratorIterator(
299 | new \RecursiveDirectoryIterator($fullPath, \RecursiveDirectoryIterator::SKIP_DOTS),
300 | \RecursiveIteratorIterator::CHILD_FIRST
301 | );
302 |
303 | foreach ($listObject as $fileinfo) {
304 | if (!$fileinfo->isDir() && strtolower(pathinfo($fileinfo->getRealPath(), PATHINFO_EXTENSION)) == explode('.', $pattern)[1])
305 | $files[] = $basename ? basename($fileinfo->getRealPath()) : $fileinfo->getRealPath();
306 | }
307 |
308 | arsort($files);
309 |
310 | return array_values($files);
311 | }
312 |
313 | /**
314 | * @return string
315 | */
316 | public function getStoragePath()
317 | {
318 | return $this->storage_path;
319 | }
320 |
321 | /**
322 | * @param $path
323 | *
324 | * @return void
325 | */
326 | public function setStoragePath($path)
327 | {
328 | $this->storage_path = $path;
329 | }
330 |
331 | public static function directoryTreeStructure($storage_path, array $array)
332 | {
333 | foreach ($array as $k => $v) {
334 | if (is_dir($k)) {
335 |
336 | $exploded = explode("\\", $k);
337 | $show = last($exploded);
338 |
339 | echo '
';
345 |
346 | if (is_array($v)) {
347 | self::directoryTreeStructure($storage_path, $v);
348 | }
349 |
350 | } else {
351 |
352 | $exploded = explode("\\", $v);
353 | $show2 = last($exploded);
354 | $folder = str_replace($storage_path, "", rtrim(str_replace($show2, "", $v), "\\"));
355 | $file = $v;
356 |
357 |
358 | echo '';
364 |
365 | }
366 | }
367 |
368 | return;
369 | }
370 |
371 |
372 | }
373 |
--------------------------------------------------------------------------------
/src/Rap2hpoutre/LaravelLogViewer/LaravelLogViewerServiceProvider.php:
--------------------------------------------------------------------------------
1 | package('rap2hpoutre/laravel-log-viewer', 'laravel-log-viewer', __DIR__.'/../../');
18 | }
19 |
20 | if (method_exists($this, 'loadViewsFrom')) {
21 | $this->loadViewsFrom(__DIR__.'/../../views', 'laravel-log-viewer');
22 | }
23 |
24 | if (method_exists($this, 'publishes')) {
25 | $this->publishes([
26 | __DIR__.'/../../views' => base_path('/resources/views/vendor/laravel-log-viewer'),
27 | ], 'views');
28 | $this->publishes([
29 | __DIR__.'/../../config/logviewer.php' => $this->config_path('logviewer.php'),
30 | ]);
31 | }
32 | }
33 |
34 | /**
35 | * Register the service provider.
36 | *
37 | * @return void
38 | */
39 | public function register()
40 | {
41 | //
42 | }
43 |
44 | /**
45 | * Get the configuration path.
46 | *
47 | * @param string $path
48 | * @return string
49 | */
50 | private function config_path($path = '')
51 | {
52 | return function_exists('config_path') ? config_path($path) : app()->basePath().DIRECTORY_SEPARATOR.'config'.($path ? DIRECTORY_SEPARATOR.$path : $path);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/Rap2hpoutre/LaravelLogViewer/Level.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private $levelsClasses = [
15 | 'debug' => 'info',
16 | 'info' => 'info',
17 | 'notice' => 'info',
18 | 'warning' => 'warning',
19 | 'error' => 'danger',
20 | 'critical' => 'danger',
21 | 'alert' => 'danger',
22 | 'emergency' => 'danger',
23 | 'processed' => 'info',
24 | 'failed' => 'warning',
25 | ];
26 |
27 | /**
28 | * @var array
29 | */
30 | private $icons = [
31 | 'debug' => 'info-circle',
32 | 'info' => 'info-circle',
33 | 'notice' => 'info-circle',
34 | 'warning' => 'exclamation-triangle',
35 | 'error' => 'exclamation-triangle',
36 | 'critical' => 'exclamation-triangle',
37 | 'alert' => 'exclamation-triangle',
38 | 'emergency' => 'exclamation-triangle',
39 | 'processed' => 'info-circle',
40 | 'failed' => 'exclamation-triangle'
41 | ];
42 |
43 | /**
44 | * @return string[]
45 | */
46 | public function all()
47 | {
48 | return array_keys($this->icons);
49 | }
50 |
51 | /**
52 | * @param string $level
53 | * @return string
54 | */
55 | public function img($level)
56 | {
57 | return $this->icons[$level];
58 | }
59 |
60 | /**
61 | * @param string $level
62 | * @return string
63 | */
64 | public function cssClass($level)
65 | {
66 | return $this->levelsClasses[$level];
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Rap2hpoutre/LaravelLogViewer/Pattern.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | private $patterns = [
11 | 'logs' => '/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}([\+-]\d{4})?\].*/',
12 | 'current_log' => [
13 | '/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}([\+-]\d{4})?)\](?:.*?(\w+)\.|.*?)',
14 | ': (.*?)( in .*?:[0-9]+)?$/i'
15 | ],
16 | 'files' => '/\{.*?\,.*?\}/i',
17 | ];
18 |
19 | /**
20 | * @return string[]
21 | */
22 | public function all()
23 | {
24 | return array_keys($this->patterns);
25 | }
26 |
27 | /**
28 | * @param string $pattern
29 | * @param null|string $position
30 | * @return string pattern
31 | */
32 | public function getPattern($pattern, $position = null)
33 | {
34 | if ($position !== null) {
35 | return $this->patterns[$pattern][$position];
36 | }
37 |
38 | return $this->patterns[$pattern];
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/config/logviewer.php:
--------------------------------------------------------------------------------
1 | 52428800, // size in Byte
13 | 'pattern' => env('LOGVIEWER_PATTERN', '*.log'),
14 | 'storage_path' => env('LOGVIEWER_STORAGE_PATH', storage_path('logs')),
15 | ];
16 |
--------------------------------------------------------------------------------
/src/controllers/LogViewerController.php:
--------------------------------------------------------------------------------
1 | log_viewer = new LaravelLogViewer();
40 | $this->request = app('request');
41 | }
42 |
43 | /**
44 | * @return array|mixed
45 | * @throws \Exception
46 | */
47 | public function index()
48 | {
49 | $folderFiles = [];
50 | if ($this->request->input('f')) {
51 | $this->log_viewer->setFolder(Crypt::decrypt($this->request->input('f')));
52 | $folderFiles = $this->log_viewer->getFolderFiles(true);
53 | }
54 | if ($this->request->input('l')) {
55 | $this->log_viewer->setFile(Crypt::decrypt($this->request->input('l')));
56 | }
57 |
58 | if ($early_return = $this->earlyReturn()) {
59 | return $early_return;
60 | }
61 |
62 | $data = [
63 | 'logs' => $this->log_viewer->all(),
64 | 'folders' => $this->log_viewer->getFolders(),
65 | 'current_folder' => $this->log_viewer->getFolderName(),
66 | 'folder_files' => $folderFiles,
67 | 'files' => $this->log_viewer->getFiles(true),
68 | 'current_file' => $this->log_viewer->getFileName(),
69 | 'standardFormat' => true,
70 | 'structure' => $this->log_viewer->foldersAndFiles(),
71 | 'storage_path' => $this->log_viewer->getStoragePath(),
72 |
73 | ];
74 |
75 | if ($this->request->wantsJson()) {
76 | return $data;
77 | }
78 |
79 | if (is_array($data['logs']) && count($data['logs']) > 0) {
80 | $firstLog = reset($data['logs']);
81 | if ($firstLog) {
82 | if (!$firstLog['context'] && !$firstLog['level']) {
83 | $data['standardFormat'] = false;
84 | }
85 | }
86 | }
87 |
88 | return app('view')->make($this->view_log, $data);
89 | }
90 |
91 | /**
92 | * @return bool|mixed
93 | * @throws \Exception
94 | */
95 | private function earlyReturn()
96 | {
97 | if ($this->request->input('f')) {
98 | $this->log_viewer->setFolder(Crypt::decrypt($this->request->input('f')));
99 | }
100 |
101 | if ($this->request->input('dl')) {
102 | return $this->download($this->pathFromInput('dl'));
103 | } elseif ($this->request->has('clean')) {
104 | app('files')->put($this->pathFromInput('clean'), '');
105 | return $this->redirect(url()->previous());
106 | } elseif ($this->request->has('del')) {
107 | app('files')->delete($this->pathFromInput('del'));
108 | return $this->redirect($this->request->url());
109 | } elseif ($this->request->has('delall')) {
110 | $files = ($this->log_viewer->getFolderName())
111 | ? $this->log_viewer->getFolderFiles(true)
112 | : $this->log_viewer->getFiles(true);
113 | foreach ($files as $file) {
114 | app('files')->delete($this->log_viewer->pathToLogFile($file));
115 | }
116 | return $this->redirect($this->request->url());
117 | }
118 | return false;
119 | }
120 |
121 | /**
122 | * @param string $input_string
123 | * @return string
124 | * @throws \Exception
125 | */
126 | private function pathFromInput($input_string)
127 | {
128 | return $this->log_viewer->pathToLogFile(Crypt::decrypt($this->request->input($input_string)));
129 | }
130 |
131 | /**
132 | * @param $to
133 | * @return mixed
134 | */
135 | private function redirect($to)
136 | {
137 | if (function_exists('redirect')) {
138 | return redirect($to);
139 | }
140 |
141 | return app('redirect')->to($to);
142 | }
143 |
144 | /**
145 | * @param string $data
146 | * @return mixed
147 | */
148 | private function download($data)
149 | {
150 | if (function_exists('response')) {
151 | return response()->download($data);
152 | }
153 |
154 | // For laravel 4.2
155 | return app('\Illuminate\Support\Facades\Response')->download($data);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/views/log.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Laravel log viewer
8 |
12 |
13 |
150 |
151 |
171 |
172 |
173 |
174 |
175 |
201 |
202 | @if ($logs === null)
203 |
204 | Log file >50M, please download it.
205 |
206 | @else
207 |
208 |
209 |
210 | @if ($standardFormat)
211 | Level |
212 | Context |
213 | Date |
214 | @else
215 | Line number |
216 | @endif
217 | Content |
218 |
219 |
220 |
221 |
222 | @foreach($logs as $key => $log)
223 |
224 | @if ($standardFormat)
225 |
226 | {{$log['level']}}
227 | |
228 | {{$log['context']}} |
229 | @endif
230 | {{{$log['date']}}} |
231 |
232 | @if ($log['stack'])
233 |
238 | @endif
239 | {{{$log['text']}}}
240 | @if (isset($log['in_file']))
241 | {{{$log['in_file']}}}
242 | @endif
243 | @if ($log['stack'])
244 | {{{ trim($log['stack']) }}}
246 |
247 | @endif
248 | |
249 |
250 | @endforeach
251 |
252 |
253 |
254 | @endif
255 |
276 |
277 |
278 |
279 |
280 |
283 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
333 |
334 |
335 |
--------------------------------------------------------------------------------
/tests/LaravelLogViewerTest.php:
--------------------------------------------------------------------------------
1 | set('app.key', 'XP0aw2Dkrk22p0JoAOzulOl8XkUxlvkO');
20 | // Copy "laravel.log" file to the orchestra package.
21 | if (!file_exists(storage_path('logs/laravel.log'))) {
22 | copy(__DIR__ . '/laravel.log', storage_path('logs/laravel.log'));
23 | }
24 | }
25 |
26 | /**
27 | * @throws \Exception
28 | */
29 | public function testSetFile()
30 | {
31 |
32 | $laravel_log_viewer = new LaravelLogViewer();
33 | $laravel_log_viewer->setFile("laravel.log");
34 |
35 | $this->assertEquals("laravel.log", $laravel_log_viewer->getFileName());
36 | }
37 |
38 |
39 | public function testSetFolderWithCorrectPath()
40 | {
41 |
42 | $laravel_log_viewer = new LaravelLogViewer();
43 | $laravel_log_viewer->setFolder(basename((__DIR__)));
44 | $this->assertEquals("tests", $laravel_log_viewer->getFolderName());
45 | }
46 |
47 |
48 | public function testSetFolderWithArrayStoragePath()
49 | {
50 | $path = __DIR__;
51 |
52 | $laravel_log_viewer = new LaravelLogViewer();
53 | $laravel_log_viewer->setStoragePath([$path]);
54 | if(!File::exists("$path/samuel")) File::makeDirectory("$path/samuel");
55 | $laravel_log_viewer->setFolder('samuel');
56 |
57 | $this->assertEquals("samuel", $laravel_log_viewer->getFolderName());
58 |
59 | }
60 |
61 | public function testSetFolderWithDefaultStoragePath()
62 | {
63 |
64 | $laravel_log_viewer = new LaravelLogViewer();
65 | $laravel_log_viewer->setStoragePath(storage_path());
66 | $laravel_log_viewer->setFolder('logs');
67 |
68 |
69 | $this->assertEquals("logs", $laravel_log_viewer->getFolderName());
70 |
71 | }
72 |
73 | public function testSetStoragePath()
74 | {
75 |
76 | $laravel_log_viewer = new LaravelLogViewer();
77 | $laravel_log_viewer->setStoragePath(basename(__DIR__));
78 |
79 | $this->assertEquals("tests", $laravel_log_viewer->getStoragePath());
80 | }
81 |
82 | public function testPathToLogFile()
83 | {
84 |
85 | $laravel_log_viewer = new LaravelLogViewer();
86 | $pathToLogFile = $laravel_log_viewer->pathToLogFile(storage_path(('logs/laravel.log')));
87 |
88 | $this->assertEquals($pathToLogFile, storage_path('logs/laravel.log'));
89 | }
90 |
91 | public function testPathToLogFileWithArrayStoragePath()
92 | {
93 |
94 | $laravel_log_viewer = new LaravelLogViewer();
95 | $laravel_log_viewer->setStoragePath([storage_path()]);
96 | $pathToLogFile = $laravel_log_viewer->pathToLogFile('laravel.log');
97 |
98 | $this->assertEquals($pathToLogFile, 'laravel.log');
99 | }
100 |
101 | public function testFailOnBadPathToLogFile()
102 | {
103 |
104 | $this->expectException(\Exception::class);
105 |
106 | $laravel_log_viewer = new LaravelLogViewer();
107 | $laravel_log_viewer->setStoragePath(storage_path());
108 | $laravel_log_viewer->setFolder('logs');
109 | $laravel_log_viewer->pathToLogFile('newlogs/nolaravel.txt');
110 | }
111 |
112 | public function testAll()
113 | {
114 | $laravel_log_viewer = new LaravelLogViewer();
115 | $laravel_log_viewer->setStoragePath(__DIR__);
116 | $laravel_log_viewer->pathToLogFile(storage_path('logs/laravel.log'));
117 | $data = $laravel_log_viewer->all();
118 | $this->assertEquals('local', $data[0]['context']);
119 | $this->assertEquals('error', $data[0]['level']);
120 | $this->assertEquals('danger', $data[0]['level_class']);
121 | $this->assertEquals('exclamation-triangle', $data[0]['level_img']);
122 | $this->assertEquals('2018-09-05 20:20:51', $data[0]['date']);
123 | }
124 |
125 | public function testAllWithEmptyFileName()
126 | {
127 | $laravel_log_viewer = new LaravelLogViewer();
128 | $laravel_log_viewer->setStoragePath(__DIR__);
129 |
130 | $data = $laravel_log_viewer->all();
131 | $this->assertEquals('local', $data[0]['context']);
132 | $this->assertEquals('error', $data[0]['level']);
133 | $this->assertEquals('danger', $data[0]['level_class']);
134 | $this->assertEquals('exclamation-triangle', $data[0]['level_img']);
135 | $this->assertEquals('2018-09-05 20:20:51', $data[0]['date']);
136 | }
137 |
138 | public function testFolderFiles()
139 | {
140 | $laravel_log_viewer = new LaravelLogViewer();
141 | $laravel_log_viewer->setStoragePath(__DIR__);
142 | $data = $laravel_log_viewer->foldersAndFiles();
143 | $this->assertIsArray($data);
144 |
145 | $this->assertIsArray($data);
146 | $this->assertNotEmpty($data);
147 |
148 | $this->assertStringContainsString('tests', $data[count(explode($data[0], '/')) - 1]);
149 | }
150 |
151 | public function testGetFolderFiles()
152 | {
153 | $laravel_log_viewer = new LaravelLogViewer();
154 | $laravel_log_viewer->setStoragePath(__DIR__);
155 | $data = $laravel_log_viewer->getFolderFiles();
156 |
157 | $this->assertIsArray($data);
158 | $this->assertNotEmpty($data, "Folder files is null");
159 | }
160 |
161 | public function testGetFiles()
162 | {
163 | $laravel_log_viewer = new LaravelLogViewer();
164 | $laravel_log_viewer->setStoragePath(storage_path());
165 | $data = $laravel_log_viewer->getFiles();
166 |
167 | $this->assertIsArray($data);
168 | $this->assertNotEmpty($data, "Folder files is null");
169 | }
170 |
171 | public function testGetFolders()
172 | {
173 | $laravel_log_viewer = new LaravelLogViewer();
174 | $laravel_log_viewer->setStoragePath(storage_path());
175 | $data = $laravel_log_viewer->getFolders();
176 |
177 | $this->assertIsArray($data);
178 | $this->assertNotEmpty($data, "files is null");
179 | }
180 |
181 | public function testDirectoryStructure()
182 | {
183 | $log_viewer = new LaravelLogViewer();
184 | ob_start();
185 | $log_viewer->directoryTreeStructure(storage_path('logs'), $log_viewer->foldersAndFiles());
186 | $data = ob_get_clean();
187 |
188 | $this->assertIsString($data);
189 | $this->assertNotEmpty($data);
190 | }
191 |
192 |
193 | }
194 |
--------------------------------------------------------------------------------
/tests/laravel.log:
--------------------------------------------------------------------------------
1 | [2018-09-05 20:20:51] local.ERROR: The "--versio" option does not exist. {"exception":"[object] (Symfony\\Component\\Console\\Exception\\RuntimeException(code: 0): The \"--versio\" option does not exist. at /x/y/z/vendor/symfony/console/Input/ArgvInput.php:217)
2 | [stacktrace]
3 | #0 /x/y/z/vendor/symfony/console/Input/ArgvInput.php(153): Symfony\\Component\\Console\\Input\\ArgvInput->addLongOption('versio', NULL)
4 | #1 /x/y/z/vendor/symfony/console/Input/ArgvInput.php(82): Symfony\\Component\\Console\\Input\\ArgvInput->parseLongOption('--versio')
5 | #2 /x/y/z/vendor/symfony/console/Input/Input.php(55): Symfony\\Component\\Console\\Input\\ArgvInput->parse()
6 | #3 /x/y/z/vendor/symfony/console/Command/Command.php(210): Symfony\\Component\\Console\\Input\\Input->bind(Object(Symfony\\Component\\Console\\Input\\InputDefinition))
7 | #4 /x/y/z/vendor/symfony/console/Application.php(886): Symfony\\Component\\Console\\Command\\Command->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
8 | #5 /x/y/z/vendor/symfony/console/Application.php(262): Symfony\\Component\\Console\\Application->doRunCommand(Object(Symfony\\Component\\Console\\Command\\ListCommand), Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
9 | #6 /x/y/z/vendor/symfony/console/Application.php(145): Symfony\\Component\\Console\\Application->doRun(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
10 | #7 /x/y/z/vendor/laravel/framework/src/Illuminate/Console/Application.php(89): Symfony\\Component\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
11 | #8 /x/y/z/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(122): Illuminate\\Console\\Application->run(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
12 | #9 /x/y/z/artisan(37): Illuminate\\Foundation\\Console\\Kernel->handle(Object(Symfony\\Component\\Console\\Input\\ArgvInput), Object(Symfony\\Component\\Console\\Output\\ConsoleOutput))
13 | #10 {main}
14 | "}
15 |
--------------------------------------------------------------------------------