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

XmlLogger::getOutFilename()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
     *  Constructs a new BuildListener that logs build events to an XML file.
141
     */
142
    public function __construct(Clock $clock = null)
143
    {
144
        if (null === $clock) {
145
            $this->clock = new DefaultClock();
146
        } else {
147
            $this->clock = $clock;
148
        }
149
        $this->doc = new DOMDocument('1.0', 'UTF-8');
150
        $this->doc->formatOutput = true;
151
    }
152
153
    /**
154
     * Fired when the build starts, this builds the top-level element for the
155
     * document and remembers the time of the start of the build.
156
     *
157
     * @param BuildEvent $event ignored
158
     */
159
    public function buildStarted(BuildEvent $event)
160
    {
161
        $this->buildTimerStart = $this->clock->getCurrentTime();
162
        $this->buildElement = $this->doc->createElement(XmlLogger::BUILD_TAG);
163
        $this->elementStack[] = $this->buildElement;
164
        $this->timesStack[] = $this->buildTimerStart;
165
    }
166
167
    /**
168
     * Fired when the build finishes, this adds the time taken and any
169
     * error stacktrace to the build element and writes the document to disk.
170
     *
171
     * @param BuildEvent $event An event with any relevant extra information.
172
     *                          Will not be <code>null</code>.
173
     *
174
     * @throws BuildException
175
     */
176
    public function buildFinished(BuildEvent $event)
177
    {
178
        $xslUri = $event->getProject()->getProperty('phing.XmlLogger.stylesheet.uri');
179
        if (null === $xslUri) {
180
            $xslUri = '';
181
        }
182
183
        if ('' !== $xslUri) {
184
            $xslt = $this->doc->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="' . $xslUri . '"');
185
            $this->doc->appendChild($xslt);
186
        }
187
188
        $elapsedTime = $this->clock->getCurrentTime() - $this->buildTimerStart;
189
190
        $this->buildElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
191
192
        if (null != $event->getException()) {
193
            $this->buildElement->setAttribute(XmlLogger::ERROR_ATTR, $event->getException()->getMessage());
194
            $errText = $this->doc->createCDATASection($event->getException()->getTraceAsString());
195
            $stacktrace = $this->doc->createElement(XmlLogger::STACKTRACE_TAG);
196
            $stacktrace->appendChild($errText);
197
            $this->buildElement->appendChild($stacktrace);
198
        }
199
200
        $this->doc->appendChild($this->buildElement);
201
202
        $outFilename = $event->getProject()->getProperty('XmlLogger.file');
203
        if (null === $outFilename) {
204
            $outFilename = 'log.xml';
205
        }
206
207
        $stream = $this->getOut();
208
209
        try {
210
            if (null === $stream) {
211
                $stream = new FileOutputStream($outFilename);
212
            }
213
214
            // Yes, we could just stream->write() but this will eventually be the better
215
            // way to do this (when we need to worry about charset conversions.
216
            $writer = new OutputStreamWriter($stream);
217
            $writer->write($this->doc->saveXML());
218
            $writer->close();
219
        } catch (IOException $exc) {
220
            try {
221
                $stream->close(); // in case there is a stream open still ...
222
            } catch (Exception $x) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
223
            }
224
225
            throw new BuildException('Unable to write log file.', $exc);
226
        }
227
228
        // cleanup:remove the buildElement
229
        $this->buildElement = null;
230
231
        array_pop($this->elementStack);
232
        array_pop($this->timesStack);
233
    }
234
235
    /**
236
     * Fired when a target starts building, remembers the current time and the name of the target.
237
     *
238
     * @param BuildEvent $event An event with any relevant extra information.
239
     *                          Will not be <code>null</code>.
240
     */
241
    public function targetStarted(BuildEvent $event)
242
    {
243
        $target = $event->getTarget();
244
245
        $targetElement = $this->doc->createElement(XmlLogger::TARGET_TAG);
246
        $targetElement->setAttribute(XmlLogger::NAME_ATTR, $target->getName());
247
248
        $this->timesStack[] = $this->clock->getCurrentTime();
249
        $this->elementStack[] = $targetElement;
250
    }
251
252
    /**
253
     * Fired when a target finishes building, this adds the time taken
254
     * to the appropriate target element in the log.
255
     *
256
     * @param BuildEvent $event An event with any relevant extra information.
257
     *                          Will not be <code>null</code>.
258
     */
259
    public function targetFinished(BuildEvent $event)
260
    {
261
        $targetTimerStart = array_pop($this->timesStack);
262
        $targetElement = array_pop($this->elementStack);
263
264
        $elapsedTime = $this->clock->getCurrentTime() - $targetTimerStart;
265
        $targetElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
266
267
        $parentElement = $this->elementStack[count($this->elementStack) - 1];
268
        $parentElement->appendChild($targetElement);
269
    }
270
271
    /**
272
     * Fired when a task starts building, remembers the current time and the name of the task.
273
     *
274
     * @param BuildEvent $event An event with any relevant extra information.
275
     *                          Will not be <code>null</code>.
276
     */
277
    public function taskStarted(BuildEvent $event)
278
    {
279
        $task = $event->getTask();
280
281
        $taskElement = $this->doc->createElement(XmlLogger::TASK_TAG);
282
        $taskElement->setAttribute(XmlLogger::NAME_ATTR, $task->getTaskName());
283
        $taskElement->setAttribute(XmlLogger::LOCATION_ATTR, (string) $task->getLocation());
284
285
        $this->timesStack[] = $this->clock->getCurrentTime();
286
        $this->elementStack[] = $taskElement;
287
    }
