ThinkPHP6/8 调试技巧与性能分析实战指南

在ThinkPHP开发过程中,掌握有效的调试技巧和性能分析方法对于提高开发效率和应用性能至关重要。本文将详细介绍ThinkPHP6/8的调试工具、性能分析技术和最佳实践。

调试环境配置

开发环境设置

.env文件中配置调试相关参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 应用调试模式
APP_DEBUG = true

# 显示错误信息
SHOW_ERROR_MSG = true

# 日志级别
LOG_LEVEL = debug

# 数据库调试
DATABASE_DEBUG = true

# 缓存调试
CACHE_DEBUG = true

# 队列调试
QUEUE_DEBUG = true

调试配置文件

创建config/debug.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
/**
* 调试配置文件
* 用于配置各种调试选项和工具
*/
return [
// 是否开启调试模式
'app_debug' => env('APP_DEBUG', false),

// 是否显示错误信息
'show_error_msg' => env('SHOW_ERROR_MSG', false),

// 错误显示级别
'error_reporting' => E_ALL,

// 是否开启Trace调试
'app_trace' => env('APP_TRACE', false),

// Trace配置
'trace' => [
'type' => 'Html',
'trace_tabs' => [
'base' => '基本',
'file' => '文件',
'info' => '流程',
'notice|error' => '错误',
'sql' => 'SQL',
'debug|log' => '调试',
],
],

// 性能分析配置
'performance' => [
// 是否开启性能分析
'enable' => env('PERFORMANCE_DEBUG', false),

// 慢查询阈值(毫秒)
'slow_query_threshold' => 1000,

// 慢请求阈值(毫秒)
'slow_request_threshold' => 2000,

// 内存使用阈值(MB)
'memory_threshold' => 128,
],

// 调试工具配置
'tools' => [
// 是否开启SQL调试
'sql_debug' => env('SQL_DEBUG', false),

// 是否开启缓存调试
'cache_debug' => env('CACHE_DEBUG', false),

// 是否开启队列调试
'queue_debug' => env('QUEUE_DEBUG', false),
],
];

调试工具与技巧

内置调试函数

创建app/common/debug.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php
/**
* 调试辅助函数
* 提供便捷的调试方法
*/

if (!function_exists('dd')) {
/**
* 调试输出并终止程序
* @param mixed ...$vars 要输出的变量
*/
function dd(...$vars)
{
foreach ($vars as $var) {
dump($var);
}
exit(1);
}
}

if (!function_exists('dump_sql')) {
/**
* 输出最后执行的SQL语句
* @param string $connection 数据库连接名
*/
function dump_sql(string $connection = 'default')
{
$db = \think\facade\Db::connect($connection);
$sql = $db->getLastSql();
dump('Last SQL: ' . $sql);
}
}

if (!function_exists('debug_backtrace_simple')) {
/**
* 简化的调用栈跟踪
* @param int $limit 限制层数
* @return array 调用栈信息
*/
function debug_backtrace_simple(int $limit = 10): array
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit);
$result = [];

foreach ($trace as $item) {
if (isset($item['file']) && isset($item['line'])) {
$result[] = [
'file' => basename($item['file']),
'line' => $item['line'],
'function' => $item['function'] ?? 'unknown',
'class' => $item['class'] ?? '',
];
}
}

return $result;
}
}

if (!function_exists('memory_usage')) {
/**
* 获取内存使用情况
* @param bool $real_usage 是否获取真实内存使用
* @return array 内存使用信息
*/
function memory_usage(bool $real_usage = false): array
{
return [
'current' => round(memory_get_usage($real_usage) / 1024 / 1024, 2) . 'MB',
'peak' => round(memory_get_peak_usage($real_usage) / 1024 / 1024, 2) . 'MB',
'limit' => ini_get('memory_limit'),
];
}
}

if (!function_exists('execution_time')) {
/**
* 计算代码执行时间
* @param callable $callback 要执行的回调函数
* @return array 包含结果和执行时间
*/
function execution_time(callable $callback): array
{
$start = microtime(true);
$result = $callback();
$end = microtime(true);

return [
'result' => $result,
'execution_time' => round(($end - $start) * 1000, 2) . 'ms',
];
}
}

调试中间件

创建app/middleware/DebugMiddleware.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<?php
declare(strict_types=1);

