├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── sample.png └── src ├── CILogViewer.php └── Views └── logs.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | =========== 3 | 4 | V2.0.0 5 | ------ 6 | - Added support for CodeIgniter 4 and deprecate support for CodeIgniter 3 7 | 8 | V1.1.2 9 | ------- 10 | - Fix issue [#13](https://github.com/SeunMatt/codeigniter-log-viewer/issues/13) and improve regex patterns 11 | 12 | V1.1.1 13 | ------- 14 | - Fix security bug with file download [#8](https://github.com/SeunMatt/codeigniter-log-viewer/issues/8) 15 | - Updated required PHP version to >=7.1 16 | 17 | V1.1.0 18 | ------- 19 | - Added API capability, such that log and log files can be obtained in a JSON response 20 | - Added log folder path and log file pattern configuration such that they can be configured via CodeIgniter's config.php file 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Seun Matt 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CodeIgniter Log Viewer 2 | ====================== 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/seunmatt/codeigniter-log-viewer/v/stable)](https://packagist.org/packages/seunmatt/codeigniter-log-viewer) [![Total Downloads](https://poser.pugx.org/seunmatt/codeigniter-log-viewer/downloads)](https://packagist.org/packages/seunmatt/codeigniter-log-viewer) [![License](https://poser.pugx.org/seunmatt/codeigniter-log-viewer/license)](https://packagist.org/packages/seunmatt/codeigniter-log-viewer) 5 | 6 | This is a simple Log Viewer for viewing CodeIgniter logs in the browser or via API calls (that returns a JSON response) 7 | 8 | This project is inspired by the [laravel-log-viewer project](https://github.com/rap2hpoutre/laravel-log-viewer). 9 | 10 | A typical log view looks like this: 11 | 12 | ![sample.png](sample.png) 13 | 14 | Usage 15 | ===== 16 | 17 | **For CodeIgniter 3, see this [reference guide](https://github.com/SeunMatt/codeigniter-log-viewer/wiki/CodeIgniter-3-Guide)** 18 | 19 | Requirements 20 | ----------- 21 | - PHP >= 7.4 22 | - CodeIgniter 4 23 | 24 | Composer Installation 25 | --------------------- 26 | ``` 27 | composer require seunmatt/codeigniter-log-viewer 28 | ``` 29 | 30 | Controller Integration for Browser Display 31 | ------------------------------------------ 32 | 33 | 34 | All that is required is to execute the `showLogs()` method in a Controller that is mapped to a route: 35 | 36 | A typical Controller *(LogViewerController.php)* will have the following content: 37 | 38 | ```php 39 | namespace App\Controllers; 40 | use CILogViewer\CILogViewer; 41 | 42 | class LogViewerController extends BaseController 43 | { 44 | public function index() { 45 | $logViewer = new CILogViewer(); 46 | return $logViewer->showLogs(); 47 | } 48 | } 49 | ``` 50 | 51 | Then the route `app/Config/Routes.php` can be configured like: 52 | 53 | ```php 54 | $routes->get('logs', "LogViewerController::index"); 55 | ``` 56 | 57 | And that's all! If you visit `/logs` on your browser 58 | you should see all the logs that are in `writable/logs` folder and their content 59 | 60 | 61 | Configuration 62 | ============== 63 | 64 | The package allows you to configure some of its parameters by creating a `CILogViewer` class in CodeIgniter's `Config` folder and then adding the following variables: 65 | 66 | - The folder path for log files can be configured with the `$logFolderPath` config var. 67 | 68 | - The file pattern for matching all the log files in the log folder can be configured by adding `$logFilePattern` config var. 69 | - The name of the view that renders the logs page can be changed using the `$viewName` config var. Please note that this can be a route relative to your `View` path or a namespace route. 70 | 71 | Example configuration file `app/Config/CILogViewer.php`: 72 | 73 | ```php 74 | logViewer->showLogs();` 96 | - Finally, map your controller function to a route. 97 | 98 | API Commands 99 | ------------ 100 | 101 | The API is implemented via a set of query params that can be appended to the `/logs` path. 102 | 103 | Query: 104 | 105 | - `/logs?api=list` will list all the log files available in the configured folder 106 | 107 | Response: 108 | 109 | ```json 110 | { 111 | "status": true, 112 | "log_files": [ 113 | { 114 | "file_b64": "bG9nLTIwMTgtMDEtMTkucGhw", 115 | "file_name": "log-2018-01-19.php" 116 | }, 117 | { 118 | "file_b64": "bG9nLTIwMTgtMDEtMTcucGhw", 119 | "file_name": "log-2018-01-17.php" 120 | } 121 | ] 122 | } 123 | ``` 124 | 125 | **file_b64 is the base64 encoded name of the file that will be used in further operations and API calls** 126 | 127 | Query: 128 | 129 | - `/logs?api=view&f=bG9nLTIwMTgtMDEtMTcucGhw` will return the logs contained in the log file specified by the `f` parameter. 130 | 131 | The value of the `f` (*f stands for file*) is the base64 encoded format of the log file name. It is obtained from the `/logs?api=list` API call. 132 | A list of all available log files is also returned. 133 | 134 | Response: 135 | 136 | ```json 137 | { 138 | "log_files": [ 139 | { 140 | "file_b64": "bG9nLTIwMTgtMDEtMTkucGhw", 141 | "file_name": "log-2018-01-19.php" 142 | }, 143 | { 144 | "file_b64": "bG9nLTIwMTgtMDEtMTcucGhw", 145 | "file_name": "log-2018-01-17.php" 146 | } 147 | ], 148 | "status": true, 149 | "logs": [ 150 | "ERROR - 2018-01-23 07:12:31 --> 404 Page Not Found: admin/Logs/index", 151 | "ERROR - 2018-01-23 07:12:37 --> 404 Page Not Found: admin//index", 152 | "ERROR - 2018-01-23 15:23:02 --> 404 Page Not Found: Faviconico/index" 153 | ] 154 | } 155 | ``` 156 | 157 | The API Query can also take one last parameter, `sline` that will determine how the logs are returned 158 | When it's `true` the logs are returned in a single line: 159 | 160 | Query: 161 | 162 | `/logs?api=view&f=bG9nLTIwMTgtMDEtMTkucGhw&sline=true` 163 | 164 | Response: 165 | 166 | ```json 167 | { 168 | "log_files": [ 169 | { 170 | "file_b64": "bG9nLTIwMTgtMDEtMTkucGhw", 171 | "file_name": "log-2018-01-19.php" 172 | }, 173 | { 174 | "file_b64": "bG9nLTIwMTgtMDEtMTcucGhw", 175 | "file_name": "log-2018-01-17.php" 176 | } 177 | ], 178 | "status": true, 179 | "logs": "ERROR - 2018-01-23 07:12:31 --> 404 Page Not Found: admin/Logs/index\r\nERROR - 2018-01-23 07:12:37 --> 404 Page Not Found: admin//index\r\nERROR - 2018-01-23 15:23:02 --> 404 Page Not Found: Faviconico/index\r\n" 180 | } 181 | ``` 182 | 183 | 184 | When it's `false` (**Default**), the logs are returned in as an array, where each element is a line in the log file: 185 | 186 | Query: 187 | 188 | `/logs?api=view&f=bG9nLTIwMTgtMDEtMTkucGhw&sline=false` OR `logs?api=view&f=bG9nLTIwMTgtMDEtMTkucGhw` 189 | 190 | Response: 191 | 192 | ```json 193 | { 194 | 195 | "logs": [ 196 | "ERROR - 2018-01-23 07:12:31 --> 404 Page Not Found: admin/Logs/index", 197 | "ERROR - 2018-01-23 07:12:37 --> 404 Page Not Found: admin//index", 198 | "ERROR - 2018-01-23 15:23:02 --> 404 Page Not Found: Faviconico/index" 199 | ] 200 | } 201 | ``` 202 | 203 | Query: 204 | 205 | `/logs?api=delete&f=bG9nLTIwMTgtMDEtMTkucGhw` will delete a single log file. The **f** parameter is the base64 encoded name of the file 206 | and can be obtained from the view api above. 207 | 208 | Query: 209 | 210 | `/logs?api=delete&f=all` will delete all log files in the configured folder path. Take note of the value for **f** which is the literal '**all**'. 211 | 212 | **IF A FILE IS TOO LARGE (> 50MB), YOU CAN DOWNLOAD IT WITH THIS API QUERY `/logs?dl=bG9nLTIwMTgtMDEtMTcucGhw`** 213 | 214 | 215 | SECURITY NOTE 216 | ============= 217 | **It is Highly Recommended that you protect/secure the route for your logs. It should not be an open resource!** 218 | 219 | Change Log 220 | ========== 221 | [Change Log is available here](./CHANGELOG.md) 222 | 223 | 224 | Author 225 | ====== 226 | - [Seun Matt](https://smattme.com) 227 | 228 | Contributors 229 | ============ 230 | - [Miguel Martinez](https://github.com/savioret) 231 | 232 | 233 | LICENSE 234 | ======= 235 | [MIT](LICENSE) 236 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seunmatt/codeigniter-log-viewer", 3 | "description": "This is a simple Log Viewer for viewing CodeIgniter log files on the browser", 4 | "keywords": ["Code Igniter", "Code Igniter 4", "ci4", "Log Viewer", "PHP"], 5 | "license": "MIT", 6 | "github": "https://github.com/SeunMatt/codeigniter-log-viewer", 7 | "authors": [ 8 | { 9 | "name": "Miguel Martinez", 10 | "email": "rodilla@gmail.com" 11 | }, 12 | { 13 | "name": "Seun Matt", 14 | "email": "smatt382@gmail.com" 15 | } 16 | ], 17 | "archive" : { 18 | "exclude" : ["/sample.png"] 19 | }, 20 | "require": { 21 | "php": ">=7.4" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "CILogViewer\\": "src/" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeunMatt/codeigniter-log-viewer/8f17091797e945063d555134675743c46a00570c/sample.png -------------------------------------------------------------------------------- /src/CILogViewer.php: -------------------------------------------------------------------------------- 1 | 'glyphicon glyphicon-warning-sign', 13 | 'NOTICE' => 'glyphicon glyphicon-warning-sign', 14 | 'WARNING' => 'glyphicon glyphicon-warning-sign', 15 | 'ALERT' => 'glyphicon glyphicon-warning-sign', 16 | 'INFO' => 'glyphicon glyphicon-info-sign', 17 | 'ERROR' => 'glyphicon glyphicon-warning-sign', 18 | 'DEBUG' => 'glyphicon glyphicon-exclamation-sign', 19 | 'EMERGENCY' => 'glyphicon glyphicon-warning-sign', 20 | 'ALL' => 'glyphicon glyphicon-minus', 21 | ]; 22 | 23 | private static $levelClasses = [ 24 | 'CRITICAL' => 'danger', 25 | 'INFO' => 'info', 26 | 'ERROR' => 'danger', 27 | 'DEBUG' => 'warning', 28 | 'NOTICE' => 'info', 29 | 'WARNING' => 'warning', 30 | 'EMERGENCY' => 'danger', 31 | 'ALERT' => 'warning', 32 | 'ALL' => 'muted', 33 | ]; 34 | 35 | const LOG_LINE_HEADER_PATTERN = '/^([A-Z]+)\s*\-\s*([\-\d]+\s+[\:\d]+)\s*\-\->\s*(.+)$/'; 36 | 37 | //this is the path (folder) on the system where the log files are stored 38 | private $logFolderPath = WRITEPATH . 'logs/'; 39 | 40 | //this is the pattern to pick all log files in the $logFilePath 41 | private $logFilePattern = "log-*.log"; 42 | 43 | //this is a combination of the LOG_FOLDER_PATH and LOG_FILE_PATTERN 44 | private $fullLogFilePath = ""; 45 | 46 | /** 47 | * Name of the view to pass to the renderer 48 | * Note that it allows namespaced views if your view is outside 49 | * the View folder. 50 | * 51 | * @var string 52 | */ 53 | private $viewName = "CILogViewer\Views\logs"; 54 | 55 | const MAX_LOG_SIZE = 52428800; //50MB 56 | const MAX_STRING_LENGTH = 300; //300 chars 57 | 58 | /** 59 | * These are the constants representing the 60 | * various API commands there are 61 | */ 62 | private const API_QUERY_PARAM = "api"; 63 | private const API_FILE_QUERY_PARAM = "f"; 64 | private const API_LOG_STYLE_QUERY_PARAM = "sline"; 65 | private const API_CMD_LIST = "list"; 66 | private const API_CMD_VIEW = "view"; 67 | private const API_CMD_DELETE = "delete"; 68 | 69 | 70 | public function __construct() { 71 | $this->init(); 72 | } 73 | 74 | /** 75 | * Bootstrap the library 76 | * sets the configuration variables 77 | * @throws \Exception 78 | */ 79 | private function init() { 80 | $viewerConfig = config('CILogViewer'); 81 | 82 | //configure the log folder path and the file pattern for all the logs in the folder 83 | if($viewerConfig) { 84 | if(isset($viewerConfig->viewName)) { 85 | $this->viewName = $viewerConfig->viewName; 86 | } 87 | if(isset($viewerConfig->logFilePattern)) { 88 | $this->logFilePattern = $viewerConfig->logFilePattern; 89 | } 90 | if(isset($viewerConfig->logFolderPath)) { 91 | $this->logFolderPath = $viewerConfig->logFolderPath; 92 | } 93 | } 94 | 95 | //concatenate to form Full Log Path 96 | $this->fullLogFilePath = $this->logFolderPath . $this->logFilePattern; 97 | } 98 | 99 | /* 100 | * This function will return the processed HTML page 101 | * and return it's content that can then be echoed 102 | * 103 | * @param $fileName optional base64_encoded filename of the log file to process. 104 | * @returns the parse view file content as a string that can be echoed 105 | * */ 106 | public function showLogs() { 107 | 108 | $request = \Config\Services::request(); 109 | 110 | if(!is_null($request->getGet("del"))) { 111 | $this->deleteFiles(base64_decode($request->getGet("del"))); 112 | $uri = \Config\Services::request()->uri->getPath(); 113 | return redirect()->to('/'.$uri); 114 | } 115 | 116 | //process download of log file command 117 | //if the supplied file exists, then perform download 118 | //otherwise, just ignore which will resolve to page reloading 119 | $dlFile = $request->getGet("dl"); 120 | if(!is_null($dlFile) && file_exists($this->logFolderPath . basename(base64_decode($dlFile))) ) { 121 | $file = $this->logFolderPath . basename(base64_decode($dlFile)); 122 | $this->downloadFile($file); 123 | } 124 | 125 | if(!is_null($request->getGet(self::API_QUERY_PARAM))) { 126 | return $this->processAPIRequests($request->getGet(self::API_QUERY_PARAM)); 127 | } 128 | 129 | //it will either get the value of f or return null 130 | $fileName = $request->getGet("f"); 131 | 132 | //get the log files from the log directory 133 | $files = $this->getFiles(); 134 | 135 | //let's determine what the current log file is 136 | if(!is_null($fileName)) { 137 | $currentFile = $this->logFolderPath . basename(base64_decode($fileName)); 138 | } 139 | else if(is_null($fileName) && !empty($files)) { 140 | $currentFile = $this->logFolderPath . $files[0]; 141 | } else { 142 | $currentFile = null; 143 | } 144 | 145 | //if the resolved current file is too big 146 | //just trigger a download of the file 147 | //otherwise process its content as log 148 | 149 | if(!is_null($currentFile) && file_exists($currentFile)) { 150 | 151 | $fileSize = filesize($currentFile); 152 | 153 | if(is_int($fileSize) && $fileSize > self::MAX_LOG_SIZE) { 154 | //trigger a download of the current file instead 155 | $logs = null; 156 | } 157 | else { 158 | $logs = $this->processLogs($this->getLogs($currentFile)); 159 | } 160 | } 161 | else { 162 | $logs = []; 163 | } 164 | 165 | $data['logs'] = $logs; 166 | $data['files'] = !empty($files) ? $files : []; 167 | $data['currentFile'] = !is_null($currentFile) ? basename($currentFile) : ""; 168 | return view($this->viewName, $data); 169 | } 170 | 171 | 172 | private function processAPIRequests($command) { 173 | $request = \Config\Services::request(); 174 | if($command === self::API_CMD_LIST) { 175 | //respond with a list of all the files 176 | $response["status"] = true; 177 | $response["log_files"] = $this->getFilesBase64Encoded(); 178 | } 179 | else if($command === self::API_CMD_VIEW) { 180 | //respond to view the logs of a particular file 181 | $file = $request->getGet(self::API_FILE_QUERY_PARAM); 182 | $response["log_files"] = $this->getFilesBase64Encoded(); 183 | 184 | if(is_null($file) || empty($file)) { 185 | $response["status"] = false; 186 | $response["error"]["message"] = "Invalid File Name Supplied: [" . json_encode($file) . "]"; 187 | $response["error"]["code"] = 400; 188 | } 189 | else { 190 | $singleLine = $request->getGet(self::API_LOG_STYLE_QUERY_PARAM); 191 | $singleLine = !is_null($singleLine) && ($singleLine === true || $singleLine === "true" || $singleLine === "1") ? true : false; 192 | $logs = $this->processLogsForAPI($file, $singleLine); 193 | $response["status"] = true; 194 | $response["logs"] = $logs; 195 | } 196 | } 197 | else if($command === self::API_CMD_DELETE) { 198 | 199 | $file = $request->getGet(self::API_FILE_QUERY_PARAM); 200 | 201 | if(is_null($file)) { 202 | $response["status"] = false; 203 | $response["error"]["message"] = "NULL value is not allowed for file param"; 204 | $response["error"]["code"] = 400; 205 | } 206 | else { 207 | 208 | //decode file if necessary 209 | $fileExists = false; 210 | 211 | if($file !== "all") { 212 | $file = basename(base64_decode($file)); 213 | $fileExists = file_exists($this->logFolderPath . $file); 214 | } 215 | else { 216 | //check if the directory exists 217 | $fileExists = file_exists($this->logFolderPath); 218 | } 219 | 220 | 221 | if($fileExists) { 222 | $this->deleteFiles($file); 223 | $response["status"] = true; 224 | $response["message"] = "File [" . $file . "] deleted"; 225 | } 226 | else { 227 | $response["status"] = false; 228 | $response["error"]["message"] = "File does not exist"; 229 | $response["error"]["code"] = 404; 230 | } 231 | 232 | 233 | } 234 | } 235 | else { 236 | $response["status"] = false; 237 | $response["error"]["message"] = "Unsupported Query Command [" . $command . "]"; 238 | $response["error"]["code"] = 400; 239 | } 240 | 241 | //convert response to json and respond 242 | header("Content-Type: application/json"); 243 | if(!$response["status"]) { 244 | //set a generic bad request code 245 | http_response_code(400); 246 | } else { 247 | http_response_code(200); 248 | } 249 | return json_encode($response); 250 | } 251 | 252 | 253 | /* 254 | * This function will process the logs. Extract the log level, icon class and other information 255 | * from each line of log and then arrange them in another array that is returned to the view for processing 256 | * 257 | * @params logs. The raw logs as read from the log file 258 | * @return array. An [[], [], [] ...] where each element is a processed log line 259 | * */ 260 | private function processLogs($logs) { 261 | 262 | if(is_null($logs)) { 263 | return null; 264 | } 265 | 266 | $superLog = []; 267 | 268 | foreach ($logs as $log) { 269 | 270 | if($this->getLogHeaderLine($log, $level, $logDate, $logMessage)) { 271 | //this is actually the start of a new log and not just another line from previous log 272 | $data = [ 273 | "level" => $level, 274 | "date" => $logDate, 275 | "icon" => self::$levelsIcon[$level], 276 | "class" => self::$levelClasses[$level], 277 | ]; 278 | 279 | if(strlen($logMessage) > self::MAX_STRING_LENGTH) { 280 | $data['content'] = substr($logMessage, 0, self::MAX_STRING_LENGTH); 281 | $data["extra"] = substr($logMessage, (self::MAX_STRING_LENGTH + 1)); 282 | } else { 283 | $data["content"] = $logMessage; 284 | } 285 | 286 | array_push($superLog, $data); 287 | 288 | } else if(!empty($superLog)) { 289 | //this log line is a continuation of previous logline 290 | //so let's add them as extra 291 | $prevLog = $superLog[count($superLog) - 1]; 292 | $extra = (array_key_exists("extra", $prevLog)) ? $prevLog["extra"] : ""; 293 | $prevLog["extra"] = $extra . "
" . $log; 294 | $superLog[count($superLog) - 1] = $prevLog; 295 | } 296 | } 297 | 298 | return $superLog; 299 | } 300 | 301 | 302 | /** 303 | * This function will extract the logs in the supplied 304 | * fileName 305 | * @param $fileNameInBase64 306 | * @param bool $singleLine 307 | * @return array|null 308 | * @internal param $logs 309 | */ 310 | private function processLogsForAPI($fileNameInBase64, $singleLine = false) { 311 | 312 | $logs = null; 313 | 314 | //let's prepare the log file name sent from the client 315 | $currentFile = $this->prepareRawFileName($fileNameInBase64); 316 | 317 | //if the resolved current file is too big 318 | //just return null 319 | //otherwise process its content as log 320 | if(!is_null($currentFile)) { 321 | 322 | $fileSize = filesize($currentFile); 323 | 324 | if (is_int($fileSize) && $fileSize > self::MAX_LOG_SIZE) { 325 | //trigger a download of the current file instead 326 | $logs = null; 327 | } else { 328 | $logs = $this->getLogsForAPI($currentFile, $singleLine); 329 | } 330 | } 331 | 332 | return $logs; 333 | } 334 | 335 | private function getLogHeaderLine($logLine, &$level, &$dateTime, &$message) { 336 | $matches = []; 337 | if(preg_match(self::LOG_LINE_HEADER_PATTERN, $logLine, $matches)) { 338 | $level = $matches[1]; 339 | $dateTime = $matches[2]; 340 | $message = $matches[3]; 341 | } 342 | return $matches; 343 | } 344 | 345 | /* 346 | * returns an array of the file contents 347 | * each element in the array is a line 348 | * in the underlying log file 349 | * @returns array | each line of file contents is an entry in the returned array. 350 | * @params complete fileName 351 | * */ 352 | private function getLogs($fileName) { 353 | $size = filesize($fileName); 354 | if(!$size || $size > self::MAX_LOG_SIZE){ 355 | return null; 356 | } 357 | return file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); 358 | } 359 | 360 | /** 361 | * This function will get the contents of the log 362 | * file as a string. It will first check for the 363 | * size of the file before attempting to get the contents. 364 | * 365 | * By default it will return all the log contents as an array where the 366 | * elements of the array is the individual lines of the files 367 | * otherwise, it will return all file content as a single string with each line ending 368 | * in line break character "\n" 369 | * @param $fileName 370 | * @param bool $singleLine 371 | * @return bool|string 372 | */ 373 | private function getLogsForAPI($fileName, $singleLine = false) { 374 | $size = filesize($fileName); 375 | if(!$size || $size > self::MAX_LOG_SIZE) { 376 | return "File Size too Large. Please donwload it locally"; 377 | } 378 | 379 | return (!$singleLine) ? file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : file_get_contents($fileName); 380 | } 381 | 382 | 383 | /* 384 | * This will get all the files in the logs folder 385 | * It will reverse the files fetched and 386 | * make sure the latest log file is in the first index 387 | * 388 | * @param boolean. If true returns the basename of the files otherwise full path 389 | * @returns array of file 390 | * */ 391 | private function getFiles($basename = true) 392 | { 393 | 394 | $files = glob($this->fullLogFilePath); 395 | 396 | $files = array_reverse($files); 397 | $files = array_filter($files, 'is_file'); 398 | if ($basename && is_array($files)) { 399 | foreach ($files as $k => $file) { 400 | $files[$k] = basename($file); 401 | } 402 | } 403 | return array_values($files); 404 | } 405 | 406 | 407 | /** 408 | * This function will return an array of available log 409 | * files 410 | * The array will containt the base64encoded name 411 | * as well as the real name of the fiile 412 | * @return array 413 | * @internal param bool $appendURL 414 | * @internal param bool $basename 415 | */ 416 | private function getFilesBase64Encoded() { 417 | 418 | $files = glob($this->fullLogFilePath); 419 | 420 | $files = array_reverse($files); 421 | $files = array_filter($files, 'is_file'); 422 | 423 | $finalFiles = []; 424 | 425 | //if we're to return the base name of the files 426 | //let's do that here 427 | foreach ($files as $file) { 428 | array_push($finalFiles, ["file_b64" => base64_encode(basename($file)), "file_name" => basename($file)]); 429 | } 430 | 431 | return $finalFiles; 432 | } 433 | 434 | /* 435 | * Delete one or more log file in the logs directory 436 | * @param filename. It can be all - to delete all log files - or specific for a file 437 | * */ 438 | private function deleteFiles($fileName) { 439 | 440 | if($fileName == "all") { 441 | array_map("unlink", glob($this->fullLogFilePath)); 442 | } 443 | else { 444 | unlink($this->logFolderPath . basename($fileName)); 445 | } 446 | } 447 | 448 | /* 449 | * Download a particular file to local disk 450 | * This should only be called if the file exists 451 | * hence, the file exist check has ot be done by the caller 452 | * @param $fileName the complete file path 453 | * */ 454 | private function downloadFile($file) { 455 | header('Content-Description: File Transfer'); 456 | header('Content-Type: application/octet-stream'); 457 | header('Content-Disposition: attachment; filename="'.basename($file).'"'); 458 | header('Expires: 0'); 459 | header('Cache-Control: must-revalidate'); 460 | header('Pragma: public'); 461 | header('Content-Length: ' . filesize($file)); 462 | readfile($file); 463 | exit; 464 | } 465 | 466 | 467 | /** 468 | * This function will take in the raw file 469 | * name as sent from the browser/client 470 | * and append the LOG_FOLDER_PREFIX and decode it from base64 471 | * @param $fileNameInBase64 472 | * @return null|string 473 | * @internal param $fileName 474 | */ 475 | private function prepareRawFileName($fileNameInBase64) { 476 | 477 | //let's determine what the current log file is 478 | if(!is_null($fileNameInBase64) && !empty($fileNameInBase64)) { 479 | $currentFile = $this->logFolderPath . basename(base64_decode($fileNameInBase64)); 480 | } 481 | else { 482 | $currentFile = null; 483 | } 484 | 485 | return $currentFile; 486 | } 487 | 488 | 489 | 490 | } 491 | 492 | 493 | 494 | -------------------------------------------------------------------------------- /src/Views/logs.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CodeIgniter log viewer 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 21 | 45 | 46 | 47 |
48 |
49 | 65 |
66 | 67 |
68 |

69 | Log file > 50MB, please download it. 70 |

71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | $log): ?> 84 | 85 | 86 | 90 | 91 | 107 | 108 | 109 | 110 |
LevelDateContent
87 | 88 |   89 | 92 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 104 | 105 | 106 |
111 | 112 |
113 | 114 | 115 | 116 | Download file 117 | 118 | - 119 | Delete file 121 | 1): ?> 122 | - 123 | "> Delete all files 124 | 125 | 126 |
127 |
128 |
129 |
130 | 131 | 132 | 133 | 134 | 158 | 159 | --------------------------------------------------------------------------------