├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin ├── .gitkeep └── epilog ├── bootstrap.php ├── box.json ├── commands.php ├── composer.json ├── epilog ├── phpunit.xml.dist ├── src ├── DataBag.php ├── Epilog.php ├── FakeLogTail.php ├── FlowException.php ├── InputReader.php ├── Interfaces │ ├── InputReaderInterface.php │ ├── LineParserInterface.php │ ├── LinePrinterInterface.php │ ├── TailInterface.php │ └── ThemeInterface.php ├── LogFinder │ ├── Generic.php │ └── Laravel.php ├── LogFinderFactory.php ├── LogTail.php ├── MonologLineParser.php ├── MonologLinePrinter.php ├── Theme.php └── Ticker.php ├── test ├── DataBagTest.php ├── EpilogTest.php ├── InputReaderTest.php ├── LogFinderTest.php ├── LogTailTest.php ├── MonologLineParserTest.php ├── TickerTest.php └── fixtures │ ├── laravel_a │ └── app │ │ └── storage │ │ └── logs │ │ └── log.log │ └── laravel_b │ └── app │ └── storage │ └── logs │ └── log.txt └── themes ├── chaplin.yml ├── default.yml ├── forest.yml ├── punchcard.yml ├── scrapbook.yml ├── sunrise.yml ├── sunset.yml └── traffic.yml /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | build/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - hhvm 9 | 10 | matrix: 11 | allow_failures: 12 | - php: hhvm 13 | - php: 7.0 14 | 15 | before_script: 16 | - composer self-update 17 | - composer require satooshi/php-coveralls:dev-master --no-update --dev 18 | - composer install --dev --prefer-source 19 | 20 | after_script: 21 | - php vendor/bin/coveralls 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Márcio Almada 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COMPILE = box build 2 | TARGET = bin/epilog.phar 3 | OUTPUT = bin/epilog 4 | DELETE_FILE = rm 5 | CHMOD_FILE = chmod +x 6 | MOVE_FILE = mv 7 | CP_FILE = cp 8 | INSTALL_PATH = /usr/local/bin 9 | 10 | default: 11 | $(COMPILE) 12 | $(MOVE_FILE) $(TARGET) $(OUTPUT) 13 | $(CHMOD_FILE) $(OUTPUT) 14 | 15 | run: 16 | ./$(OUTPUT) --help 17 | 18 | pretend: 19 | ./$(OUTPUT) pretend 20 | 21 | clean: 22 | $(DELETE_FILE) $(OUTPUT) 23 | 24 | install: 25 | $(CP_FILE) $(OUTPUT) $(INSTALL_PATH) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Epilog 3 |

