XMLLogger   A
last analyzed

Complexity

Total Complexity 30

Size/Duplication

Total Lines 186
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 30
eloc 72
c 2
b 0
f 0
dl 0
loc 186
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A closeLogfile() 0 7 3
A createLogfile() 0 14 2
A openLogfile() 0 12 4
A __construct() 0 3 1
C log() 0 61 15
A reset() 0 3 1
A addChildToDoc() 0 8 3
A setXSLFile() 0 3 1
1
<?php
2
declare(strict_types=1);
3
4
namespace SKien\XLogger;
5
6
use Psr\Log\LogLevel;
7
8
/**
9
 * PSR-3 compliant logger for Output to XML file.
10
 *
11
 * This class creates a XML file that can be transformed with given xsl into HTML
12
 *
13
 * #### Structure of the XML-File
14
 * ```xml
15
 *  <log>
16
 *      <item>
17
 *          <timestamp>2020-07-21 18:22:58</timestamp>
18
 *          <user>SKien</user>
19
 *          <caller>/packages/XLogger/XLogTest.php (62)</caller>
20
 *          <level>ERROR</level>
21
 *          <message>bad conditions :-(</message>
22
 *      </item>
23
 *      <item>
24
 *          ...
25
 *      </item>
26
 *  </log>
27
 * ```
28
 *
29
 * @package XLogger
30
 * @author Stefanius <[email protected]>
31
 * @copyright MIT License - see the LICENSE file for details
32
 */
33
class XMLLogger extends XLogger
34
{
35
    /** @var  \DOMDocument dom document for logging    */
36
    protected ?\DOMDocument  $xmlDoc = null;
37
    /** @var  \DOMElement root element of the dom document    */
38
    protected ?\DOMElement  $xmlRoot = null;
39
    /** @var string fullpath to XSL file for HTML transformation of the XML log     */
40
    protected string $strXSLFile = '';
41
42
    /**
43
     * Init logging level and remote username (if set).
44
     * @see XLogger::setLogLevel()
45
     * @param string $level the min. `LogLevel` to be logged
46
     */
47
    public function __construct(string $level = LogLevel::DEBUG)
48
    {
49
        parent::__construct($level);
50
    }
51
52
    /**
53
     * Set XSL file to transform the log to HTML.
54
     * The specified XSL must be: <ul>
55
     * <li> located in the same directory as the log file </li>
56
     * <li> contain relative path to the log file </li>
57
     * <li> contain absolute path </li></ul>
58
     * @param string $strXSLFilen
59
     * @return void
60
     */
61
    public function setXSLFile(string $strXSLFilen) : void
62
    {
63
        $this->strXSLFile = $strXSLFilen;
64
    }
65
66
    /**
67
     * Logs with an arbitrary level.
68
     * @param string    $level
69
     * @param mixed     $message
70
     * @param mixed[]   $context
71
     * @return void
72
     * @throws \Psr\Log\InvalidArgumentException
73
     */
74
    public function log($level, $message, array $context = array()) : void
75
    {
76
        // check, if requested level should be logged
77
        // causes InvalidArgumentException in case of unknown level.
78
        if ($this->logLevel($level)) {
79
            // First open the logfile.
80
            $this->openLogfile();
81
            if (!$this->xmlDoc || !$this->xmlRoot) {
82
                return;
83
            }
84
85
            $xmlItem = $this->addChildToDoc('item', '', $this->xmlRoot);
86
87
            $this->addChildToDoc('timestamp', date('Y-m-d H:i:s'), $xmlItem);
88
            // IP adress
89
            if (($this->iOptions & self::LOG_IP) != 0) {
90
                $strIP = $_SERVER['REMOTE_ADDR'];
91
                if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
92
                    $strIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
93
                }
94
                $this->addChildToDoc('IP-adress', $strIP, $xmlItem);
95
            }
96
            // user
97
            if (($this->iOptions & self::LOG_USER) != 0) {
98
                $this->addChildToDoc('user', $this->strUser, $xmlItem);
99
            }
100
            // backtrace - caller
101
            if (($this->iOptions & self::LOG_BT) != 0) {
102
                $this->addChildToDoc('caller', $this->getCaller(), $xmlItem);
103
            }
104
            // the message
105
            $strMessage = $this->replaceContext($message, $context);
106
            $this->addChildToDoc('level', strtoupper($level), $xmlItem);
107
            $this->addChildToDoc('message', $strMessage, $xmlItem);
108
            // user agent
109
            if (($this->iOptions & self::LOG_UA) != 0) {
110
                $this->addChildToDoc('useragent', $_SERVER["HTTP_USER_AGENT"], $xmlItem);
111
            }
112
113
            if (count($context) > 0) {
114
                foreach ($context as $key => $value) {
115
                    if ($key == 'exception') {
116
                        $xmlEx = $this->addChildToDoc('exception', '', $xmlItem);
117
                        $this->addChildToDoc('msg', (string)$value, $xmlEx);
118
                        $this->addChildToDoc('class', get_class($value), $xmlEx);
119
                        $aTrace = $value->getTrace();
120
                        foreach ($aTrace as $aTraceItem) {
121
                            $xmlTrace = $this->addChildToDoc('trace', '', $xmlEx);
122
                            foreach ($aTraceItem as $tkey => $tvalue) {
123
                                $this->addChildToDoc($tkey, (string)$tvalue, $xmlTrace);
124
                            }
125
                        }
126
                    } else if (strpos($message, '{' . $key . '}') === false) {
127
                        $xmlContext = $this->addChildToDoc('context', '', $xmlItem);
128
                        $this->addChildToDoc('key', (string)$key, $xmlContext);
129
                        $this->addChildToDoc('value', (string)$value, $xmlContext);
130
                    }
131
                }
132
            }
133
134
            $this->xmlDoc->save($this->getFullpath());
135
        }
136
    }