namespace app\middleware;

use think\facade\Config;
use think\facade\Log;
use think\facade\Db;

/**
* 调试中间件
* 用于收集和记录调试信息
*/
class DebugMiddleware
{
/**
* 处理请求
* @param \think\Request $request 请求对象
* @param \Closure $next 下一个中间件
* @return mixed
*/
public function handle($request, \Closure $next)
{
// 只在调试模式下启用
if (!Config::get('app.app_debug')) {
return $next($request);
}

$startTime = microtime(true);
$startMemory = memory_get_usage();

// 开启SQL日志记录
if (Config::get('debug.tools.sql_debug')) {
Db::listen(function ($sql, $time, $explain) {
$this->logSqlQuery($sql, $time, $explain);
});
}

try {
$response = $next($request);

// 记录请求信息
$this->logRequestInfo($request, $response, $startTime, $startMemory);

// 添加调试头信息
$this->addDebugHeaders($response, $startTime, $startMemory);

return $response;
} catch (\Exception $e) {
// 记录异常信息
$this->logException($request, $e, $startTime, $startMemory);
throw $e;
}
}

/**
* 记录SQL查询
* @param string $sql SQL语句
* @param float $time 执行时间
* @param array $explain 执行计划
*/
private function logSqlQuery(string $sql, float $time, array $explain): void
{
$logData = [
'sql' => $sql,
'time' => round($time * 1000, 2) . 'ms',
'explain' => $explain,
];

// 慢查询告警
$threshold = Config::get('debug.performance.slow_query_threshold', 1000);
if ($time * 1000 > $threshold) {
Log::warning('慢查询检测', $logData);
} else {
Log::debug('SQL查询', $logData);
}
}

/**
* 记录请求信息
* @param \think\Request $request 请求对象
* @param \think\Response $response 响应对象
* @param float $startTime 开始时间
* @param int $startMemory 开始内存
*/
private function logRequestInfo($request, $response, float $startTime, int $startMemory): void
{
$endTime = microtime(true);
$endMemory = memory_get_usage();

$requestInfo = [
'method' => $request->method(),
'url' => $request->url(true),
'ip' => $request->ip(),
'user_agent' => $request->header('User-Agent'),
'status_code' => $response->getCode(),
'execution_time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
'memory_usage' => round(($endMemory - $startMemory) / 1024 / 1024, 2) . 'MB',
'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB',
];

// 慢请求告警
$threshold = Config::get('debug.performance.slow_request_threshold', 2000);
if (($endTime - $startTime) * 1000 > $threshold) {
Log::warning('慢请求检测', $requestInfo);
} else {
Log::debug('请求信息', $requestInfo);
}
}

/**
* 添加调试头信息
* @param \think\Response $response 响应对象
* @param float $startTime 开始时间
* @param int $startMemory 开始内存
*/
private function addDebugHeaders($response, float $startTime, int $startMemory): void
{
$endTime = microtime(true);
$endMemory = memory_get_usage();

$response->header([
'X-Debug-Time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
'X-Debug-Memory' => round(($endMemory - $startMemory) / 1024 / 1024, 2) . 'MB',
'X-Debug-Peak-Memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB',
'X-Debug-Queries' => Db::getQueryTimes(),
]);
}

/**
* 记录异常信息
* @param \think\Request $request 请求对象
* @param \Exception $exception 异常对象
* @param float $startTime 开始时间
* @param int $startMemory 开始内存
*/
private function logException($request, \Exception $exception, float $startTime, int $startMemory): void
{
$endTime = microtime(true);
$endMemory = memory_get_usage();

$exceptionInfo = [
'method' => $request->method(),
'url' => $request->url(true),
'exception_class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'execution_time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
'memory_usage' => round(($endMemory - $startMemory) / 1024 / 1024, 2) . 'MB',
'trace' => $exception->getTraceAsString(),
];

Log::error('请求异常', $exceptionInfo);
}
}

性能分析工具

性能监控服务

创建app/service/PerformanceMonitor.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
<?php
declare(strict_types=1);

namespace app\service;

use think\facade\Cache;
use think\facade\Log;
use think\facade\Db;

