Passed
Push — master ( f5d0a9...f61cc5 )
by Andreas
25:28
created

midcom_debug::log_php_error()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 5
ccs 0
cts 4
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use Symfony\Component\VarDumper\Cloner\VarCloner;
10
use Symfony\Component\VarDumper\Dumper\CliDumper;
11
use Monolog\Logger;
12
13
/**
14
 * This is a debugger class.
15
 *
16
 * Helps in debugging your code. It lets you decide which messages are logged into the
17
 * logfile by setting loglevels for the debugger and for each message.
18
 *
19
 * There are five loglevel constants you can use when setting the loglevel or when
20
 * logging messages:
21
 *
22
 * - MIDCOM_LOG_DEBUG
23
 * - MIDCOM_LOG_INFO
24
 * - MIDCOM_LOG_WARN
25
 * - MIDCOM_LOG_ERROR
26
 * - MIDCOM_LOG_CRIT
27
 *
28
 * @package midcom
29
 */
30
class midcom_debug
31
{
32
    /**
33
     * Current loglevel
34
     *
35
     * @var int
36
     */
37
    private $_loglevel;
38
39
    /**
40
     * Map to Monolog levels
41
     *
42
     * @var array
43
     */
44
    private $level_map = [
45
        MIDCOM_LOG_DEBUG => Logger::DEBUG,
46
        MIDCOM_LOG_INFO  => Logger::INFO,
47
        MIDCOM_LOG_WARN  => Logger::WARNING,
48
        MIDCOM_LOG_ERROR => Logger::ERROR,
49
        MIDCOM_LOG_CRIT  => Logger::CRITICAL
50
    ];
51
52
    /**
53
     * @var Logger
54
     */
55
    private $logger;
56
57
    /**
58
     * Standard constructor
59
     */
60
    public function __construct(Logger $logger)
61
    {
62
        $this->logger = $logger;
63
        $this->_loglevel = midcom::get()->config->get('log_level');
64
    }
65
66
    /**
67
     * Set log level
68
     *
69
     * @param int $loglevel        New log level
70
     */
71
    public function set_loglevel($loglevel)
72
    {
73
        $this->_loglevel = $loglevel;
74
    }
75
76
    /**
77
     * Get log level
78
     */
79
    public function get_loglevel() : int
80
    {
81
        return $this->_loglevel;
82
    }
83
84
    public function log_php_error($loglevel)
85
    {
86
        $error = error_get_last();
87
        if (!empty($error['message'])) {
88
            $this->log('Last PHP error was: ' . $error['message'], $loglevel);
89
        }
90
    }
91
92
    /**
93
     * Log a message
94
     *
95
     * @param string $message    The message to be logged
96
     * @param int $loglevel        The log level
97
     */
98 737
    public function log($message, $loglevel = MIDCOM_LOG_DEBUG)
99
    {
100 737
        if (!$this->check_level($loglevel)) {
101
            return;
102
        }
103
104
        $context = [
105 737
            'caller' => $this->_get_caller()
106
        ];
107 737
        if (function_exists('xdebug_memory_usage')) {
108 737
            static $lastmem = 0;
109 737
            $context['time'] = xdebug_time_index();
110 737
            $context['curmem'] = xdebug_memory_usage();
111 737
            $context['delta'] = $context['curmem'] - $lastmem;
112 737
            $lastmem = $context['curmem'];
113
        }
114
115 737
        $level = $this->level_map[$loglevel] ?? $loglevel;
116 737
        $this->logger->addRecord($level, trim($message), $context);
117 737
    }
118
119 737
    private function check_level(int $loglevel) : bool
120
    {
121 737
        return $this->_loglevel >= $loglevel;
122
    }
123
124 737
    private function _get_caller() : string
125
    {
126 737
        $return = '';
127 737
        $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
128
129 737
        while ($bt) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bt of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
130 737
            $caller = array_shift($bt);
131 737
            if (   !array_key_exists('class', $caller)
132 737
                || $caller['class'] != midcom_debug::class) {
133 737
                if (   !array_key_exists('function', $bt[0])
134 737
                    || $bt[0]['function'] != 'require') {
135 737
                    $caller = array_shift($bt);
136
                }
137
138 737
                break;
139
            }
140
        }
141
142 737
        if (array_key_exists('class', $caller)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $caller does not seem to be defined for all execution paths leading up to this point.
Loading history...
143 737
            $return .= $caller['class'] . '::';
144
        }
145 737
        if (   array_key_exists('function', $caller)
146 737
            && substr($caller['function'], 0, 6) != 'debug_') {
147 737
            $return .= $caller['function'];
148
        } else {
149
            $return .= $caller['file'] . ' (' . $caller['line']. ')';
150
        }
151 737
        return $return;
152
    }
