Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}