/**
* 性能监控服务
* 用于监控应用性能指标
*/
class PerformanceMonitor
{
/**
* 性能数据缓存键前缀
*/
private const CACHE_PREFIX = 'performance:';

/**
* 记录性能指标
* @param string $metric 指标名称
* @param mixed $value 指标值
* @param array $tags 标签
*/
public function recordMetric(string $metric, $value, array $tags = []): void
{
$data = [
'metric' => $metric,
'value' => $value,
'tags' => $tags,
'timestamp' => time(),
];

// 存储到缓存
$key = self::CACHE_PREFIX . $metric . ':' . date('Y-m-d-H');
$metrics = Cache::get($key, []);
$metrics[] = $data;

// 只保留最近1000条记录
if (count($metrics) > 1000) {
$metrics = array_slice($metrics, -1000);
}

Cache::set($key, $metrics, 3600);

// 记录到日志
Log::info('性能指标', $data);
}

/**
* 获取性能指标
* @param string $metric 指标名称
* @param int $hours 获取最近几小时的数据
* @return array 性能指标数据
*/
public function getMetrics(string $metric, int $hours = 1): array
{
$metrics = [];

for ($i = 0; $i < $hours; $i++) {
$hour = date('Y-m-d-H', time() - $i * 3600);
$key = self::CACHE_PREFIX . $metric . ':' . $hour;
$hourMetrics = Cache::get($key, []);
$metrics = array_merge($metrics, $hourMetrics);
}

return $metrics;
}

/**
* 分析响应时间
* @param int $hours 分析最近几小时的数据
* @return array 响应时间分析结果
*/
public function analyzeResponseTime(int $hours = 1): array
{
$metrics = $this->getMetrics('response_time', $hours);

if (empty($metrics)) {
return [
'count' => 0,
'avg' => 0,
'min' => 0,
'max' => 0,
'p95' => 0,
'p99' => 0,
];
}

$values = array_column($metrics, 'value');
sort($values);

$count = count($values);
$sum = array_sum($values);

return [
'count' => $count,
'avg' => round($sum / $count, 2),
'min' => min($values),
'max' => max($values),
'p95' => $this->percentile($values, 95),
'p99' => $this->percentile($values, 99),
];
}

/**
* 分析内存使用
* @param int $hours 分析最近几小时的数据
* @return array 内存使用分析结果
*/
public function analyzeMemoryUsage(int $hours = 1): array
{
$metrics = $this->getMetrics('memory_usage', $hours);

if (empty($metrics)) {
return [
'count' => 0,
'avg' => 0,
'min' => 0,
'max' => 0,
'peak' => 0,
];
}

$values = array_column($metrics, 'value');
$count = count($values);
$sum = array_sum($values);

return [
'count' => $count,
'avg' => round($sum / $count, 2),
'min' => min($values),
'max' => max($values),
'peak' => max($values),
];
}

/**
* 分析数据库查询性能
* @param int $hours 分析最近几小时的数据
* @return array 数据库查询性能分析结果
*/
public function analyzeDatabasePerformance(int $hours = 1): array
{
$queryMetrics = $this->getMetrics('db_query_time', $hours);
$queryCountMetrics = $this->getMetrics('db_query_count', $hours);

$result = [
'query_time' => $this->analyzeMetricValues($queryMetrics),
'query_count' => $this->analyzeMetricValues($queryCountMetrics),
'slow_queries' => $this->getSlowQueries($hours),
];

return $result;
}

/**
* 获取慢查询
* @param int $hours 获取最近几小时的数据
* @return array 慢查询列表
*/
public function getSlowQueries(int $hours = 1): array
{
$slowQueries = [];

for ($i = 0; $i < $hours; $i++) {
$hour = date('Y-m-d-H', time() - $i * 3600);
$key = self::CACHE_PREFIX . 'slow_queries:' . $hour;
$hourSlowQueries = Cache::get($key, []);
$slowQueries = array_merge($slowQueries, $hourSlowQueries);
}

// 按执行时间排序
usort($slowQueries, function ($a, $b) {
return $b['time'] <=> $a['time'];
});

return array_slice($slowQueries, 0, 50); // 返回最慢的50条
}

/**
* 记录慢查询
* @param string $sql SQL语句
* @param float $time 执行时间(秒)
* @param array $explain 执行计划
*/
public function recordSlowQuery(string $sql, float $time, array $explain = []): void
{
$data = [
'sql' => $sql,
'time' => $time,
'explain' => $explain,
'timestamp' => time(),
];

$key = self::CACHE_PREFIX . 'slow_queries:' . date('Y-m-d-H');
$slowQueries = Cache::get($key, []);
$slowQueries[] = $data;

// 只保留最近100条记录
if (count($slowQueries) > 100) {
$slowQueries = array_slice($slowQueries, -100);
}

Cache::set($key, $slowQueries, 3600);
}

/**
* 生成性能报告
* @param int $hours 报告时间范围(小时)
* @return array 性能报告
*/
public function generateReport(int $hours = 24): array
{
return [
'response_time' => $this->analyzeResponseTime($hours),
'memory_usage' => $this->analyzeMemoryUsage($hours),
'database' => $this->analyzeDatabasePerformance($hours),
'error_rate' => $this->calculateErrorRate($hours),
'throughput' => $this->calculateThroughput($hours),
];
}

/**
* 计算百分位数
* @param array $values 数值数组
* @param float $percentile 百分位数
* @return float 百分位数值
*/
private function percentile(array $values, float $percentile): float
{
$count = count($values);
$index = ($percentile / 100) * ($count - 1);

if (floor($index) == $index) {
return $values[$index];
}

$lower = $values[floor($index)];
$upper = $values[ceil($index)];
$fraction = $index - floor($index);

return $lower + ($upper - $lower) * $fraction;
}

/**
* 分析指标值
* @param array $metrics 指标数据
* @return array 分析结果
*/
private function analyzeMetricValues(array $metrics): array
{
if (empty($metrics)) {
return [
'count' => 0,
'avg' => 0,
'min' => 0,
'max' => 0,
];
}

$values = array_column($metrics, 'value');
$count = count($values);
$sum = array_sum($values);

return [
'count' => $count,
'avg' => round($sum / $count, 2),
'min' => min($values),
'max' => max($values),
];
}

/**
* 计算错误率
* @param int $hours 计算最近几小时的数据
* @return float 错误率(百分比)
*/
private function calculateErrorRate(int $hours): float
{
$totalRequests = $this->getMetrics('request_count', $hours);
$errorRequests = $this->getMetrics('error_count', $hours);

$totalCount = count($totalRequests);
$errorCount = count($errorRequests);

if ($totalCount == 0) {
return 0;
}

return round(($errorCount / $totalCount) * 100, 2);
}

/**
* 计算吞吐量
* @param int $hours 计算最近几小时的数据
* @return float 每秒请求数
*/
private function calculateThroughput(int $hours): float
{
$requests = $this->getMetrics('request_count', $hours);
$count = count($requests);
$seconds = $hours * 3600;

return round($count / $seconds, 2);
}
}

