Passed
Push — master ( 70a891...5569dc )
by Stefan
01:45
created

XMLLogger::log()   C

Complexity

Conditions 15
Paths 50

Size

Total Lines 61
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 36
c 2
b 0
f 0
dl 0
loc 61
rs 5.9166
cc 15
nc 50
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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