Passed
Push — main ( 221f6d...f8c128 )
by Siad
05:28
created

src/Phing/Listener/XmlLogger.php (2 issues)

Labels
Severity
1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Listener;
22
23
use DOMDocument;
24
use DOMElement;
25
use Exception;
26
use Phing\Exception\BuildException;
27
use Phing\Io\FileOutputStream;
28
use Phing\Io\IOException;
29
use Phing\Io\OutputStream;
30
use Phing\Io\OutputStreamWriter;
31
use Phing\Project;
32
use Phing\Util\Clock;
33
use Phing\Util\DefaultClock;
34
35
/**
36
 * Generates a file in the current directory with
37
 * an XML description of what happened during a build.
38
 * The default filename is "log.xml", but this can be overridden
39
 * with the property <code>XmlLogger.file</code>.
40
 *
41
 * @author  Michiel Rook <[email protected]>
42
 */
43
class XmlLogger implements BuildLogger
44
{
45
    /**
46
     * XML element name for a build.
47
     */
48
    public const BUILD_TAG = 'build';
49
50
    /**
51
     * XML element name for a target.
52
     */
53
    public const TARGET_TAG = 'target';
54
55
    /**
56
     * XML element name for a task.
57
     */
58
    public const TASK_TAG = 'task';
59
60
    /**
61
     * XML element name for a message.
62
     */
63
    public const MESSAGE_TAG = 'message';
64
65
    /**
66
     * XML attribute name for a name.
67
     */
68
    public const NAME_ATTR = 'name';
69
70
    /**
71
     * XML attribute name for a time.
72
     */
73
    public const TIME_ATTR = 'time';
74
75
    /**
76
     * XML attribute name for a message priority.
77
     */
78
    public const PRIORITY_ATTR = 'priority';
79
80
    /**
81
     * XML attribute name for a file location.
82
     */
83
    public const LOCATION_ATTR = 'location';
84
85
    /**
86
     * XML attribute name for an error description.
87
     */
88
    public const ERROR_ATTR = 'error';
89
90
    /**
91
     * XML element name for a stack trace.
92
     */
93
    public const STACKTRACE_TAG = 'stacktrace';
94
    /**
95
     * @var Clock|DefaultClock
96
     */
97
    protected $clock;
98
99
    /**
100
     * @var DOMDocument the XML document created by this logger
101
     */
102
    private $doc;
103
104
    /**
105
     * @var int start time for entire build
106
     */
107
    private $buildTimerStart = 0;
108
109
    /**
110
     * @var DOMElement Top-level (root) build element
111
     */
112
    private $buildElement;
113
114
    /**
115
     * @var array DOMElement[] The parent of the element being processed
116
     */
117
    private $elementStack = [];
118
119
    /**
120
     * @var array int[] Array of millisecond times for the various elements being processed
121
     */
122
    private $timesStack = [];
123
124
    /**
125
     * @var int
126
     */
127
    private $msgOutputLevel = Project::MSG_DEBUG;
128
129
    /**
130
     * @var OutputStream stream to use for standard output
131
     */
132
    private $out;
133
134
    /**
135
     * @var OutputStream stream to use for error output
136
     */
137
    private $err;
138
139
    /**
140
     * @var string name of filename to create
141
     */
142
    private $outFilename;
143
144
    /**
145
     *  Constructs a new BuildListener that logs build events to an XML file.
146
     */
147
    public function __construct(Clock $clock = null)
148
    {
149
        if (null === $clock) {
150
            $this->clock = new DefaultClock();
151
        } else {
152
            $this->clock = $clock;
153
        }
154
        $this->doc = new DOMDocument('1.0', 'UTF-8');
155
        $this->doc->formatOutput = true;
156
    }
157
158
    /**
159
     * Fired when the build starts, this builds the top-level element for the
160
     * document and remembers the time of the start of the build.
161
     *
162
     * @param BuildEvent ignored
0 ignored issues
show
The type Phing\Listener\ignored was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
163
     */
164
    public function buildStarted(BuildEvent $event)
165
    {
166
        $this->buildTimerStart = $this->clock->getCurrentTime();
167
        $this->buildElement = $this->doc->createElement(XmlLogger::BUILD_TAG);
168
        $this->elementStack[] = $this->buildElement;
169
        $this->timesStack[] = $this->buildTimerStart;
170
    }
171
172
    /**
173
     * Fired when the build finishes, this adds the time taken and any
174
     * error stacktrace to the build element and writes the document to disk.
175
     *
176
     * @param BuildEvent $event An event with any relevant extra information.
177
     *                          Will not be <code>null</code>.
178
     *
179
     * @throws BuildException
180
     */
181
    public function buildFinished(BuildEvent $event)
182
    {
183
        $xslUri = $event->getProject()->getProperty('phing.XmlLogger.stylesheet.uri');
184
        if (null === $xslUri) {
185
            $xslUri = '';
186
        }
187
188
        if ('' !== $xslUri) {
189
            $xslt = $this->doc->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="' . $xslUri . '"');
190
            $this->doc->appendChild($xslt);
191
        }
192
193
        $elapsedTime = $this->clock->getCurrentTime() - $this->buildTimerStart;
194
195
        $this->buildElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
196
197
        if (null != $event->getException()) {
198
            $this->buildElement->setAttribute(XmlLogger::ERROR_ATTR, $event->getException()->getMessage());
199
            $errText = $this->doc->createCDATASection($event->getException()->getTraceAsString());
200
            $stacktrace = $this->doc->createElement(XmlLogger::STACKTRACE_TAG);
201
            $stacktrace->appendChild($errText);
202
            $this->buildElement->appendChild($stacktrace);
203
        }
204
205
        $this->doc->appendChild($this->buildElement);
206
207
        $outFilename = $event->getProject()->getProperty('XmlLogger.file');
208
        if (null == $outFilename) {
0 ignored issues
show
It seems like you are loosely comparing $outFilename of type mixed|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
209
            $outFilename = 'log.xml';
210
        }
211
212
        $stream = $this->getOut();
213
214
        try {
215
            if (null === $stream) {
216
                $stream = new FileOutputStream($outFilename);
217
            }
218
219
            // Yes, we could just stream->write() but this will eventually be the better
220
            // way to do this (when we need to worry about charset conversions.
221
            $writer = new OutputStreamWriter($stream);
222
            $writer->write($this->doc->saveXML());
223
            $writer->close();
224
        } catch (IOException $exc) {
225
            try {
226
                $stream->close(); // in case there is a stream open still ...
227
            } catch (Exception $x) {
228
            }
229
230
            throw new BuildException('Unable to write log file.', $exc);
231
        }
232
233
        // cleanup:remove the buildElement
234
        $this->buildElement = null;
235
236
        array_pop($this->elementStack);
237
        array_pop($this->timesStack);
238
    }
239
240
    /**
241
     * Fired when a target starts building, remembers the current time and the name of the target.
242
     *
243
     * @param BuildEvent $event An event with any relevant extra information.
244
     *                          Will not be <code>null</code>.
245
     */
246
    public function targetStarted(BuildEvent $event)
247
    {
248
        $target = $event->getTarget();
249
250
        $targetElement = $this->doc->createElement(XmlLogger::TARGET_TAG);
251
        $targetElement->setAttribute(XmlLogger::NAME_ATTR, $target->getName());
252
253
        $this->timesStack[] = $this->clock->getCurrentTime();
254
        $this->elementStack[] = $targetElement;
255
    }
256
257
    /**
258
     * Fired when a target finishes building, this adds the time taken
259
     * to the appropriate target element in the log.
260
     *
261
     * @param BuildEvent $event An event with any relevant extra information.
262
     *                          Will not be <code>null</code>.
263
     */
264
    public function targetFinished(BuildEvent $event)
265
    {
266
        $targetTimerStart = array_pop($this->timesStack);
267
        $targetElement = array_pop($this->elementStack);
268
269
        $elapsedTime = $this->clock->getCurrentTime() - $targetTimerStart;
270
        $targetElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
271
272
        $parentElement = $this->elementStack[count($this->elementStack) - 1];
273
        $parentElement->appendChild($targetElement);
274
    }
275
276
    /**
277
     * Fired when a task starts building, remembers the current time and the name of the task.
278
     *
279
     * @param BuildEvent $event An event with any relevant extra information.
280
     *                          Will not be <code>null</code>.
281
     */
282
    public function taskStarted(BuildEvent $event)
283
    {
284
        $task = $event->getTask();
285
286
        $taskElement = $this->doc->createElement(XmlLogger::TASK_TAG);
287
        $taskElement->setAttribute(XmlLogger::NAME_ATTR, $task->getTaskName());
288
        $taskElement->setAttribute(XmlLogger::LOCATION_ATTR, (string) $task->getLocation());
289
290
        $this->timesStack[] = $this->clock->getCurrentTime();
291
        $this->elementStack[] = $taskElement;
292
    }
293
294
    /**
295
     * Fired when a task finishes building, this adds the time taken
296
     * to the appropriate task element in the log.
297
     *
298
     * @param BuildEvent $event An event with any relevant extra information.
299
     *                          Will not be <code>null</code>.
300
     */
301
    public function taskFinished(BuildEvent $event)
302
    {
303
        $taskTimerStart = array_pop($this->timesStack);
304
        $taskElement = array_pop($this->elementStack);
305
306
        $elapsedTime = $this->clock->getCurrentTime() - $taskTimerStart;
307
        $taskElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
308
309
        $parentElement = $this->elementStack[count($this->elementStack) - 1];
310
        $parentElement->appendChild($taskElement);
311
    }
312
313
    /**
314
     * Fired when a message is logged, this adds a message element to the
315
     * most appropriate parent element (task, target or build) and records
316
     * the priority and text of the message.
317
     *
318
     * @param BuildEvent An event with any relevant extra information.
319
     *              Will not be <code>null</code>.
320
     */
321
    public function messageLogged(BuildEvent $event)
322
    {
323
        $priority = $event->getPriority();
324
325
        if ($priority > $this->msgOutputLevel) {
326
            return;
327
        }
328
329
        $messageElement = $this->doc->createElement(XmlLogger::MESSAGE_TAG);
330
331
        switch ($priority) {
332
            case Project::MSG_ERR:
333
                $name = 'error';
334
335
                break;
336
337
            case Project::MSG_WARN:
338
                $name = 'warn';
339
340
                break;
341
342
            case Project::MSG_INFO:
343
                $name = 'info';
344
345
                break;
346
347
            default:
348
                $name = 'debug';
349
350
                break;
351
        }
352
353
        $messageElement->setAttribute(XmlLogger::PRIORITY_ATTR, $name);
354
355
        if (function_exists('mb_convert_encoding')) {
356
            $messageConverted = mb_convert_encoding($event->getMessage(), 'UTF-8');
357
        } else {
358
            $messageConverted = utf8_encode($event->getMessage());
359
        }
360
361
        $messageText = $this->doc->createCDATASection($messageConverted);
362
363
        $messageElement->appendChild($messageText);
364
365
        if (!empty($this->elementStack)) {
366
            $this->elementStack[count($this->elementStack) - 1]->appendChild($messageElement);
367
        }
368
    }
369
370
    /**
371
     *  Set the msgOutputLevel this logger is to respond to.
372
     *
373
     *  Only messages with a message level lower than or equal to the given
374
     *  level are output to the log.
375
     *
376
     *  <p> Constants for the message levels are in Project.php. The order of
377
     *  the levels, from least to most verbose, is:
378
     *
379
     *  <ul>
380
     *    <li>Project::MSG_ERR</li>
381
     *    <li>Project::MSG_WARN</li>
382
     *    <li>Project::MSG_INFO</li>
383
     *    <li>Project::MSG_VERBOSE</li>
384
     *    <li>Project::MSG_DEBUG</li>
385
     *  </ul>
386
     *
387
     *  The default message level for DefaultLogger is Project::MSG_ERR.
388
     *
389
     * @param int $level the logging level for the logger
390
     *
391
     * @see   BuildLogger#setMessageOutputLevel()
392
     */
393
    public function setMessageOutputLevel($level)
394
    {
395
        $this->msgOutputLevel = (int) $level;
396
    }
397
398
    /**
399
     * Sets the output stream.
400
     *
401
     * @see   BuildLogger#setOutputStream()
402
     */
403
    public function setOutputStream(OutputStream $output)
404
    {
405
        $this->out = $output;
406
    }
407
408
    /**
409
     * Sets the error stream.
410
     *
411
     * @see   BuildLogger#setErrorStream()
412
     */
413
    public function setErrorStream(OutputStream $err)
414
    {
415
        $this->err = $err;
416
    }
417
418
    /**
419
     * Sets this logger to produce emacs (and other editor) friendly output.
420
     *
421
     * @param bool $emacsMode true if output is to be unadorned so that emacs and other editors
422
     *                        can parse files names, etc
423
     */
424
    public function setEmacsMode($emacsMode)
425
    {
426
    }
427
428
    /**
429
     * @return DOMDocument
430
     */
431
    public function getDoc()
432
    {
433
        return $this->doc;
434
    }
435
436
    /**
437
     * @return int
438
     */
439
    public function getBuildTimerStart()
440
    {
441
        return $this->buildTimerStart;
442
    }
443
444
    /**
445
     * @return DOMElement
446
     */
447
    public function getBuildElement()
448
    {
449
        return $this->buildElement;
450
    }
451
452
    public function setBuildElement($elem)
453
    {
454
        $this->buildElement = $elem;
455
    }
456
457
    public function &getElementStack(): array
458
    {
459
        return $this->elementStack;
460
    }
461
462
    public function &getTimesStack(): array
463
    {
464
        return $this->timesStack;
465
    }
466
467
    /**
468
     * @return int
469
     */
470
    public function getMsgOutputLevel()
471
    {
472
        return $this->msgOutputLevel;
473
    }
474
475
    /**
476
     * @return OutputStream
477
     */
478
    public function getOut()
479
    {
480
        return $this->out;
481
    }
482
483
    /**
484
     * @return OutputStream
485
     */
486
    public function getErr()
487
    {
488
        return $this->err;
489
    }
490
491
    /**
492
     * @return string
493
     */
494
    public function getOutFilename()
495
    {
496
        return $this->outFilename;
497
    }
498
}
499