性能分析命令

创建app/command/PerformanceAnalysis.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
<?php
declare(strict_types=1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use app\service\PerformanceMonitor;

/**
* 性能分析命令
* 用于分析应用性能数据
*/
class PerformanceAnalysis extends Command
{
/**
* 配置命令
*/
protected function configure()
{
$this->setName('performance:analysis')
->setDescription('分析应用性能数据')
->addOption('hours', 'h', \think\console\input\Option::VALUE_OPTIONAL, '分析时间范围(小时)', 1)
->addOption('format', 'f', \think\console\input\Option::VALUE_OPTIONAL, '输出格式(table|json)', 'table')
->addOption('export', 'e', \think\console\input\Option::VALUE_OPTIONAL, '导出文件路径', '');
}

/**
* 执行命令
* @param Input $input 输入对象
* @param Output $output 输出对象
* @return int 执行结果
*/
protected function execute(Input $input, Output $output)
{
$hours = (int) $input->getOption('hours');
$format = $input->getOption('format');
$exportPath = $input->getOption('export');

$output->writeln('<info>开始分析性能数据...</info>');

$monitor = new PerformanceMonitor();
$report = $monitor->generateReport($hours);

if ($format === 'json') {
$this->outputJson($output, $report, $exportPath);
} else {
$this->outputTable($output, $report, $exportPath);
}

$output->writeln('<info>性能分析完成!</info>');

return 0;
}

/**
* 输出表格格式
* @param Output $output 输出对象
* @param array $report 报告数据
* @param string $exportPath 导出路径
*/
private function outputTable(Output $output, array $report, string $exportPath): void
{
// 响应时间分析
$output->writeln('<comment>响应时间分析:</comment>');
$responseTable = new \think\console\Table();
$responseTable->setHeaders(['指标', '值']);
$responseTable->setRows([
['请求总数', $report['response_time']['count']],
['平均响应时间', $report['response_time']['avg'] . 'ms'],
['最小响应时间', $report['response_time']['min'] . 'ms'],
['最大响应时间', $report['response_time']['max'] . 'ms'],
['95%响应时间', $report['response_time']['p95'] . 'ms'],
['99%响应时间', $report['response_time']['p99'] . 'ms'],
]);
$responseTable->render($output);

// 内存使用分析
$output->writeln('<comment>内存使用分析:</comment>');
$memoryTable = new \think\console\Table();
$memoryTable->setHeaders(['指标', '值']);
$memoryTable->setRows([
['请求总数', $report['memory_usage']['count']],
['平均内存使用', $report['memory_usage']['avg'] . 'MB'],
['最小内存使用', $report['memory_usage']['min'] . 'MB'],
['最大内存使用', $report['memory_usage']['max'] . 'MB'],
['峰值内存使用', $report['memory_usage']['peak'] . 'MB'],
]);
$memoryTable->render($output);

// 数据库性能分析
$output->writeln('<comment>数据库性能分析:</comment>');
$dbTable = new \think\console\Table();
$dbTable->setHeaders(['指标', '查询时间', '查询次数']);
$dbTable->setRows([
['平均值', $report['database']['query_time']['avg'] . 'ms', $report['database']['query_count']['avg']],
['最小值', $report['database']['query_time']['min'] . 'ms', $report['database']['query_count']['min']],
['最大值', $report['database']['query_time']['max'] . 'ms', $report['database']['query_count']['max']],
]);
$dbTable->render($output);

// 系统指标
$output->writeln('<comment>系统指标:</comment>');
$systemTable = new \think\console\Table();
$systemTable->setHeaders(['指标', '值']);
$systemTable->setRows([
['错误率', $report['error_rate'] . '%'],
['吞吐量', $report['throughput'] . ' req/s'],
]);
$systemTable->render($output);

// 导出到文件
if ($exportPath) {
$this->exportToFile($exportPath, $report, 'table');
$output->writeln("<info>报告已导出到: {$exportPath}</info>");
}
}

/**
* 输出JSON格式
* @param Output $output 输出对象
* @param array $report 报告数据
* @param string $exportPath 导出路径
*/
private function outputJson(Output $output, array $report, string $exportPath): void
{
$json = json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$output->writeln($json);

if ($exportPath) {
$this->exportToFile($exportPath, $report, 'json');
$output->writeln("<info>报告已导出到: {$exportPath}</info>");
}
}

/**
* 导出到文件
* @param string $path 文件路径
* @param array $report 报告数据
* @param string $format 格式
*/
private function exportToFile(string $path, array $report, string $format): void
{
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}

if ($format === 'json') {
$content = json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} else {
$content = $this->formatReportAsText($report);
}

file_put_contents($path, $content);
}

/**
* 格式化报告为文本
* @param array $report 报告数据
* @return string 格式化后的文本
*/
private function formatReportAsText(array $report): string
{
$text = "性能分析报告\n";
$text .= "生成时间: " . date('Y-m-d H:i:s') . "\n\n";

$text .= "响应时间分析:\n";
$text .= " 请求总数: {$report['response_time']['count']}\n";
$text .= " 平均响应时间: {$report['response_time']['avg']}ms\n";
$text .= " 最小响应时间: {$report['response_time']['min']}ms\n";
$text .= " 最大响应时间: {$report['response_time']['max']}ms\n";
$text .= " 95%响应时间: {$report['response_time']['p95']}ms\n";
$text .= " 99%响应时间: {$report['response_time']['p99']}ms\n\n";

$text .= "内存使用分析:\n";
$text .= " 请求总数: {$report['memory_usage']['count']}\n";
$text .= " 平均内存使用: {$report['memory_usage']['avg']}MB\n";
$text .= " 最小内存使用: {$report['memory_usage']['min']}MB\n";
$text .= " 最大内存使用: {$report['memory_usage']['max']}MB\n";
$text .= " 峰值内存使用: {$report['memory_usage']['peak']}MB\n\n";

$text .= "数据库性能分析:\n";
$text .= " 平均查询时间: {$report['database']['query_time']['avg']}ms\n";
$text .= " 平均查询次数: {$report['database']['query_count']['avg']}\n";
$text .= " 最大查询时间: {$report['database']['query_time']['max']}ms\n";
$text .= " 最大查询次数: {$report['database']['query_count']['max']}\n\n";

$text .= "系统指标:\n";
$text .= " 错误率: {$report['error_rate']}%\n";
$text .= " 吞吐量: {$report['throughput']} req/s\n";

return $text;
}
}

