Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Task log manager. 19 * 20 * @package core 21 * @category task 22 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core\task; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Task log manager. 31 * 32 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class logmanager { 36 37 /** @var int Do not log anything */ 38 const MODE_NONE = 0; 39 40 /** @var int Log all tasks */ 41 const MODE_ALL = 1; 42 43 /** @var int Only log fails */ 44 const MODE_FAILONLY = 2; 45 46 /** @var int The default chunksize to use in ob_start */ 47 const CHUNKSIZE = 1; 48 49 /** 50 * @var \core\task\task_base The task being logged. 51 */ 52 protected static $task = null; 53 54 /** 55 * @var \stdClass Metadata about the current log 56 */ 57 protected static $taskloginfo = null; 58 59 /** 60 * @var \resource The current filehandle used for logging 61 */ 62 protected static $fh = null; 63 64 /** 65 * @var string The path to the log file 66 */ 67 protected static $logpath = null; 68 69 /** 70 * @var bool Whether the task logger has been registered with the shutdown handler 71 */ 72 protected static $tasklogregistered = false; 73 74 /** 75 * @var int The level of output buffering in place before starting. 76 */ 77 protected static $oblevel = null; 78 79 /** 80 * @var bool Output logged content to screen. 81 */ 82 protected static $outputloggedcontent = true; 83 84 /** 85 * Create a new task logger for the specified task, and prepare for logging. 86 * 87 * @param \core\task\task_base $task The task being run 88 */ 89 public static function start_logging(task_base $task) { 90 global $CFG, $DB; 91 92 if (!self::should_log()) { 93 return; 94 } 95 96 // We register a shutdown handler to ensure that logs causing any failures are correctly disposed of. 97 // Note: This must happen before the per-request directory is requested because the shutdown handler deletes the logfile. 98 if (!self::$tasklogregistered) { 99 \core_shutdown_manager::register_function(function() { 100 // These will only actually do anything if capturing is current active when the thread ended, which 101 // constitutes a failure. 102 \core\task\logmanager::finalise_log(true); 103 }); 104 105 // Create a brand new per-request directory basedir. 106 get_request_storage_directory(true, true); 107 108 self::$tasklogregistered = true; 109 } 110 111 if (self::is_current_output_buffer()) { 112 // We cannot capture when we are already capturing. 113 throw new \coding_exception('Logging is already in progress for task "' . get_class(self::$task) . '". ' . 114 'Nested logging is not supported.'); 115 } 116 117 // Store the initial data about the task and current state. 118 self::$task = $task; 119 self::$taskloginfo = (object) [ 120 'dbread' => $DB->perf_get_reads(), 121 'dbwrite' => $DB->perf_get_writes(), 122 'timestart' => microtime(true), 123 ]; 124 125 // For simplicity's sake we always store logs on disk and flush at the end. 126 self::$logpath = make_request_directory() . DIRECTORY_SEPARATOR . "task.log"; 127 self::$fh = fopen(self::$logpath, 'w+'); 128 129 // Note the level of the current output buffer. 130 // Note: You cannot use ob_get_level() as it will return `1` when the default output buffer is enabled. 131 if ($obstatus = ob_get_status()) { 132 self::$oblevel = $obstatus['level']; 133 } else { 134 self::$oblevel = null; 135 } 136 137 self::$outputloggedcontent = !empty($CFG->task_logtostdout); 138 139 // Start capturing output. 140 ob_start([\core\task\logmanager::class, 'add_line'], self::CHUNKSIZE); 141 } 142 143 /** 144 * Whether logging is possible and should be happening. 145 * 146 * @return bool 147 */ 148 protected static function should_log() : bool { 149 global $CFG; 150 151 // Respect the config setting. 152 if (isset($CFG->task_logmode) && empty($CFG->task_logmode)) { 153 return false; 154 } 155 156 $loggerclass = self::get_logger_classname(); 157 if (empty($loggerclass)) { 158 return false; 159 } 160 161 return $loggerclass::is_configured(); 162 } 163 164 /** 165 * Return the name of the logging class to use. 166 * 167 * @return string 168 */ 169 public static function get_logger_classname() : string { 170 global $CFG; 171 172 if (!empty($CFG->task_log_class)) { 173 // Configuration is present to use an alternative task logging class. 174 return $CFG->task_log_class; 175 } 176 177 // Fall back on the default database logger. 178 return database_logger::class; 179 } 180 181 /** 182 * Whether this task logger has a report available. 183 * 184 * @return bool 185 */ 186 public static function has_log_report() : bool { 187 $loggerclass = self::get_logger_classname(); 188 189 return $loggerclass::has_log_report(); 190 } 191 192 /** 193 * Whether to use the standard settings form. 194 */ 195 public static function uses_standard_settings() : bool { 196 $classname = self::get_logger_classname(); 197 if (!class_exists($classname)) { 198 return false; 199 } 200 201 if (is_a($classname, database_logger::class, true)) { 202 return true; 203 } 204 205 return false; 206 } 207 208 /** 209 * Get any URL available for viewing relevant task log reports. 210 * 211 * @param string $classname The task class to fetch for 212 * @return \moodle_url 213 */ 214 public static function get_url_for_task_class(string $classname) : \moodle_url { 215 $loggerclass = self::get_logger_classname(); 216 217 return $loggerclass::get_url_for_task_class($classname); 218 } 219 220 /** 221 * Whether we are the current log collector. 222 * 223 * @return bool 224 */ 225 protected static function is_current_output_buffer() : bool { 226 if (empty(self::$taskloginfo)) { 227 return false; 228 } 229 230 if ($ob = ob_get_status()) { 231 return 'core\\task\\logmanager::add_line' == $ob['name']; 232 } 233 234 return false; 235 } 236 237 /** 238 * Whether we are capturing at all. 239 * 240 * @return bool 241 */ 242 protected static function is_capturing() : bool { 243 $buffers = ob_get_status(true); 244 foreach ($buffers as $ob) { 245 if ('core\\task\\logmanager::add_line' == $ob['name']) { 246 return true; 247 } 248 } 249 250 return false; 251 } 252 253 /** 254 * Finish writing for the current task. 255 * 256 * @param bool $failed 257 */ 258 public static function finalise_log(bool $failed = false) { 259 global $CFG, $DB, $PERF; 260 261 if (!self::should_log()) { 262 return; 263 } 264 265 if (!self::is_capturing()) { 266 // Not capturing anything. 267 return; 268 } 269 270 // Ensure that all logs are closed. 271 $buffers = ob_get_status(true); 272 foreach (array_reverse($buffers) as $ob) { 273 if (null !== self::$oblevel) { 274 if ($ob['level'] <= self::$oblevel) { 275 // Only close as far as the initial output buffer level. 276 break; 277 } 278 } 279 280 // End and flush this buffer. 281 ob_end_flush(); 282 283 if ('core\\task\\logmanager::add_line' == $ob['name']) { 284 break; 285 } 286 } 287 self::$oblevel = null; 288 289 // Flush any remaining buffer. 290 self::flush(); 291 292 // Close and unset the FH. 293 fclose(self::$fh); 294 self::$fh = null; 295 296 if ($failed || empty($CFG->task_logmode) || self::MODE_ALL == $CFG->task_logmode) { 297 // Finalise the log. 298 $loggerclass = self::get_logger_classname(); 299 $loggerclass::store_log_for_task( 300 self::$task, 301 self::$logpath, 302 $failed, 303 $DB->perf_get_reads() - self::$taskloginfo->dbread, 304 $DB->perf_get_writes() - self::$taskloginfo->dbwrite, 305 self::$taskloginfo->timestart, 306 microtime(true) 307 ); 308 } 309 310 // Tidy up. 311 self::$logpath = null; 312 self::$taskloginfo = null; 313 } 314 315 /** 316 * Flush the current output buffer. 317 * 318 * This function will ensure that we are the current output buffer handler. 319 */ 320 public static function flush() { 321 // We only call ob_flush if the current output buffer belongs to us. 322 if (self::is_current_output_buffer()) { 323 ob_flush(); 324 } 325 } 326 327 /** 328 * Add a log record to the task log. 329 * 330 * @param string $log 331 * @return string 332 */ 333 public static function add_line(string $log) : string { 334 if (empty(self::$taskloginfo)) { 335 return $log; 336 } 337 338 if (empty(self::$fh)) { 339 return $log; 340 } 341 342 if (self::is_current_output_buffer()) { 343 fwrite(self::$fh, $log); 344 } 345 346 if (self::$outputloggedcontent) { 347 return $log; 348 } else { 349 return ''; 350 } 351 } 352 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body