Completed
Push — master ( f6e48a...088fe8 )
by Andreas
18:26
created

midcom_debug::set_loglevel()   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 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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
12
/**
13
 * This is a debugger class.
14
 *
15
 * Helps in debugging your code. It lets you decide which messages are logged into the
16
 * logfile by setting loglevels for the debugger and for each message.
17
 *
18
 * There are five loglevel constants you can use when setting the loglevel or when
19
 * logging messages:
20
 *
21
 * - MIDCOM_LOG_DEBUG
22
 * - MIDCOM_LOG_INFO
23
 * - MIDCOM_LOG_WARN
24
 * - MIDCOM_LOG_ERROR
25
 * - MIDCOM_LOG_CRIT
26
 *
27
 * This file declares shortcuts like debug_add (see below).
28
 *
29
 * @package midcom
30
 */
31
class midcom_debug
32
{
33
    /**
34
     * Logfile name
35
     *
36
     * @var string
37
     */
38
    private $_filename;
39
40
    /**
41
     * Current loglevel
42
     *
43
     * @var int
44
     */
45
    private $_loglevel;
46
47
    /**
48
     * All available loglevels
49
     *
50
     * @var array
51
     */
52
    private $_loglevels = [
53
        MIDCOM_LOG_DEBUG => "debug",
54
        MIDCOM_LOG_INFO  => "info",
55
        MIDCOM_LOG_WARN  => "warn",
56
        MIDCOM_LOG_ERROR => "error",
57
        MIDCOM_LOG_CRIT  => "critical"
58
    ];
59
60
    /**
61
     * Standard constructor
62
     */
63
    public function __construct($filename = null)
64
    {
65
        if (null === $filename) {
66
            $filename = midcom::get()->config->get('log_filename');
67
        }
68
        $this->_filename = $filename;
69
        $this->_loglevel = midcom::get()->config->get('log_level');
70
    }
71
72
    /**
73
     * Set log level
74
     *
75
     * @param int $loglevel        New log level
76
     */
77
    public function set_loglevel($loglevel)
78
    {
79
        $this->_loglevel = $loglevel;
80
    }
81
82
    /**
83
     * Get log level
84
     */
85
    public function get_loglevel() : int
86
    {
87
        return $this->_loglevel;
88
    }
89
90
    public function log_php_error($loglevel)
91
    {
92
        $error = error_get_last();
93
        if (!empty($error['message'])) {
94
            $this->log('Last PHP error was: ' . $error['message'], $loglevel);
95
        }
96
    }
97
98
    /**
99
     * Log a message
100
     *
101
     * @param string $message    The message to be logged
102
     * @param int $loglevel        The log level
103
     */
104 729
    public function log($message, $loglevel = MIDCOM_LOG_DEBUG)
105
    {
106 729
        if (!$this->check_level($loglevel)) {
107
            return;
108
        }
109
110 729
        $file = fopen($this->_filename, 'a+');
111
112 729
        if (function_exists('xdebug_memory_usage')) {
113 729
            static $lastmem = 0;
114 729
            $curmem = xdebug_memory_usage();
115 729
            $delta = $curmem - $lastmem;
116 729
            $lastmem = $curmem;
117
118 729
            $prefix = sprintf("%s (%012.9f, %9s, %7s):\t",
119 729
                date('M d Y H:i:s'),
120 729
                xdebug_time_index(),
121 729
                number_format($curmem, 0, ',', '.'),
122 729
                number_format($delta, 0, ',', '.')
123
            );
124
        } else {
125
            $prefix = date('M d Y H:i:s') . "\t";
126
        }
127
128 729
        if (array_key_exists($loglevel, $this->_loglevels)) {
129 729
            $prefix .= '[' . $this->_loglevels[$loglevel] . '] ';
130
        }
131
132
        //find the proper caller
133 729
        $prefix .= $this->_get_caller();
134 729
        fwrite($file, $prefix . trim($message) . "\n");
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, 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

134
        fwrite(/** @scrutinizer ignore-type */ $file, $prefix . trim($message) . "\n");
Loading history...
135 729
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

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