错误处理与日志分析

自定义异常处理

创建app/exception/DebugExceptionHandle.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
<?php
declare(strict_types=1);

namespace app\exception;

use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\facade\Log;
use think\Response;
use Throwable;

/**
* 调试异常处理器
* 提供详细的异常信息和调试功能
*/
class DebugExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类型
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];

/**
* 记录异常信息
* @param Throwable $exception 异常对象
*/
public function report(Throwable $exception): void
{
// 检查是否需要记录
if (!$this->isIgnoreReport($exception)) {
$this->recordException($exception);
}

parent::report($exception);
}

/**
* 渲染异常为响应
* @param \think\Request $request 请求对象
* @param Throwable $e 异常对象
* @return Response 响应对象
*/
public function render($request, Throwable $e): Response
{
// 如果是AJAX请求或API请求,返回JSON格式
if ($request->isAjax() || $request->isApi()) {
return $this->renderJson($e);
}

// 如果开启调试模式,显示详细错误信息
if (config('app.app_debug')) {
return $this->renderDebug($e);
}

return parent::render($request, $e);
}

/**
* 记录异常信息
* @param Throwable $exception 异常对象
*/
private function recordException(Throwable $exception): void
{
$exceptionData = [
'exception_class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'code' => $exception->getCode(),
'trace' => $exception->getTraceAsString(),
'request_info' => $this->getRequestInfo(),
'system_info' => $this->getSystemInfo(),
'timestamp' => date('Y-m-d H:i:s'),
];

Log::error('应用异常', $exceptionData);

// 记录到性能监控
if (class_exists('\\app\\service\\PerformanceMonitor')) {
$monitor = new \app\service\PerformanceMonitor();
$monitor->recordMetric('error_count', 1, [
'exception_class' => get_class($exception),
'file' => basename($exception->getFile()),
]);
}
}

