Test Failed
Push — dev ( 3785bc...ce443c )
by 世昌
02:27
created

Debug::getCaller()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
nc 2
nop 1
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace suda\framework\debug;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerAwareTrait;
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\LoggerTrait;
9
use Psr\Log\LogLevel;
10
use suda\framework\debug\attach\DumpTrait;
11
use suda\framework\debug\attach\AttachTrait;
12
use suda\framework\debug\attach\DumpInterface;
13
use suda\framework\debug\attach\AttachInterface;
14
15
class Debug implements LoggerInterface, LoggerAwareInterface, DumpInterface, AttachInterface, ConfigInterface
16
{
17
    use LoggerTrait, LoggerAwareTrait, DumpTrait, AttachTrait, ConfigTrait;
18
19
    /**
20
     * @var array
21
     */
22
    protected static $levels = ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'];
23
24
    /**
25
     * 忽略堆栈
26
     *
27
     * @var array
28
     */
29
    protected $ignoreTraces = [__DIR__];
30
31
    /**
32
     * 时间记录
33
     *
34
     * @var array
35
     */
36
    protected $timeRecord;
37
38
    /**
39
     * @var array
40
     */
41
    protected $timing;
42
43
    /**
44
     * @param mixed $level
45
     * @param string $message
46
     * @param array $context
47
     */
48
    public function log($level, $message, array $context = [])
49
    {
50
        $attribute = $this->getAttribute();
51
52
        $caller = $this->getCaller($context);
53
54
        [$attach, $replace] = $this->analyse($message, $context);
55
        $attribute            = array_merge($attribute, $attach);
56
        $attribute['message'] = strtr($message, $replace);
57
        $attribute['level']   = $level;
58
        $attribute['file']    = $caller['file'];
59
        $attribute['line']    = $caller['line'];
60
61
        $attribute = $this->assignAttributes($attribute);
62
        $this->logger->log($level, $this->interpolate($this->getConfig('log-format'), $attach, $attribute), []);
63
    }
64
65
    private function getCaller(array $context)
66
    {
67
        if (array_key_exists('exception', $context) && $context['exception'] instanceof \Throwable) {
68
            $backtrace = $context['exception']->getTrace();
69
        } else {
70
            $backtrace = debug_backtrace();
71
        }
72
        $caller = new Caller($backtrace, $this->getIgnoreTraces());
73
        return $caller->getCallerTrace();
74
    }
75
76
    /**
77
     * @param string $message
78
     * @param array $context
79
     * @return array
80
     */
81
    public function analyse(string $message, array $context)
82
    {
83
        $replace = [];
84
        $attach  = [];
85
        foreach ($context as $key => $val) {
86
            $replaceKey = '{' . $key . '}';
87
            if ($this->canBeStringValue($val) && strpos($message, $replaceKey) !== false) {
88
                $replace['{' . $key . '}'] = $val;
89
            } else {
90
                $attach[$key] = $val;
91
            }
92
        }
93
        return [$attach, $replace];
94
    }
95
96
    /**
97
     * 设置忽略前缀
98
     *
99
     * @return array
100
     */
101
    public function getIgnoreTraces(): array
102
    {
103
        return $this->ignoreTraces;
104
    }
105
106
    /**
107
     * @return array
108
     */
109
    public function getDefaultConfig(): array
110
    {
111
        return [
112
            'log-format'   => '%time-format% - %memory-format% [%level%] %file%:%line% %message%',
113
            'start-time'   => 0,
114
            'start-memory' => 0,
115
        ];
116
    }
117
118
    /**
119
     * @param $val
120
     * @return bool
121
     */
122
    protected function canBeStringValue($val): bool
123
    {
124
        return !is_array($val) && (!is_object($val) || method_exists($val, '__toString'));
125
    }
126
127
    /**
128
     * @param array $attribute
129
     * @return array
130
     */
131
    protected function assignAttributes(array $attribute): array
132
    {
133
        $attribute['current-time']  = number_format(microtime(true), 4, '.', '');
134
        $time                       = microtime(true) - $this->getConfig('start-time');
135
        $memory                     = memory_get_usage() - $this->getConfig('start-memory');
136
        $attribute['time-format']   = number_format($time, 10, '.', '');
137
        $attribute['memory-format'] = $this->formatBytes($memory, 2);
138
        $attribute['memory']        = $memory;
139
        return $attribute;
140
    }
141
142
    /**
143
     * @param int $bytes
144
     * @param int $precision
145
     * @return string
146
     */
147
    public static function formatBytes(int $bytes, int $precision = 0)
148
    {
149
        $human = ['B', 'KB', 'MB', 'GB', 'TB'];
150
        $bytes = max($bytes, 0);
151
        $pow   = floor(($bytes ? log($bytes) : 0) / log(1024));
152
        $pos   = min($pow, count($human) - 1);
153
        $bytes /= (1 << (10 * $pos));
154
        return round($bytes, $precision) . ' ' . $human[$pos];
155
    }
156
157
    /**
158
     * @param string $name
159
     * @param string $type
160
     */
161
    public function time(string $name, string $type = LogLevel::INFO)
162
    {
163
        $this->timeRecord[$name] = ['time' => microtime(true), 'level' => $type];
164
    }
165
166
    /**
167
     * @param string $name
168
     * @return float
169
     */
170
    public function timeEnd(string $name)
171
    {
172
        if (array_key_exists($name, $this->timeRecord)) {
173
            $pass = microtime(true) - $this->timeRecord[$name]['time'];
174
            $this->log(
175
                $this->timeRecord[$name]['level'],
176
                sprintf("process %s cost %ss", $name, number_format($pass, 5, '.', ''))
177
            );
178
            unset($this->timeRecord[$name]);
179
            return $pass;
180
        }
181
        return 0;
182
    }
183
184
    /**
185
     * @param string $name
186
     * @param float $time
187
     * @param string $description
188
     */
189
    public function recordTiming(string $name, float $time, string $description = '')
190
    {
191
        if (array_key_exists($name, $this->timing)) {
192
            $this->timing[$name]['time'] += $time;
193
        } else {
194
            $this->timing[$name]['time'] = $time;
195
        }
196
        $this->timing[$name]['description'] = $description;
197
    }
198
199
    /**
200
     * @return array
201
     */
202
    public function getTiming(): array
203
    {
204
        return $this->timing;
205
    }
206
207
    /**
208
     * @param array $ignoreTraces
209
     * @return $this
210
     */
211
    public function setIgnoreTraces(array $ignoreTraces)
212
    {
213
        $this->ignoreTraces = $ignoreTraces;
214
        return $this;
215
    }
216
217
218
    /**
219
     * 比较优先级
220
     *
221
     * @param string $a
222
     * @param string $b
223
     * @return integer
224
     */
225
    public static function compare(string $a, string $b): int
226
    {
227
        $indexA = array_search($a, static::$levels);
228
        $indexB = array_search($b, static::$levels);
229
        return $indexA - $indexB;
230
    }
231
232
    /**
233
     * 添加忽略路径
234
     * @param string $path
235
     * @return $this
236
     */
237
    public function addIgnorePath(string $path)
238
    {
239
        $this->ignoreTraces[] = $path;
240
        return $this;
241
    }
242
}
243