137
138
    /**
139
     * we just create new XML document
140
     */
141
    public function reset() : void
142
    {
143
        $this->createLogfile();
144
    }
145
146
    /**
147
     * Open the logfile.
148
     * If not opened so far, the file will be opened and
149
     * root element to append new items is set.
150
     * If file does not exist, it will be created.
151
     * @return void
152
     */
153
    protected function openLogfile() : void
154
    {
155
        if (!$this->xmlDoc || !$this->xmlRoot) {
156
            $strFullPath = $this->getFullpath();
157
            if (!file_exists($strFullPath)) {
158
                $this->createLogfile();
159
            } else {
160
                $this->xmlDoc = new \DOMDocument();
161
                $this->xmlDoc->preserveWhiteSpace = false;
162
                $this->xmlDoc->formatOutput = true;
163
                $this->xmlDoc->load($strFullPath);
164
                $this->xmlRoot = $this->xmlDoc->documentElement;
165
                // TODO: check, if XSL set and insert if PI do not exist so far
166
            }
167
        }
168
    }
169
170
    /**
171
     * Create new logfile and insert base XML-structure.
172
     * @return void
173
     */
174
    protected function createLogfile() : void
175
    {
176
        $this->xmlDoc = null;
177
        $this->xmlRoot = null;
178
179
        $this->xmlDoc = new \DOMDocument();
180
        $this->xmlDoc->preserveWhiteSpace = false;
181
        $this->xmlDoc->formatOutput = true;
182
        if (strlen($this->strXSLFile) > 0) {
183
            $xslt = $this->xmlDoc->createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="' . $this->strXSLFile . '"');
184
            $this->xmlDoc->appendChild($xslt);
185
        }
186
        $this->xmlRoot = $this->addChildToDoc('log');
187
        $this->xmlDoc->save($this->getFullpath());
188
    }
189
190
    /**
191
     * Close the logfile
192
     * @return void
193
     */
194
    protected function closeLogfile() : void
195
    {
196
        if ($this->xmlDoc) {
197
            $this->xmlDoc = null;
198
        }
199
        if ($this->xmlRoot) {
200
            $this->xmlRoot = null;
201
        }
202
    }
203
204
    /**
205
     * create new DOMNode and append it to given parent
206
     * @param string $strName
207
     * @param string $strValue
208
     * @param \DOMElement $oParent
209
     * @return \DOMElement
210
     */
211
    public function addChildToDoc(string $strName, string $strValue = '', \DOMElement $oParent = null) : ?\DOMElement
212
    {
213
        $oChild = null;
214
        if ($this->xmlDoc) {
215
            $oChild = $this->xmlDoc->createElement($strName, $strValue);
216
            $oParent ? $oParent->appendChild($oChild) : $this->xmlDoc->appendChild($oChild);
217
        }
218
        return $oChild;
219
    }
220
}
221