/**
* 渲染JSON格式异常响应
* @param Throwable $e 异常对象
* @return Response JSON响应
*/
private function renderJson(Throwable $e): Response
{
$data = [
'code' => $e->getCode() ?: 500,
'message' => $e->getMessage(),
];

// 调试模式下添加详细信息
if (config('app.app_debug')) {
$data['debug'] = [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTrace(),
];
}

return json($data, $data['code']);
}

/**
* 渲染调试模式异常页面
* @param Throwable $e 异常对象
* @return Response HTML响应
*/
private function renderDebug(Throwable $e): Response
{
$debugInfo = [
'exception' => [
'class' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'code' => $e->getCode(),
],
'trace' => $e->getTrace(),
'request' => $this->getRequestInfo(),
'system' => $this->getSystemInfo(),
'source_code' => $this->getSourceCode($e->getFile(), $e->getLine()),
];

// 生成调试页面HTML
$html = $this->generateDebugHtml($debugInfo);

return Response::create($html, 'html', 500);
}

/**
* 获取请求信息
* @return array 请求信息
*/
private function getRequestInfo(): array
{
$request = request();

return [
'method' => $request->method(),
'url' => $request->url(true),
'ip' => $request->ip(),
'user_agent' => $request->header('User-Agent'),
'headers' => $request->header(),
'params' => $request->param(),
];
}

/**
* 获取系统信息
* @return array 系统信息
*/
private function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'thinkphp_version' => \think\App::VERSION,
'memory_usage' => round(memory_get_usage() / 1024 / 1024, 2) . 'MB',
'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB',
'execution_time' => round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2) . 'ms',
];
}

