Passed
Push — master ( ab3fce...bb4bd6 )
by Andreas
22:41
created

midcom_debug::get_caller()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9.0139

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 18
nc 16
nop 1
dl 0
loc 28
ccs 17
cts 18
cp 0.9444
crap 9.0139
rs 8.0555
c 1
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
     * @var Logger
34
     */
35
    private $logger;
36
37
    /**
38
     * Standard constructor
39
     */
40
    public function __construct(Logger $logger)
41
    {
42
        $this->logger = $logger;
43
    }
44
45
    /**
46
     * Converts MidCOM log levels to Monolog
47
     */
48 737
    public static function convert_level(int $level) : int
49
    {
50
        $level_map = [
51 737
            MIDCOM_LOG_DEBUG => Logger::DEBUG,
52
            MIDCOM_LOG_INFO  => Logger::INFO,
53
            MIDCOM_LOG_WARN  => Logger::WARNING,
54
            MIDCOM_LOG_ERROR => Logger::ERROR,
55
            MIDCOM_LOG_CRIT  => Logger::CRITICAL
56
        ];
57 737
        return $level_map[$level] ?? $level;
58
    }
59
60
    public function log_php_error($loglevel)
61
    {
62
        $error = error_get_last();
63
        if (!empty($error['message'])) {
64
            $this->log('Last PHP error was: ' . $error['message'], $loglevel);
65
        }
66
    }
67
68
    /**
69
     * Log a message
70
     *
71
     * @param string $message    The message to be logged
72
     * @param int $loglevel        The log level
73
     */
74 737
    public function log($message, $loglevel = MIDCOM_LOG_DEBUG)
75
    {
76 737
        $this->logger->pushProcessor([$this, 'get_caller']);
77 737
        $this->logger->addRecord(self::convert_level($loglevel), trim($message));
78 737
        $this->logger->popProcessor();
79 737
    }
80
81
    /**
82
     * @internal
83
     */
84 737
    public function get_caller(array $record) : array
85
    {
86 737
        $record['extra']['caller'] = '';
87 737
        $bt = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 3);
88
89 737
        while ($bt) {
90 737
            $caller = array_shift($bt);
91 737
            if (   !array_key_exists('class', $caller)
92 737
                || $caller['class'] != midcom_debug::class) {
93 737
                if (   !array_key_exists('function', $bt[0])
94 737
                    || $bt[0]['function'] != 'require') {
95 737
                    $caller = array_shift($bt);
96
                }
97
98 737
                break;
99
            }
100
        }
101
102 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...
103 737
            $record['extra']['caller'] .= $caller['class'] . '::';
104
        }
105 737
        if (   array_key_exists('function', $caller)
106 737
            && substr($caller['function'], 0, 6) != 'debug_') {
107 737
            $record['extra']['caller'] .= $caller['function'];
108
        } else {
109
            $record['extra']['caller'] .= $caller['file'] . ' (' . $caller['line']. ')';
110
        }
111 737
        return $record;
112
    }
113
114
    /**
115
     * Dump a variable
116
     *
117
     * @param string $message    The message to be logged
118
     * @param mixed $variable    The variable to be logged
119
     * @param int $loglevel        The log level
120
     */
121 22
    public function print_r($message, $variable, $loglevel = MIDCOM_LOG_DEBUG)
122
    {
123
        $this->logger->pushProcessor(function(array $record) use ($variable) {
124 22
            $cloner = new VarCloner();
125 22
            $dumper = new CliDumper();
126 22
            $record['message'] .= ' ' . $dumper->dump($cloner->cloneVar($variable), true);
127 22
            return $record;
128 22
        });
129 22
        $this->log($message, $loglevel);
130 22
        $this->logger->popProcessor();
131 22
    }
132
133
    /**
134
     * Dump stack trace, only working when XDebug is present.
135
     *
136
     * @param string $message    The message to be logged
137
     * @param int $loglevel        The log level
138
     * @link http://www.xdebug.org/ xdebug.org
139
     */
140 25
    public function print_function_stack($message, $loglevel = MIDCOM_LOG_DEBUG)
141
    {
142
        $this->logger->pushProcessor(function(array $record) {
143
            // the last four levels are already inside the debugging system, so skip those
144 25
            $stack = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 4);
145 25
            $stacktrace = "";
146 25
            foreach ($stack as $number => $frame) {
147 25
                $stacktrace .= $number + 1;
148 25
                if (isset($frame['file'])) {
149 25
                    $stacktrace .= ": {$frame['file']}:{$frame['line']} ";
150
                }
151 25
                if (array_key_exists('class', $frame)) {
152 25
                    $stacktrace .= "{$frame['class']}::{$frame['function']}";
153 25
                } elseif (array_key_exists('function', $frame)) {
154 25
                    $stacktrace .= $frame['function'];
155
                } else {
156
                    $stacktrace .= 'require, include or eval';
157
                }
158 25
                $stacktrace .= "\n";
159
            }
160
161 25
            $record['message'] .= "\n{$stacktrace}";
162 25
            return $record;
163 25
        });
164 25
        $this->log($message, $loglevel);
165 25
        $this->logger->popProcessor();
166 25
    }
167
}
168