4 | 5 | [![Build Status][t-badge]][t-link] 6 | [![Coverage Status][c-badge]][c-link] 7 | [![Scrutinizer Quality Score][s-badge]][s-link] 8 | [![Latest Stable Version][v-badge]][p-link] 9 | [![Total Downloads][d-badge]][p-link] 10 | [![License][l-badge]][p-link] 11 | [![SensioLabsInsight][sl-badge]][sl-link] 12 | 13 | The lightweight, themeable and interactive PSR-3 log viewer. Monitor monologs with style: 14 | 15 | [![asciinema](https://dl.dropboxusercontent.com/u/49549530/epilog/asciinema.png?v1.0)](https://asciinema.org/a/12309?autoplay=true&speed=2) 16 | 17 | ## Install 18 | 19 | As a composer project dependency: 20 | 21 | ```json 22 | { 23 | "require": { 24 | "marc/epilog": "~1.0" 25 | } 26 | } 27 | ``` 28 | 29 | As a packed phar: 30 | 31 | ```bash 32 | wget https://github.com/marcioAlmada/epilog/raw/master/bin/epilog && chmod +x epilog 33 | mv epilog /usr/local/bin/ # or somewhere in your $PATH 34 | ``` 35 | 36 | As a composer global package: 37 | 38 | ```bash 39 | composer global require marc/epilog:~1.0 40 | ``` 41 | 42 | Or build the phar yourself: 43 | 44 | ```bash 45 | # install box2 -> https://github.com/box-project/box2 46 | git clone https://github.com/marcioAlmada/epilog.git 47 | cd epilog 48 | composer install 49 | make # phar will available at bin/ folder 50 | sudo make install # will put epilog at /usr/local/bin/ folder 51 | ``` 52 | 53 | ### Quick test 54 | 55 | Run **epilog** `pretend` command and tail a fake log stream. Check if output looks good: 56 | 57 | ```bash 58 | epilog pretend 59 | ``` 60 | 61 | ## Usage 62 | 63 | Basic usage is: 64 | 65 | ```bash 66 | epilog watch /path/to/monolog/file.log []... 67 | ``` 68 | 69 | While epilog is monitoring the log file (or a fake stream), hit `[return]` to see a nice interactive menu: 70 | 71 | ``` 72 | $ [ ⏎ ] 73 | 74 | Woot! Epilog here. Please type a theme number, a valid regexp or a valid flag: 75 | 76 | [#] load another theme: 77 | 78 | 1:chaplin 2:forest 79 | 3:scrapbook 4:punchcard 80 | 5:sunset 6:sunrise 81 | 7:traffic 8:usa 82 | 83 | [ r ] load random theme from list above. 84 | [ i ] toggle invert theme. 85 | [ c ] clear screen. 86 | [ - ] reset regexp filter. 87 | [ q ] quit. 88 | ``` 89 | 90 | ## Framework Integration 91 | 92 | Epilog can use [log finders](https://github.com/marcioAlmada/epilog/tree/master/src/LogFinder) to look for 93 | logs inside app directories according to framework conventions so you don't need to aim at your latest 94 | log files mannualy. Ex: 95 | 96 | ```bash 97 | $ epilog watch ~/www/my-project --app laravel 98 | # instead of long and boring 99 | $ epilog watch ~/www/my-project/app/storage/logs/log-cli-server-2015-01-11.txt 100 | ``` 101 | 102 | > PRs implementing log finders to support your framework of choice are highly encouraged :octocat: 103 | 104 | ## Themes 105 | 106 | Epilog themes are very simple `yml` files with hooks where you can put ANSI color escape sequences and literal text. 107 | The hooks will decorate each log line, [nyancatyze](http://youtu.be/QH2-TGUlwu4) your terminal 108 | and sometimes [puke rainbows](http://youtu.be/-Cec2iAgW1Q). Here is a theme example: 109 | 110 | ```yaml 111 | name: Punched Card 112 | author: Márcio Almada 113 | theme: 114 | extends: default 115 | # the log line template 116 | # template tags are: {date}, {level}, {logger}, {message}, {context}, {extra} 117 | template: "{level} {date} {message} [{logger}] [{context}] [{extra}]" 118 | # literal string that will be prepended to entire line 119 | prepend: "" 120 | # literal string that will be appended to entire line 121 | append: "\e[0m\n" 122 | # level section 123 | level: 124 | padding: 10 125 | DEBUG: 126 | prepend: " • \e[2m" 127 | INFO: 128 | prepend: " • \e[2m" 129 | NOTICE: 130 | prepend: " • \e[2m" 131 | WARNING: 132 | prepend: " • \e[2m" 133 | ERROR: 134 | prepend: " • \e[1m" 135 | CRITICAL: 136 | prepend: " • \e[1m" 137 | ALERT: 138 | prepend: " • \e[1m" 139 | EMERGENCY: 140 | prepend: " • \e[1m" 141 | DEFAULT: 142 | prepend: " -------- \e[1m" 143 | ``` 144 | 145 | Which will make log lines look like the following, when interpreted: 146 | 147 | ![punchcard theme](https://dl.dropboxusercontent.com/u/49549530/epilog/punchcard.png) 148 | 149 | ## Roadmap 150 | 151 | - [x] Basic functionalities 152 | - [x] Add `--app` option to allow easy framework integration. Ex: `epilog watch /my/project --app laravel` 153 | - [ ] Add `listen` command to aggregate log entries through a REST API. Ex: `epilog listen --port 8888` 154 | - [ ] Add `server` command to view logs in a browser instead of terminal `epilog server --port 8888` 155 | - [ ] Add better unicode support, more themes etc 156 | - [ ] Bother with windows ... anyone? 157 | - [ ] Other cool things, probably 158 | - [ ] Release stable version 159 | 160 | ## Contributions 161 | 162 | Found a bug? Have an improvement? Take a look at the [issues](https://github.com/marcioAlmada/epilog/issues). 163 | 164 | ### Guide 165 | 166 | 0. Fork [marc/epilog](https://github.com/marcioAlmada/epilog/fork) 167 | 0. Clone forked repository 168 | 0. Install composer dependencies `$ composer install` 169 | 0. Run unit tests `$ phpunit` 170 | 0. Modify code: correct bug, implement features 171 | 0. Back to step 4 172 | 173 | ## Copyright 174 | 175 | Copyright (c) 2014-2015 Márcio Almada. Distributed under the terms of an MIT-style license. 176 | See LICENSE for details. 177 | 178 | [t-link]: https://travis-ci.org/marcioAlmada/epilog "Travis Build" 179 | [c-link]: https://coveralls.io/r/marcioAlmada/epilog?branch=master "Code Coverage" 180 | [s-link]: https://scrutinizer-ci.com/g/marcioAlmada/epilog/?branch=master "Code Quality" 181 | [p-link]: https://packagist.org/packages/marc/epilog "Packagist" 182 | [sl-link]: https://insight.sensiolabs.com/projects/02d1fd01-8a70-4fe4-a550-381a3c0e33f3 "Sensiolabs Insight" 183 | 184 | [t-badge]: https://travis-ci.org/marcioAlmada/epilog.png?branch=master 185 | [c-badge]: https://coveralls.io/repos/marcioAlmada/epilog/badge.png?branch=master 186 | [s-badge]: https://scrutinizer-ci.com/g/marcioAlmada/epilog/badges/quality-score.png?b=master 187 | [v-badge]: https://poser.pugx.org/marc/epilog/v/stable.png 188 | [d-badge]: https://poser.pugx.org/marc/epilog/downloads.png 189 | [l-badge]: https://poser.pugx.org/marc/epilog/license.png 190 | [sl-badge]: https://insight.sensiolabs.com/projects/02d1fd01-8a70-4fe4-a550-381a3c0e33f3/mini.png 191 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcioAlmada/epilog/e5d8d718c4f929a2a723383261f3687f179d480d/bin/.gitkeep -------------------------------------------------------------------------------- /bin/epilog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcioAlmada/epilog/e5d8d718c4f929a2a723383261f3687f179d480d/bin/epilog -------------------------------------------------------------------------------- /bootstrap.php: -------------------------------------------------------------------------------- 1 | [...] 22 | * 23 | * Commands are: 24 | * epilog watch Monitor a log files 25 | * epilog pretend Display a fake log stream, for testing only 26 | * 27 | * See 'epilog -h' to read about a specific subcommand. 28 | * 29 | * Options: 30 | * -h --help Show this screen. 31 | * -v --version Show version. 32 | */ 33 | function epilog() 34 | { 35 | $specReader = AnnotationsReader::createFromDefaults(); 36 | $handler = new Handler([ 37 | 'version'=> 'Epilog ' . Epilog::VERSION . ' by Márcio Almada', 'optionsFirst' => true ]); 38 | 39 | $response = $handler->handle( 40 | $specReader->getFunctionAnnotations(__FUNCTION__)->get('command.spec')); 41 | 42 | $command = __NAMESPACE__ . '\\' . $response['']; 43 | 44 | if(! function_exists($command)) 45 | throw new FlowException("Command `{$response['']}` not defined, try epilog --help", 1); 46 | 47 | $handler->optionsFirst = false; 48 | $response = $handler->handle( 49 | $specReader->getFunctionAnnotations($command)->get('command.spec')); 50 | 51 | $command($response); 52 | } 53 | 54 | /** 55 | * @command.spec 56 | * 57 | * Usage: 58 | * epilog pretend [--filter=][--sleep-interval=][--theme=][--theme-invert][--no-follow][--debug] 59 | * 60 | * Options: 61 | * -f --filter= Filter log entries with a given regular expression. 62 | * -s --sleep-interval= Sleep interval [default: .5]. 63 | * -t --theme= Theme, see `Epilogs themes` to see list the list of themes [default: sunrise]. 64 | * -i --theme-invert Invert theme foreground vs background colors. 65 | * -n --no-follow Print last logged lines and quit. 66 | * -d --debug Reloads theme on every loop. Slow, but useful while building new themes. 67 | * -h --help Show this screen. 68 | */ 69 | function pretend(Response $response) 70 | { 71 | (new Epilog($response))->run(new FakeLogTail); 72 | } 73 | 74 | /** 75 | * @command.spec 76 | * 77 | * Usage: 78 | * epilog watch [--filter=][--sleep-interval=][--theme=][--theme-invert][--no-follow][--app=][--debug] 79 | * 80 | * Options: 81 | * -f --filter= Filter log entries with a given regular expression. 82 | * -s --sleep-interval= Sleep interval [default: .5]. 83 | * -t --theme= Theme, see `Epilogs themes` to see list the list of themes [default: sunrise]. 84 | * -i --theme-invert Invert theme foreground vs background colors. 85 | * -n --no-follow Print last logged lines and quit. 86 | * -a --app= Finds latest project log automatically by framework (ex: --app laravel) [default: generic]. 87 | * -d --debug Reloads theme on every loop. Slow, but useful while building new themes. 88 | * -h --help Show this screen. 89 | */ 90 | function watch(Response $response) 91 | { 92 | $epilog = new Epilog($response); 93 | $logFinder = LogFinderFactory::getLogFinder($response['--app']); 94 | $log = new LogTail($logFinder->find($response[''])); 95 | $epilog->run($log); 96 | } 97 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marc/epilog", 3 | "description": "Epilog is a CLI log viewer with style", 4 | "keywords": ["log", "viewer"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Márcio Almada", 9 | "email": "marcio3w@gmail.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Epilog\\": "src/" 15 | }, 16 | "files": [ 17 | "commands.php" 18 | ] 19 | }, 20 | "bin": [ 21 | "epilog" 22 | ], 23 | "require": { 24 | "docopt/docopt": "~1.0", 25 | "minime/annotations": "~2.3", 26 | "igorw/get-in": "~1.0", 27 | "symfony/yaml": "2.5.3", 28 | "regex-guard/regex-guard": "~1.0" 29 | }, 30 | "require-dev": { 31 | "mockery/mockery": "dev-master", 32 | "mikey179/vfsStream": "~1.2.0" 33 | }, 34 | "minimum-stability": "dev" 35 | } 36 | -------------------------------------------------------------------------------- /epilog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getMessage(), "\n"; 11 | return $e->getCode(); 12 | } 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | 16 | test/ 17 | 18 | 19 | 20 | 21 | 22 | src/ 23 | 24 | 25 | 26 | 27 | 31 | 35 | 42 | 46 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/DataBag.php: -------------------------------------------------------------------------------- 1 | data = $data; 12 | } 13 | 14 | public function __get($key) 15 | { 16 | return $this->get($key); 17 | } 18 | 19 | public function __set($key, $value) 20 | { 21 | return $this->set($key, $value); 22 | } 23 | 24 | public function get($key, $default = null) 25 | { 26 | return \igorw\get_in($this->data, $this->makePath($key), $default); 27 | } 28 | 29 | public function set($key, $value) 30 | { 31 | return $this->data = \igorw\assoc_in($this->data, $this->makePath($key), $value); 32 | } 33 | 34 | public function all() 35 | { 36 | return $this->data; 37 | } 38 | 39 | public function keys() 40 | { 41 | return array_keys($this->data); 42 | } 43 | 44 | public function values() 45 | { 46 | return array_values($this->data); 47 | } 48 | 49 | protected function makePath($key) 50 | { 51 | return explode('.', str_replace(' ', '.', $key)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Epilog.php: -------------------------------------------------------------------------------- 1 | 'default', 21 | 2 => 'chaplin', 22 | 3 => 'forest', 23 | 4 => 'scrapbook', 24 | 5 => 'punchcard', 25 | 6 => 'sunset', 26 | 7 => 'sunrise', 27 | 8 => 'traffic', 28 | ]; 29 | 30 | /** 31 | * table of callable subcommands 32 | * 33 | * @var array 34 | */ 35 | protected $commands = []; 36 | 37 | /** 38 | * Epilog command line args container 39 | * 40 | * @var \Docopt\Response 41 | */ 42 | protected $args; 43 | 44 | /** 45 | * Input reader 46 | * 47 | * @var \Epilog\Interfaces\InputReaderInterface 48 | */ 49 | protected $stdin; 50 | 51 | /** 52 | * Line printer 53 | * 54 | * @var \Epilog\Interfaces\LinePrinterInterface 55 | */ 56 | protected $printer; 57 | 58 | /** 59 | * @var \Epilog\Ticker 60 | */ 61 | protected $ticker; 62 | 63 | /** 64 | * Regex validator 65 | * 66 | * @var \RegexGuard\RegexGuard 67 | */ 68 | protected $regexGuard; 69 | 70 | public function __construct(Response $args) 71 | { 72 | $this->args = $args; 73 | $args['--sleep-interval'] = (float) $args['--sleep-interval']; 74 | $this->ticker = new Ticker; 75 | $this->printer = $this->loadPrinter(); 76 | $this->regexGuard = RegexGuard::getGuard(); 77 | $this->commands[''] = function () {}; 78 | $this->commands['q'] = $this->commands[false] = [$this, 'quit']; 79 | $this->commands['r'] = [$this, 'loadRandomTheme']; 80 | $this->commands['c'] = [$this, 'clear']; 81 | $this->commands['i'] = [$this, 'invertTheme']; 82 | $this->commands['-'] = [$this, 'clearFilter']; 83 | $this->commands['default'] = function ($command) { 84 | if ($this->regexGuard->isRegexValid($command)) { 85 | $this->args['--filter'] = $command; 86 | } elseif (isset(self::$themes[$command])) { 87 | $this->args['--theme'] = self::$themes[$command]; 88 | $this->printer = $this->loadPrinter(); 89 | } else { 90 | $this->output(" Invalid option \"{$command}\" given.\n"); 91 | } 92 | }; 93 | } 94 | 95 | public function run(TailInterface $log, InputReaderInterface $stdin = null) 96 | { 97 | $this->stdin = $stdin ?: new InputReader; 98 | $this->stdin->block(false); 99 | while (true) { 100 | $log->seekLastLineRead(); 101 | while (! $log->eof()) { 102 | $line = $log->fgets(); 103 | $filter = $this->args['--filter']; 104 | if (! empty($filter)) 105 | if(! $this->regexGuard->match($filter, $line)) continue; 106 | $this->output($this->printer->format($line)); 107 | } 108 | if($this->args['--no-follow']) $this->quit(); 109 | $this->output($this->status($log->getRealPath())); 110 | $this->sleep(); // wait before trigger new iteration 111 | if($this->args['--debug']) $this->printer = $this->loadPrinter(); 112 | $this->handleInteraction(); 113 | } 114 | } 115 | 116 | public function args() 117 | { 118 | return $this->args; 119 | } 120 | 121 | protected function handleInteraction() 122 | { 123 | if (false !== $this->stdin->readChar()) { 124 | $this->output( 125 | "\n Woot! Epilog here. Please type a theme number," 126 | . " a valid regexp to filter messages or a valid flag: \n" 127 | . "\n [#] load another theme:\n" 128 | ); 129 | foreach (self::$themes as $key => $theme) { 130 | $this->output((! ($key & 1) ? " \t" : "\n ") . "{$key}:$theme"); 131 | } 132 | $this->output( 133 | "\n\n [ r ] load random theme from list above." 134 | . "\n [ i ] toggle invert theme." 135 | . "\n [ c ] clear screen." 136 | . "\n [ - ] reset regexp filter." 137 | . "\n [ q ] quit." 138 | . "\n\n [ ⏎ ] " 139 | ); 140 | 141 | $command = $input = $this->stdin->block()->readLine(); 142 | if(! isset($this->commands[$command])) $command = 'default'; 143 | call_user_func_array($this->commands[$command], [$input]); 144 | $this->output($this->printer->unformat()); 145 | $this->stdin->block(false); 146 | } 147 | } 148 | 149 | protected function output($string) 150 | { 151 | if (! isset($this->args['--silent'])) echo $string; 152 | } 153 | 154 | /** 155 | * Renders ANSI escaped status line 156 | * 157 | * @param string $message status line message 158 | * @return string ANSI escaped status line 159 | */ 160 | protected function status($message) 161 | { 162 | $clear = $this->printer->clearLinesUp(1); 163 | $statusLine = "{$clear}\n[{$this->ticker}] {$message} [ ⏎ ]"; 164 | $screenWidth = $this->printer->getScreenWidth() + 5; 165 | 166 | return $this->printer->pad($statusLine, $screenWidth) . $this->printer->unformat(); 167 | } 168 | 169 | protected function quit() 170 | { 171 | throw new FlowException('Bye!', 0); 172 | } 173 | 174 | protected function sleep() 175 | { 176 | usleep($this->args['--sleep-interval'] * 1000000); 177 | } 178 | 179 | protected function loadPrinter() 180 | { 181 | $theme = __DIR__ . '/../themes/'. $this->args['--theme'] . '.yml'; 182 | 183 | return new MonologLinePrinter( 184 | new MonologLineParser, new Theme($theme), $this->args['--theme-invert']); 185 | } 186 | 187 | protected function loadRandomTheme() 188 | { 189 | $this->args['--theme'] = self::$themes[array_rand(self::$themes)]; 190 | $this->printer = $this->loadPrinter(); 191 | } 192 | 193 | protected function clear() 194 | { 195 | $this->output($this->printer->clearAll()); 196 | } 197 | 198 | protected function invertTheme() 199 | { 200 | $this->args['--theme-invert'] = $this->printer->invert(); 201 | } 202 | 203 | protected function clearFilter() 204 | { 205 | $this->args['--filter'] = null; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/FakeLogTail.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i:s'); 48 | $message = self::sentence(rand(5, 10)); 49 | 50 | return "[{$date}] log.{$level}: {$message} [] []"; 51 | } 52 | 53 | public function eof() 54 | { 55 | return $this->eof = ! $this->eof; 56 | } 57 | 58 | public function getRealPath() 59 | { 60 | return 'nsa://'; 61 | } 62 | 63 | /** 64 | * Generate an array of random words 65 | * 66 | * @example array('Lorem', 'ipsum', 'dolor') 67 | * @param integer $nb how many words to return 68 | * @param bool $asText if true the sentences are returned as one string 69 | * @return array|string 70 | */ 71 | protected static function words($nb = 3, $asText = false) 72 | { 73 | $words = []; 74 | for ($i=0; $i < $nb; $i++) { 75 | $words []= static::randomElement(self::$wordList); 76 | } 77 | 78 | return $asText ? join(' ', $words) : $words; 79 | } 80 | 81 | /** 82 | * Generate a random sentence 83 | * 84 | * @example 'Lorem ipsum dolor sit amet.' 85 | * @param integer $nbWords around how many words the sentence should contain 86 | * @param boolean $variableNbWords set to false if you want exactly $nbWords returned, 87 | * otherwise $nbWords may vary by +/-40% with a minimum of 1 88 | * @return string 89 | */ 90 | protected static function sentence($nbWords = 6, $variableNbWords = true) 91 | { 92 | if ($variableNbWords) { 93 | $nbWords = self::randomizeNbElements($nbWords); 94 | } 95 | 96 | $words = static::words($nbWords); 97 | $words[0] = ucwords($words[0]); 98 | 99 | return join($words, ' ') . '.'; 100 | } 101 | 102 | /** 103 | * @param integer $nbElements 104 | */ 105 | protected static function randomizeNbElements($nbElements) 106 | { 107 | return (int) ($nbElements * mt_rand(60, 140) / 100) + 1; 108 | } 109 | 110 | /** 111 | * Returns random elements from a provided array 112 | * 113 | * @param array $array Array to take elements from. Defaults to a-f 114 | * @param integer $count Number of elements to take. 115 | * 116 | * @return array New array with $count elements from $array 117 | */ 118 | protected static function randomElements(array $array = ['a', 'b', 'c'], $count = 1) 119 | { 120 | $allKeys = array_keys($array); 121 | $numKeys = count($allKeys); 122 | $highKey = $numKeys - 1; 123 | $elements = []; 124 | $numElements = 0; 125 | 126 | while ($numElements < $count) { 127 | $num = mt_rand(0, $highKey); 128 | $elements[] = $array[$allKeys[$num]]; 129 | $numElements++; 130 | } 131 | 132 | return $elements; 133 | } 134 | 135 | /** 136 | * Returns a random element from a passed array 137 | * 138 | * @param array $array 139 | * @return mixed 140 | */ 141 | protected static function randomElement($array = ['a', 'b', 'c']) 142 | { 143 | $elements = static::randomElements($array, 1); 144 | 145 | return $elements[0]; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/FlowException.php: -------------------------------------------------------------------------------- 1 | resource = fopen($wrapper, 'r'); 14 | } 15 | 16 | public function block($block = true) 17 | { 18 | stream_set_blocking($this->resource, (int) $block); 19 | 20 | return $this; 21 | } 22 | 23 | public function readChar() 24 | { 25 | return fgetc($this->resource); 26 | } 27 | 28 | public function readLine() 29 | { 30 | $input = fgets($this->resource); 31 | 32 | return (false !== $input) ? trim(chop($input)) : $input; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Interfaces/InputReaderInterface.php: -------------------------------------------------------------------------------- 1 | getLineCount(); 16 | $this->index = ($lineCount > 10) ? ($lineCount - 10) : $lineCount; 17 | } 18 | 19 | public function seekLastLineRead() 20 | { 21 | return parent::seek($this->index); 22 | } 23 | 24 | protected function getLineCount() 25 | { 26 | $this->fastForward(); 27 | 28 | return $this->key(); 29 | } 30 | 31 | protected function fastForward() 32 | { 33 | while(! $this->eof()) $this->fgets(); 34 | } 35 | 36 | public function eof() 37 | { 38 | if($eof = parent::eof()) 39 | $this->index = ($this->key() > 1) ? ($this->key() - 1) : $this->key(); 40 | 41 | return $eof; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/MonologLineParser.php: -------------------------------------------------------------------------------- 1 | .*)\]\s(?P\w+).(?P\w+):\s(?P.+?)\s(?P\{.*\}|\[.*\])\s(?P\{.*\}|\[.*\])$/'; 10 | 11 | /** 12 | * Constructor 13 | * @param string $pattern 14 | */ 15 | public function __construct($pattern = null) 16 | { 17 | $this->pattern = ($pattern) ?: $this->pattern; 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function parse($log) 24 | { 25 | if ( !is_string($log) || strlen($log) === 0) return null; 26 | 27 | preg_match($this->pattern, $log, $data); 28 | 29 | if (! isset($data['date'])) return null; 30 | return [ 31 | '{date}' => \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']), 32 | '{logger}' => $data['logger'], 33 | '{level}' => $data['level'], 34 | '{message}' => $data['message'], 35 | '{context}' => json_decode($data['context'], true), 36 | '{extra}' => json_decode($data['extra'], true) 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/MonologLinePrinter.php: -------------------------------------------------------------------------------- 1 | parser = $parser; 18 | $this->invert = $invert; 19 | $this->theme = $theme; 20 | } 21 | 22 | public function invert() 23 | { 24 | return $this->invert = ! $this->invert; 25 | } 26 | 27 | public function format($raw) 28 | { 29 | $raw = trim($raw); 30 | if(empty($raw)) return; 31 | $data = $this->parser->parse($raw); 32 | $line = $raw . "\n"; 33 | if ($data) { 34 | $data = new DataBag($data); 35 | $data->{'{date}'} = $data->{'{date}'}->format($this->theme->{'date format'}); 36 | $data->{'{extra}'} = json_encode($data->{'{extra}'}); 37 | $data->{'{context}'} = json_encode($data->{'{context}'}); 38 | $data->{'{date}'} = $this->theme->{'date prepend'} . $data->{'{date}'} . $this->theme->{'date append'}; 39 | $data->{'{logger}'} = $this->theme->{'logger prepend'} . $data->{'{logger}'} . $this->theme->{'logger append'}; 40 | $data->{'{message}'} = $this->theme->{'message prepend'} . $data->{'{message}'} . $this->theme->{'message append'}; 41 | $levelFormat = $this->theme->get('level ' . $data->{'{level}'}, $this->theme->{'level DEFAULT'}); 42 | $data->{'{level}'} = 43 | $this->theme->{'level prepend'} 44 | . $levelFormat['prepend'] 45 | . $this->pad($data->{'{level}'}, $this->theme->{'level pad'}, constant($this->theme->{'level pad-type'})) 46 | . $levelFormat['append'] 47 | . $this->theme->{'level append'}; 48 | $line = (($this->invert) ? "\e[7m" : '') 49 | . $this->theme->{'prepend'} 50 | . $this->pad( 51 | str_replace($data->keys(), $data->values(), $this->theme->{'template'}), 52 | $this->getScreenWidth() + $this->theme->{'compensation'}) 53 | . $this->theme->{'append'}; 54 | } 55 | 56 | return $this->clearLine() . $line; 57 | } 58 | 59 | public function pad($input, $pad_length, $pad_style = STR_PAD_RIGHT, $encoding = 'UTF-8') 60 | { 61 | return str_pad($input, strlen($input) - mb_strlen($input,$encoding) + $pad_length, ' ', $pad_style); 62 | } 63 | 64 | public function clearLinesUp($lines = 1) 65 | { 66 | return "\e[{$lines}A"; 67 | } 68 | 69 | public function clearLine() 70 | { 71 | return "\r\e[K"; 72 | } 73 | 74 | public function clearAll() 75 | { 76 | return "\e[2J"; 77 | } 78 | 79 | public function unformat() 80 | { 81 | return "\e[0m"; 82 | } 83 | 84 | public function getScreenWidth() 85 | { 86 | return (int) exec('tput cols'); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Theme.php: -------------------------------------------------------------------------------- 1 | data = call_user_func_array( 27 | 'array_replace_recursive', array_reverse($themes))['theme']; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Ticker.php: -------------------------------------------------------------------------------- 1 | rewind(); 13 | } 14 | 15 | public function __toString() 16 | { 17 | return $this->current() . $this->next(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/DataBagTest.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'b' => [ 17 | 'c' => 'd' 18 | ] 19 | ] 20 | ]; 21 | 22 | public function setUp() 23 | { 24 | $this->bag = new DataBag($this->data); 25 | } 26 | 27 | public function testAll() 28 | { 29 | $this->assertEquals($this->data, $this->bag->all()); 30 | } 31 | 32 | public function testSet() 33 | { 34 | $this->bag->set('a.b.c', 'PATCH'); // update nested index 35 | $this->assertArraySubset(['a' => ['b' => ['c' => 'PATCH']]], $this->bag->all()); 36 | 37 | $this->bag->set('a.b.c.d', 'PATCH'); // override non-array value 38 | $this->assertArraySubset(['a' => ['b' => ['c' => ['d' => 'PATCH']]]], $this->bag->all()); 39 | 40 | $this->bag->{'x y z'} = 'PATCH'; // create nested index 41 | $this->assertArraySubset(['x' => ['y' => ['z' => 'PATCH']]], $this->bag->all()); 42 | } 43 | 44 | public function testGet() 45 | { 46 | $this->assertEquals('d', $this->bag->get('a.b.c')); 47 | $this->assertEquals('d', $this->bag->{'a b c'}); 48 | $this->assertEquals('default', $this->bag->get('not set', 'default')); 49 | } 50 | 51 | public function testKeys() 52 | { 53 | $this->assertSame(['a'], $this->bag->keys()); 54 | } 55 | 56 | public function testValues() 57 | { 58 | $this->bag->set('a', 'b'); 59 | $this->assertSame(['b'], $this->bag->values()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /test/EpilogTest.php: -------------------------------------------------------------------------------- 1 | false, 17 | '--theme' => 'default', 18 | '--theme-invert' => false, 19 | '--filter' => null, 20 | '--debug' => false, 21 | '--sleep-interval' => 0, 22 | '--silent' => true 23 | ]; 24 | 25 | public function tearDown() 26 | { 27 | M::close(); 28 | } 29 | 30 | /** 31 | * @expectedException Epilog\FlowException 32 | * @expectedExceptionMessage Bye! 33 | */ 34 | public function testNoFollowOption() 35 | { 36 | $this->getEpilog(['--no-follow' => true]) 37 | ->run(new FakeLogTail); 38 | } 39 | 40 | /** 41 | * @expectedException RegexGuard\RegexException 42 | */ 43 | public function testInvalidFilterOption() 44 | { 45 | $this->getEpilog(['--filter' => '/bad-regex']) 46 | ->run(new FakeLogTail); 47 | } 48 | 49 | /** 50 | * @expectedException Epilog\FlowException 51 | * @expectedExceptionMessage Bye! 52 | */ 53 | public function testQuitInteraction() 54 | { 55 | $stdin = $this->getStdinMockTemplate() 56 | ->shouldReceive('readLine')->twice() 57 | ->andReturn('', 'q') 58 | ->getMock(); 59 | $epilog = $this->getEpilog(); 60 | $epilog->run(new FakeLogTail, $stdin); 61 | } 62 | 63 | /** 64 | * @expectedException Epilog\FlowException 65 | */ 66 | public function testInvalidInteraction() 67 | { 68 | $this->expectOutputRegex('#Invalid option "\?" given\.#'); 69 | $stdin = $this->getStdinMockTemplate() 70 | ->shouldReceive('readLine')->twice() 71 | ->andReturn('?', 'q') 72 | ->getMock(); 73 | $this->getEpilog(['--silent' => null]) 74 | ->run(new FakeLogTail, $stdin); 75 | } 76 | 77 | /** 78 | * @expectedException Epilog\FlowException 79 | */ 80 | public function testClearInteraction() 81 | { 82 | $this->expectOutputRegex('#\\e\[2J#'); 83 | $stdin = $this->getStdinMockTemplate() 84 | ->shouldReceive('readLine')->twice() 85 | ->andReturn('c', 'q') 86 | ->getMock(); 87 | $this->getEpilog(['--silent' => null]) 88 | ->run(new FakeLogTail, $stdin); 89 | 90 | } 91 | 92 | public function testThemeInteraction() 93 | { 94 | $stdin = $this->getStdinMockTemplate() 95 | ->shouldReceive('readLine')->times(4) 96 | ->andReturn('3', 'q', 'r', 'q') 97 | ->getMock(); 98 | $epilog = $this->getEpilog(); 99 | 100 | $this->assertEquals($epilog::$themes['3'], $this->sandbox($epilog, $stdin)->args()['--theme']); 101 | $this->sandbox($epilog, $stdin); 102 | } 103 | 104 | /** 105 | * @expectedException Epilog\FlowException 106 | */ 107 | public function testThemeInvertInteraction() 108 | { 109 | $this->expectOutputRegex('#\\e\[7m#'); 110 | $stdin = $this->getStdinMockTemplate() 111 | ->shouldReceive('readLine')->twice() 112 | ->andReturn('i', 'q') 113 | ->getMock(); 114 | $this->getEpilog(['--silent' => null]) 115 | ->run(new FakeLogTail, $stdin); 116 | } 117 | 118 | public function testDebugInteraction() 119 | { 120 | $stdin = $this->getStdinMockTemplate() 121 | ->shouldReceive('readLine')->times(2) 122 | ->andReturn('d', 'q') 123 | ->getMock(); 124 | $epilog = $this->getEpilog(['--debug' => true]); 125 | $this->assertTrue($this->sandbox($epilog, $stdin)->args()['--debug']); 126 | } 127 | 128 | public function testFilterInteraction() 129 | { 130 | $stdin = $this->getStdinMockTemplate() 131 | ->shouldReceive('readLine')->times(4) 132 | ->andReturn('/DEBUG/', 'q', '-', 'q') 133 | ->getMock(); 134 | $epilog = $this->getEpilog(); 135 | $this->assertEquals('/DEBUG/', $this->sandbox($epilog, $stdin)->args()['--filter']); 136 | $this->assertNull($this->sandbox($epilog, $stdin)->args()['--filter']); 137 | } 138 | 139 | protected function getEpilog(array $options = []) 140 | { 141 | $args = array_replace_recursive($this->defaults, $options); 142 | 143 | return new Epilog(new Response($args)); 144 | } 145 | 146 | protected function getStdinMockTemplate() 147 | { 148 | return M::mock('Epilog\Interfaces\InputReaderInterface') 149 | ->shouldReceive('block') 150 | ->andReturn(M::self()) 151 | ->shouldReceive('readChar') 152 | ->andReturn('r'); 153 | } 154 | 155 | protected function sandbox($epilog, $stdin) 156 | { 157 | try { 158 | $epilog->run(new FakeLogTail, $stdin); 159 | } catch (\Epilog\FlowException $e) { 160 | return $epilog; 161 | } 162 | $this->fail('Epilog console did not quit ptoperly'); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/InputReaderTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('BLOCKING', $stdinReader->readLine()); 17 | $this->assertEquals('A', $stdinReader->readChar()); 18 | $this->assertEquals(PHP_EOL, $stdinReader->readChar()); 19 | $stdinReader->block(false); 20 | $this->assertEquals('UNBLOCKING', $stdinReader->readLine()); 21 | $this->assertEquals('B', $stdinReader->readChar()); 22 | $this->assertEquals(PHP_EOL, $stdinReader->readChar()); 23 | $stdinReader->block(true); 24 | $this->assertEquals('BLOCKING', $stdinReader->readLine()); 25 | $this->assertEquals('C', $stdinReader->readChar()); 26 | $this->assertFalse($stdinReader->readChar()); 27 | $this->assertFalse($stdinReader->readLine()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /test/LogFinderTest.php: -------------------------------------------------------------------------------- 1 | at($dir); 25 | $this->assertEquals('vfs://dir/allow.log', $finder->find(Vsf::url('dir/allow.log'))); // pass 26 | 27 | Vsf::newFile('dir/deny.log')->at($dir)->withContent('*')->chmod(555); 28 | $finder->find(Vsf::url('dir/deny.log')); // throw 29 | } 30 | 31 | public function laravelLogProvider() 32 | { 33 | return [ 34 | ['/fixtures/laravel_a/app/storage/logs/log.log', '/fixtures/laravel_a/'], 35 | ['/fixtures/laravel_a/app/storage/logs/log.log', '/fixtures/laravel_a/app/storage/logs/log.log'], 36 | ['/fixtures/laravel_b/app/storage/logs/log.txt', '/fixtures/laravel_b/'], 37 | ['/fixtures/laravel_b/app/storage/logs/log.txt', '/fixtures/laravel_b/app/storage/logs/log.txt'], 38 | ]; 39 | } 40 | 41 | /** 42 | * @dataProvider laravelLogProvider 43 | */ 44 | public function testLaravelFinder($log, $app) 45 | { 46 | $finder = LogFinderFactory::getLogFinder('laravel'); 47 | $this->assertEquals(__DIR__ . $log, $finder->find(__DIR__ . $app)); 48 | } 49 | 50 | /** 51 | * @expectedException Epilog\FlowException 52 | * @expectedExceptionMessage Could not read latest log file 53 | */ 54 | public function testLaravelFinderFailure() 55 | { 56 | $finder = LogFinderFactory::getLogFinder('laravel'); 57 | $finder->find('missing'); 58 | } 59 | 60 | /** 61 | * @expectedException Epilog\FlowException 62 | * @expectedExceptionMessage Log finder for Foo is not available 63 | */ 64 | public function testLogFinderFactoryFailure() 65 | { 66 | LogFinderFactory::getLogFinder('Foo'); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /test/LogTailTest.php: -------------------------------------------------------------------------------- 1 | fixture = tempnam(sys_get_temp_dir(), 'epilog-'); 18 | } 19 | 20 | public function testLogTailWithEmptyFile() 21 | { 22 | $log = new LogTail($this->fixture, 'r'); 23 | $this->assertEquals(0, $log->key()); 24 | $log->seekLastLineRead(); 25 | $this->assertEquals(0, $log->key()); 26 | $this->assertEquals('', $log->fgets()); 27 | $this->assertEquals(0, $log->key()); 28 | $this->assertTrue($log->eof()); 29 | } 30 | 31 | public function testLogTailWithShortFile() 32 | { 33 | $this->growLog("line {i}", 3); 34 | $log = new LogTail($this->fixture, 'r'); 35 | $log->seekLastLineRead(); 36 | $this->assertEquals(3, $log->key()); 37 | $this->assertTrue($log->eof()); 38 | } 39 | 40 | public function testLogTailWithLongerFile() 41 | { 42 | $this->growLog("line {i}", 11); 43 | $log = new LogTail($this->fixture, 'r'); 44 | $log->seekLastLineRead(); 45 | $this->assertEquals(1, $log->key()); 46 | $log->fgets(); 47 | $log->fgets(); 48 | $log->fgets(); 49 | $log->fgets(); 50 | $log->fgets(); 51 | $log->fgets(); 52 | $log->fgets(); 53 | $log->fgets(); 54 | $this->assertEquals("line 10\n", $log->fgets()); 55 | $this->assertEquals('', $log->fgets()); 56 | $this->assertTrue($log->eof()); 57 | 58 | $this->growLog('bump!'); 59 | $log->seekLastLineRead(); 60 | $this->assertFalse($log->eof()); 61 | $this->assertEquals("bump!\n", $log->fgets()); 62 | $this->assertEquals('', $log->fgets()); 63 | $this->assertTrue($log->eof()); 64 | } 65 | 66 | public function testLogTailWhenFileIsTruncated() 67 | { 68 | $this->growLog("line {i}", 100); 69 | $log = new LogTail($this->fixture, 'r'); 70 | $log->seekLastLineRead(); 71 | $this->assertEquals(90, $log->key()); 72 | $this->truncateLog(); 73 | $log->seekLastLineRead(); 74 | $this->assertEquals(1, $log->key()); 75 | } 76 | 77 | protected function growLog($text, $times = 1) 78 | { 79 | for ($i=0; $i < $times; $i++) 80 | file_put_contents( 81 | $this->fixture, str_replace('{i}', $i, $text) . "\n", FILE_APPEND); 82 | } 83 | 84 | protected function truncateLog() 85 | { 86 | file_put_contents($this->fixture, "\n"); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/MonologLineParserTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($parsed, $parser->parse($entry)); 19 | } 20 | 21 | public function logEntryProvider() 22 | { 23 | return [ 24 | [ 25 | "[2014-09-22 18:01:29] log.EMERGENCY: Ea ratione eaque quae adipisci odio. [] []", 26 | [ 27 | '{date}' => \DateTime::createFromFormat('Y-m-d H:i:s', '2014-09-22 18:01:29'), 28 | '{logger}' => 'log', 29 | '{level}' => 'EMERGENCY', 30 | '{message}' => 'Ea ratione eaque quae adipisci odio.', 31 | '{context}' => json_decode('[]', true), 32 | '{extra}' => json_decode('[]', true) 33 | ] 34 | ], 35 | [ 36 | "[2014-09-22 18:01:30] log.NOTICE: [\"lala\", \"lele\",{\"lili\": {\"lolo\" : \"lulu\"}}] [] []", 37 | [ 38 | '{date}' => \DateTime::createFromFormat('Y-m-d H:i:s', '2014-09-22 18:01:30'), 39 | '{logger}' => 'log', 40 | '{level}' => 'NOTICE', 41 | '{message}' => "[\"lala\", \"lele\",{\"lili\": {\"lolo\" : \"lulu\"}}]", 42 | '{context}' => json_decode('[]', true), 43 | '{extra}' => json_decode('[]', true) 44 | ] 45 | ], 46 | [ 47 | '[2014-12-11 20:23:47] log.WARNING: POST http://just/some-url/657B_A-YD {"data":"[object] (stdClass: {\"serialized\":\"object\",\"sometimestamp\":\"2014-12-11T20:23:46.764Z\"})","someobj":{"string":"ABC-DEF"}} []', 48 | [ 49 | '{date}' => \DateTime::createFromFormat('Y-m-d H:i:s', '2014-12-11 20:23:47'), 50 | '{logger}' => 'log', 51 | '{level}' => 'WARNING', 52 | '{message}' => 'POST http://just/some-url/657B_A-YD', 53 | '{context}' => json_decode('{"data":"[object] (stdClass: {\"serialized\":\"object\",\"sometimestamp\":\"2014-12-11T20:23:46.764Z\"})","someobj":{"string":"ABC-DEF"}}', true), 54 | '{extra}' => json_decode('[]', true), 55 | ] 56 | ], 57 | [ 58 | "invalid log Consequatur vitae molestias ut ipsa praesentium. [] []", 59 | null 60 | ] 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/TickerTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('0', $ticker->__toString()); 16 | $this->assertEquals('1', $ticker->__toString()); 17 | $this->assertEquals('2', $ticker->__toString()); 18 | $this->assertEquals('3', $ticker->__toString()); 19 | $this->assertEquals('0', $ticker->__toString()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/laravel_a/app/storage/logs/log.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcioAlmada/epilog/e5d8d718c4f929a2a723383261f3687f179d480d/test/fixtures/laravel_a/app/storage/logs/log.log -------------------------------------------------------------------------------- /test/fixtures/laravel_b/app/storage/logs/log.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcioAlmada/epilog/e5d8d718c4f929a2a723383261f3687f179d480d/test/fixtures/laravel_b/app/storage/logs/log.txt -------------------------------------------------------------------------------- /themes/chaplin.yml: -------------------------------------------------------------------------------- 1 | name: Chaplin 2 | author: Márcio Almada 3 | theme: 4 | extends: default 5 | 6 | # the log line template 7 | template: "{level} {date} {message} [{logger}] [{context}] [{extra}]" 8 | 9 | # literal string that will be prepended to entire line 10 | prepend: "\e[0m" 11 | 12 | # literal string that will be appended to entire line 13 | append: "\n" 14 | 15 | compensation: 24 16 | 17 | # level section 18 | level: 19 | DEBUG: 20 | prepend: "\e[048;5;255m\e[038;5;235m " 21 | INFO: 22 | prepend: "\e[048;5;255m\e[038;5;235m " 23 | NOTICE: 24 | prepend: "\e[048;5;255m\e[038;5;235m " 25 | WARNING: 26 | prepend: "\e[048;5;255m\e[038;5;235m " 27 | ERROR: 28 | prepend: "\e[048;5;235m\e[038;5;255m " 29 | CRITICAL: 30 | prepend: "\e[048;5;235m\e[038;5;255m " 31 | ALERT: 32 | prepend: "\e[048;5;235m\e[038;5;255m " 33 | EMERGENCY: 34 | prepend: "\e[048;5;235m\e[038;5;255m " 35 | DEFAULT: 36 | prepend: "\e[048;5;235m\e[038;5;255m " 37 | 38 | # message segent 39 | message: 40 | prepend: '"' 41 | append: '"' 42 | -------------------------------------------------------------------------------- /themes/default.yml: -------------------------------------------------------------------------------- 1 | name: #theme name 2 | author: #author name 3 | theme: 4 | # the theme to inherit properties 5 | # extends: # default does not inherit 6 | 7 | # the log line template 8 | # you can use {date}, {level}, {logger}, {message}, {context}, {extra} template tags 9 | # !! 10 | template: "{date} {level}.{logger} {message} {context} {extra}" 11 | 12 | # style to be applied at line start (ANSI escaped seq or literal string) 13 | prepend: 14 | 15 | # style to be applied at line ends (ANSI escaped seq or literal string) 16 | append: "\n" 17 | 18 | # padding to compensate ANSI escaped characters 19 | compensation: 0 20 | 21 | # level section 22 | level: 23 | pad: 10 24 | pad-type: STR_PAD_RIGHT 25 | prepend: 26 | append: 27 | DEBUG: 28 | prepend: 29 | append: 30 | INFO: 31 | prepend: 32 | append: 33 | NOTICE: 34 | prepend: 35 | append: 36 | WARNING: 37 | prepend: 38 | append: 39 | ERROR: 40 | prepend: 41 | append: 42 | CRITICAL: 43 | prepend: 44 | append: 45 | ALERT: 46 | prepend: 47 | append: 48 | EMERGENCY: 49 | prepend: 50 | append: 51 | DEFAULT: 52 | prepend: 53 | append: 54 | 55 | # message segent 56 | message: 57 | prepend: 58 | append: 59 | 60 | # date segment 61 | date: 62 | format: "Y-m-d H:i:s" 63 | prepend: 64 | append: 65 | 66 | # logger section 67 | logger: 68 | prepend: 69 | append: 70 | 71 | # context section 72 | context: 73 | prepend: 74 | append: 75 | 76 | # extra section 77 | extra: 78 | prepend: 79 | append: 80 | -------------------------------------------------------------------------------- /themes/forest.yml: -------------------------------------------------------------------------------- 1 | name: Amazônia 2 | author: Márcio Almada 3 | theme: 4 | extends: default 5 | # the line template 6 | template: "{level} ▴ {date} ▴ {message} [{logger}] [{context}] [{extra}]" 7 | 8 | # padding compensation in case of ANSI escaped characters 9 | compensation: 13 10 | 11 | # level section 12 | level: 13 | DEBUG: 14 | prepend: "\e[038;05;072m ▴ " 15 | INFO: 16 | prepend: "\e[038;05;072m ▴ ▴ " 17 | NOTICE: 18 | prepend: "\e[038;05;072m ▴▴ ▴ " 19 | WARNING: 20 | prepend: "\e[038;05;035m ▴▴ ▴ ▴ " 21 | ERROR: 22 | prepend: "\e[038;05;035m ▴▴▴ ▴▴ " 23 | CRITICAL: 24 | prepend: "\e[038;05;035m ▴▴ ▴▴ ▴▴ " 25 | ALERT: 26 | prepend: "\e[038;05;029m ▴▴▴ ▴▴▴▴ " 27 | EMERGENCY: 28 | prepend: "\e[038;05;029m ▴▴ ▴▴▴▴▴▴ " 29 | append: 30 | DEFAULT: 31 | prepend: "\e[038;05;029m . . .. ." 32 | -------------------------------------------------------------------------------- /themes/punchcard.yml: -------------------------------------------------------------------------------- 1 | name: Punched Card 2 | author: Márcio Almada 3 | theme: 4 | extends: default 5 | 6 | # the log line template 7 | template: "{level} {date} {message} [{logger}] [{context}] [{extra}]" 8 | 9 | # literal string that will be prepended to entire line 10 | prepend: "" 11 | 12 | # literal string that will be appended to entire line 13 | append: "\e[0m\n" 14 | 15 | # level section 16 | level: 17 | padding: 10 18 | DEBUG: 19 | prepend: " • \e[2m" 20 | INFO: 21 | prepend: " • \e[2m" 22 | NOTICE: 23 | prepend: " • \e[2m" 24 | WARNING: 25 | prepend: " • \e[2m" 26 | ERROR: 27 | prepend: " • \e[1m" 28 | CRITICAL: 29 | prepend: " • \e[1m" 30 | ALERT: 31 | prepend: " • \e[1m" 32 | EMERGENCY: 33 | prepend: " • \e[1m" 34 | DEFAULT: 35 | prepend: " -------- \e[1m" 36 | -------------------------------------------------------------------------------- /themes/scrapbook.yml: -------------------------------------------------------------------------------- 1 | name: Minial 2 | author: Márcio Almada 3 | theme: 4 | extends: default 5 | # the line template 6 | template: "\n [{date}] [{logger}.{level}]\n Message:{message}\n Context:{context}\n Extra:{extra}\n" 7 | append: "\n=================================================================================\n" 8 | level: 9 | pad: 0 10 | #date section 11 | date: 12 | format: "H:i:s" 13 | -------------------------------------------------------------------------------- /themes/sunrise.yml: -------------------------------------------------------------------------------- 1 | name: Sunrise 2 | author: Márcio Almada 3 | theme: 4 | extends: sunset 5 | prepend: "\e[038;5;237m" 6 | level: 7 | DEBUG: 8 | prepend: "\e[048;5;189m " 9 | INFO: 10 | prepend: "\e[048;5;153m " 11 | NOTICE: 12 | prepend: "\e[048;5;225m " 13 | WARNING: 14 | prepend: "\e[048;5;224m " 15 | ERROR: 16 | prepend: "\e[048;5;223m " 17 | CRITICAL: 18 | prepend: "\e[048;5;222m " 19 | ALERT: 20 | prepend: "\e[048;5;221m " 21 | EMERGENCY: 22 | prepend: "\e[048;5;220m " 23 | DEFAULT: 24 | prepend: "\e[048;5;184m " 25 | -------------------------------------------------------------------------------- /themes/sunset.yml: -------------------------------------------------------------------------------- 1 | name: Sunset 2 | author: Márcio Almada 3 | theme: 4 | # the theme to inherit properties 5 | extends: default 6 | 7 | # the line template 8 | template: "{level} {date} {message} [{logger}] [{context}] [{extra}]" 9 | 10 | # style to be applied at line start (ANSI escaped seq or literal string) 11 | prepend: "\e[038;5;15m" 12 | 13 | # padding to compensate ANSI escaped characters 14 | compensation: 12 15 | 16 | # level section 17 | level: 18 | DEBUG: 19 | prepend: "\e[048;5;099m - " 20 | append: 21 | INFO: 22 | prepend: "\e[048;5;135m -- " 23 | append: 24 | NOTICE: 25 | prepend: "\e[048;5;171m --- " 26 | append: 27 | WARNING: 28 | prepend: "\e[048;5;207m ---- " 29 | append: 30 | ERROR: 31 | prepend: "\e[048;5;206m ----- " 32 | append: 33 | CRITICAL: 34 | prepend: "\e[048;5;205m ------ " 35 | append: 36 | ALERT: 37 | prepend: "\e[048;5;204m ------- " 38 | append: 39 | EMERGENCY: 40 | prepend: "\e[048;5;203m -------- " 41 | append: 42 | DEFAULT: 43 | prepend: "\e[048;5;167m -------- " 44 | append: 45 | -------------------------------------------------------------------------------- /themes/traffic.yml: -------------------------------------------------------------------------------- 1 | name: Traffic 2 | author: Márcio Almada 3 | theme: 4 | extends: default 5 | 6 | # the log line template 7 | template: "{date} {logger} # {level} # {message} [{context}] [{extra}]" 8 | 9 | # level section 10 | level: 11 | padding: 10 12 | pad-type: STR_PAD_BOTH 13 | DEBUG: 14 | prepend: " | " 15 | append: " | " 16 | INFO: 17 | prepend: " | " 18 | append: " | " 19 | NOTICE: 20 | prepend: " | " 21 | append: " | " 22 | WARNING: 23 | prepend: " | " 24 | append: " | " 25 | ERROR: 26 | prepend: " | " 27 | append: " | " 28 | CRITICAL: 29 | prepend: " | " 30 | append: " | " 31 | ALERT: 32 | prepend: " | " 33 | append: " | " 34 | EMERGENCY: 35 | prepend: " | " 36 | append: " | " 37 | DEFAULT: 38 | prepend: " " 39 | append: 40 | 41 | date: 42 | format: H:i:s 43 | --------------------------------------------------------------------------------