153
154
    /**
155
     * Dump a variable
156
     *
157
     * @param string $message    The message to be logged
158
     * @param mixed $variable    The variable to be logged
159
     * @param int $loglevel        The log level
160
     */
161 22
    public function print_r($message, $variable, $loglevel = MIDCOM_LOG_DEBUG)
162
    {
163 22
        if (!$this->check_level($loglevel)) {
164
            return;
165
        }
166
167 22
        $cloner = new VarCloner();
168 22
        $dumper = new CliDumper();
169
170 22
        $varstring = $dumper->dump($cloner->cloneVar($variable), true);
171
172 22
        $this->log(trim($message) . ' ' . $varstring, $loglevel);
173 22
    }
174
175
    /**
176
     * Dump stack trace, only working when XDebug is present.
177
     *
178
     * @param string $message    The message to be logged
179
     * @param int $loglevel        The log level
180
     * @link http://www.xdebug.org/ xdebug.org
181
     */
182 25
    public function print_function_stack($message, $loglevel = MIDCOM_LOG_DEBUG)
183
    {
184 25
        if (!$this->check_level($loglevel)) {
185
            return;
186
        }
187
188 25
        if (function_exists('xdebug_get_function_stack')) {
189 25
            $stack = array_reverse(xdebug_get_function_stack());
190
        } else {
191
            $stack = debug_backtrace(0);
192
        }
193
        //the last two levels are already inside the debugging system, so skip those
194 25
        array_shift($stack);
195 25
        array_shift($stack);
196
197 25
        $stacktrace = "";
198 25
        foreach ($stack as $number => $frame) {
199 25
            $stacktrace .= $number + 1;
200 25
            if (isset($frame['file'])) {
201 25
                $stacktrace .= ": {$frame['file']}:{$frame['line']} ";
202
            }
203 25
            if (array_key_exists('class', $frame)) {
204 25
                if (!array_key_exists('function', $frame)) {
205
                    // workaround for what is most likely a bug in xdebug 2.4.rc3 and/or PHP 7.0.3
206
                    continue;
207
                }
208
209 25
                $stacktrace .= "{$frame['class']}::{$frame['function']}";
210 25
            } elseif (array_key_exists('function', $frame)) {
211 25
                $stacktrace .= $frame['function'];
212
            } else {
213
                $stacktrace .= 'require, include or eval';
214
            }
215 25
            $stacktrace .= "\n";
216
        }
217
218 25
        $this->log(trim($message) . "\n{$stacktrace}", $loglevel);
219 25
    }
220
221
    /**
222
     * Dump a variable's type
223
     *
224
     * @param string $message    The message to be logged
225
     * @param mixed $variable    The variable of which the type should be logged
226
     * @param int $loglevel        The log level
227
     */
228
    public function print_type($message, $variable, $loglevel = MIDCOM_LOG_DEBUG)
229
    {
230
        if (!$this->check_level($loglevel)) {
231
            return;
232
        }
233
234
        $type = gettype($variable);
235
        if ($type == "object") {
236
            $type .= ": " . get_class($variable);
237
        }
238
239
        $this->log(trim($message) . "\nVariable Type: $type", $loglevel);
240
    }
241
242
    /**
243
     * Dump the current memory usage and the delta to the last call of this function.
244
     * Useful for tracking memory leaks.
245
     *
246
     * Format will be:
247
     *
248
     * $curmem (delta $delta): $message
249
     *
250
     * @param string $message    The message to be logged
251
     * @param int $loglevel        The log level
252
     */
253
    public function print_dump_mem($message, $loglevel)
254
    {
255
        if (!$this->check_level($loglevel)) {
256
            return;
257
        }
258
259
        static $lastmem = 0;
260
        $curmem = memory_get_usage();
261
        $delta = $curmem - $lastmem;
262
        $lastmem = $curmem;
263
264
        $curmem = str_pad(number_format($curmem), 10, " ", STR_PAD_LEFT);
265
        $delta = str_pad(number_format($delta), 10, " ", STR_PAD_LEFT);
266
        $this->log("{$curmem} (delta {$delta}): {$message}", $loglevel);
267
    }
268
}
269