/**
* 获取源代码片段
* @param string $file 文件路径
* @param int $line 行号
* @param int $context 上下文行数
* @return array 源代码片段
*/
private function getSourceCode(string $file, int $line, int $context = 5): array
{
if (!file_exists($file)) {
return [];
}

$lines = file($file);
$start = max(0, $line - $context - 1);
$end = min(count($lines), $line + $context);

$sourceCode = [];
for ($i = $start; $i < $end; $i++) {
$sourceCode[$i + 1] = [
'number' => $i + 1,
'code' => rtrim($lines[$i]),
'highlight' => ($i + 1) === $line,
];
}

return $sourceCode;
}

/**
* 生成调试页面HTML
* @param array $debugInfo 调试信息
* @return string HTML内容
*/
private function generateDebugHtml(array $debugInfo): string
{
$html = '<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>应用异常 - ThinkPHP调试</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { background: #e74c3c; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
.content { padding: 20px; }
.section { margin-bottom: 30px; }
.section h3 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
.code { background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 4px; overflow-x: auto; }
.highlight { background: #e74c3c !important; }
.trace-item { background: #ecf0f1; margin: 5px 0; padding: 10px; border-radius: 4px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #3498db; color: white; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>应用异常</h1>
<p><strong>' . htmlspecialchars($debugInfo['exception']['class']) . '</strong></p>
<p>' . htmlspecialchars($debugInfo['exception']['message']) . '</p>
</div>
<div class="content">';

// 异常信息
$html .= '<div class="section">
<h3>异常信息</h3>
<table>
<tr><th>异常类</th><td>' . htmlspecialchars($debugInfo['exception']['class']) . '</td></tr>
<tr><th>错误消息</th><td>' . htmlspecialchars($debugInfo['exception']['message']) . '</td></tr>
<tr><th>文件</th><td>' . htmlspecialchars($debugInfo['exception']['file']) . '</td></tr>
<tr><th>行号</th><td>' . $debugInfo['exception']['line'] . '</td></tr>
<tr><th>错误代码</th><td>' . $debugInfo['exception']['code'] . '</td></tr>
</table>
</div>';

// 源代码
if (!empty($debugInfo['source_code'])) {
$html .= '<div class="section">
<h3>源代码</h3>
<div class="code">';
foreach ($debugInfo['source_code'] as $lineInfo) {
$class = $lineInfo['highlight'] ? ' highlight' : '';
$html .= '<div class="' . $class . '">' . $lineInfo['number'] . ': ' . htmlspecialchars($lineInfo['code']) . '</div>';
}
$html .= '</div>
</div>';
}

// 调用栈
$html .= '<div class="section">
<h3>调用栈</h3>';
foreach ($debugInfo['trace'] as $index => $trace) {
$html .= '<div class="trace-item">
<strong>#' . $index . '</strong> ';
if (isset($trace['file'])) {
$html .= htmlspecialchars($trace['file']) . '(' . $trace['line'] . '): ';
}
if (isset($trace['class'])) {
$html .= htmlspecialchars($trace['class']) . htmlspecialchars($trace['type']);
}
$html .= htmlspecialchars($trace['function']) . '()';
$html .= '</div>';
}
$html .= '</div>';

// 系统信息
$html .= '<div class="section">
<h3>系统信息</h3>
<table>';
foreach ($debugInfo['system'] as $key => $value) {
$html .= '<tr><th>' . htmlspecialchars($key) . '</th><td>' . htmlspecialchars($value) . '</td></tr>';
}
$html .= '</table>
</div>';

$html .= '</div>
</div>
</body>
</html>';

return $html;
}
}

最佳实践总结

调试技巧最佳实践

  1. 分层调试

    • 控制器层:检查请求参数和路由
    • 服务层:验证业务逻辑
    • 数据层:分析SQL查询和数据操作
  2. 日志策略

    • 使用不同的日志级别
    • 结构化日志记录
    • 敏感信息脱敏
  3. 性能监控

    • 实时性能指标收集
    • 定期性能报告生成
    • 异常情况告警

性能优化建议

  1. 数据库优化

    • 监控慢查询
    • 优化索引设计
    • 使用查询缓存
  2. 内存管理

    • 监控内存使用
    • 及时释放资源
    • 避免内存泄漏
  3. 缓存策略

    • 合理使用缓存
    • 缓存失效策略
    • 缓存命中率监控

通过掌握这些调试技巧和性能分析方法,可以有效提高ThinkPHP应用的开发效率和运行性能,确保应用的稳定性和可维护性。

本站由 提供部署服务