288
289
    /**
290
     * Fired when a task finishes building, this adds the time taken
291
     * to the appropriate task element in the log.
292
     *
293
     * @param BuildEvent $event An event with any relevant extra information.
294
     *                          Will not be <code>null</code>.
295
     */
296
    public function taskFinished(BuildEvent $event)
297
    {
298
        $taskTimerStart = array_pop($this->timesStack);
299
        $taskElement = array_pop($this->elementStack);
300
301
        $elapsedTime = $this->clock->getCurrentTime() - $taskTimerStart;
302
        $taskElement->setAttribute(XmlLogger::TIME_ATTR, DefaultLogger::formatTime($elapsedTime));
303
304
        $parentElement = $this->elementStack[count($this->elementStack) - 1];
305
        $parentElement->appendChild($taskElement);
306
    }
307
308
    /**
309
     * Fired when a message is logged, this adds a message element to the
310
     * most appropriate parent element (task, target or build) and records
311
     * the priority and text of the message.
312
     *
313
     * @param BuildEvent $event An event with any relevant extra information.
314
     *              Will not be <code>null</code>.
315
     */
316
    public function messageLogged(BuildEvent $event)
317
    {
318
        $priority = $event->getPriority();
319
320
        if ($priority > $this->msgOutputLevel) {
321
            return;
322
        }
323
324
        $messageElement = $this->doc->createElement(XmlLogger::MESSAGE_TAG);
325
326
        switch ($priority) {
327
            case Project::MSG_ERR:
328
                $name = 'error';
329
330
                break;
331
332
            case Project::MSG_WARN:
333
                $name = 'warn';
334
335
                break;
336
337
            case Project::MSG_INFO:
338
                $name = 'info';
339
340
                break;
341
342
            default:
343
                $name = 'debug';
344
345
                break;
346
        }
347
348
        $messageElement->setAttribute(XmlLogger::PRIORITY_ATTR, $name);
349
350
        if (function_exists('mb_convert_encoding')) {
351
            $messageConverted = mb_convert_encoding($event->getMessage(), 'UTF-8');
352
        } else {
353
            $messageConverted = utf8_encode($event->getMessage());
354
        }
355
356
        $messageText = $this->doc->createCDATASection($messageConverted);
0 ignored issues
show
Bug introduced by
It seems like $messageConverted can also be of type array; however, parameter $data of DOMDocument::createCDATASection() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

356
        $messageText = $this->doc->createCDATASection(/** @scrutinizer ignore-type */ $messageConverted);
Loading history...
357
358
        $messageElement->appendChild($messageText);
359
360
        if (!empty($this->elementStack)) {
361
            $this->elementStack[count($this->elementStack) - 1]->appendChild($messageElement);
362
        }
363
    }
364
365
    /**
366
     *  Set the msgOutputLevel this logger is to respond to.
367
     *
368
     *  Only messages with a message level lower than or equal to the given
369
     *  level are output to the log.
370
     *
371
     *  <p> Constants for the message levels are in Project.php. The order of
372
     *  the levels, from least to most verbose, is:
373
     *
374
     *  <ul>
375
     *    <li>Project::MSG_ERR</li>
376
     *    <li>Project::MSG_WARN</li>
377
     *    <li>Project::MSG_INFO</li>
378
     *    <li>Project::MSG_VERBOSE</li>
379
     *    <li>Project::MSG_DEBUG</li>
380
     *  </ul>
381
     *
382
     *  The default message level for DefaultLogger is Project::MSG_ERR.
383
     *
384
     * @param int $level the logging level for the logger
385
     *
386
     * @see   BuildLogger#setMessageOutputLevel()
387
     */
388
    public function setMessageOutputLevel($level)
389
    {
390
        $this->msgOutputLevel = (int) $level;
391
    }
392
393
    /**
394
     * Sets the output stream.
395
     *
396
     * @see BuildLogger::setOutputStream()
397
     */
398
    public function setOutputStream(OutputStream $output)
399
    {
400
        $this->out = $output;
401
    }
402
403
    /**
404
     * Sets the error stream.
405
     *
406
     * @see BuildLogger::setErrorStream()
407
     */
408
    public function setErrorStream(OutputStream $err)
409
    {
410
        $this->err = $err;
411
    }
412
413
    /**
414
     * Sets this logger to produce emacs (and other editor) friendly output.
415
     *
416
     * @param bool $emacsMode true if output is to be unadorned so that emacs and other editors
417
     *                        can parse files names, etc
418
     */
419
    public function setEmacsMode($emacsMode)
420
    {
421
    }
422
423
    /**
424
     * @return DOMDocument
425
     */
426
    public function getDoc()
427
    {
428
        return $this->doc;
429
    }
430
431
    /**
432
     * @return int
433
     */
434
    public function getBuildTimerStart()
435
    {
436
        return $this->buildTimerStart;
437
    }
438
439
    /**
440
     * @return DOMElement
441
     */
442
    public function getBuildElement()
443
    {
444
        return $this->buildElement;
445
    }
446
447
    public function setBuildElement($elem)
448
    {
449
        $this->buildElement = $elem;
450
    }
451
452
    public function &getElementStack(): array
453
    {
454
        return $this->elementStack;
455
    }
456
457
    public function &getTimesStack(): array
458
    {
459
        return $this->timesStack;
460
    }
461
462
    /**
463
     * @return int
464
     */
465
    public function getMsgOutputLevel()
466
    {
467
        return $this->msgOutputLevel;
468
    }
469
470
    /**
471
     * @return OutputStream
472
     */
473
    public function getOut()
474
    {
475
        return $this->out;
476
    }
477
478
    /**
479
     * @return OutputStream
480
     */
481
    public function getErr()
482
    {
483
        return $this->err;
484
    }
485
}
486