Telescope 监控、Sentry 错误追踪与零停机发布
TaskFlow 上线后,需要实时了解:哪些请求慢了、队列任务失败了、N+1 查询出现了、用户遇到了什么错误。Laravel Telescope 提供开发/Staging 环境的全面诊断,Sentry 负责生产错误追踪,Envoyer 或 GitHub Actions 实现零停机发布。
Laravel Telescope
# 安装(仅开发/Staging 环境)
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
# 生产环境不安装 Telescope(性能和安全考虑)
# 通过 composer.json 的 require-dev 限制
// app/Providers/TelescopeServiceProvider.php
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
public function register(): void
{
// 只在非生产环境运行
if ($this->app->isProduction()) {
return;
}
Telescope::night(); // 深色主题
$this->hideSensitiveRequestDetails();
// 过滤:只记录慢查询(>100ms)
Telescope::filter(function (IncomingEntry $entry) {
if ($entry->type === EntryType::QUERY) {
return $entry->content['slow'] === true;
}
return true;
});
}
protected function hideSensitiveRequestDetails(): void
{
Telescope::hideRequestParameters(['password', 'password_confirmation', 'token']);
Telescope::hideRequestHeaders(['cookie', 'x-csrf-token', 'x-xsrf-token']);
}
// 访问权限:只允许特定邮箱访问
protected function gate(): void
{
Gate::define('viewTelescope', function (User $user) {
return in_array($user->email, [
'admin@taskflow.app',
'developer@taskflow.app',
]);
});
}
}
# .env(Staging 环境)
TELESCOPE_ENABLED=true
# .env(生产环境)
TELESCOPE_ENABLED=false
Telescope 功能速览
访问 /telescope(开发/Staging 环境):
📋 Requests — 所有 HTTP 请求:URL、状态码、响应时间、内存
🗃️ Queries — 每个请求的 SQL 查询(高亮 N+1 重复查询)
⚡ Cache — 缓存命中/miss 统计
📬 Mail — 发出的所有邮件(可直接预览 HTML)
🔔 Notifications — 所有通知记录
📦 Jobs — 队列任务状态、失败详情、重试次数
⏰ Schedule — 定时任务执行记录
🎯 Events — 所有触发的事件和监听器
🔐 Gates — 权限检查结果(authorize 调用记录)
⚠️ Exceptions — 异常堆栈信息
Laravel Debugbar(开发环境)
composer require barryvdh/laravel-debugbar --dev
开发环境浏览器底部工具栏显示:
⏱ 时间:请求总时间 / PHP 执行时间 / 框架启动时间
💾 内存:峰值内存使用
🗄 数据库:查询数量 / 执行时间 / 完整 SQL
📦 缓存:命中 / Miss / 写入次数
📬 邮件:发送的邮件
🔄 模型:加载的 Eloquent 模型实例
常见 N+1 识别:数据库面板里看到重复的 SELECT WHERE id = ? 查询
Sentry:生产错误监控
composer require sentry/sentry-laravel
php artisan sentry:publish --dsn=https://xxx@sentry.io/xxx
// bootstrap/app.php — 注册 Sentry 错误处理
->withExceptions(function (Exceptions $exceptions) {
// 集成 Sentry
Integration::handles($exceptions);
// 自定义:哪些异常不上报 Sentry
$exceptions->dontReport([
AuthorizationException::class,
ModelNotFoundException::class,
ValidationException::class,
ThrottleRequestsException::class,
]);
// 给异常添加用户上下文
$exceptions->context(fn () => [
'user_id' => auth()->id(),
'url' => request()->fullUrl(),
]);
})
// 手动上报错误(业务逻辑中的可预期异常)
try {
$result = $this->externalApiService->sync($task);
} catch (ExternalApiException $e) {
// 上报到 Sentry,附加额外上下文
\Sentry\captureException($e, [
'extra' => [
'task_id' => $task->id,
'api_status' => $e->getStatusCode(),
],
]);
// 给用户友好提示,不暴露内部错误
return response()->json(['message' => '同步失败,请稍后重试'], 503);
}
# .env(生产)
SENTRY_LARAVEL_DSN=https://xxx@sentry.io/12345
SENTRY_TRACES_SAMPLE_RATE=0.1 # 采样 10% 的请求做性能追踪
SENTRY_PROFILES_SAMPLE_RATE=0.1
零停机发布:Envoyer
Envoyer 零停机发布原理:
/home/forge/taskflow.app/ ← 当前符号链接指向的版本
current → releases/20240322_143000/
releases/
20240322_143000/ ← 新版本(正在准备)
20240322_120000/ ← 上一版本(保留用于回滚)
20240320_100000/ ← 保留 5 个版本
storage/ ← 共享目录(跨版本持久化)
app/
framework/
logs/
.env ← 共享环境配置
发布流程:
1. 克隆代码到 releases/新目录
2. 安装依赖、运行迁移、生成缓存
3. 把 current 符号链接原子切换到新目录 ← 切换瞬间完成,无停机
4. 重启 PHP-FPM / queue:restart
零停机发布:GitHub Actions 方案
# deploy.sh(不用 Envoyer,自己实现原子切换)
#!/bin/bash
set -e
DEPLOY_PATH="/home/forge/taskflow.app"
RELEASES_PATH="$DEPLOY_PATH/releases"
SHARED_PATH="$DEPLOY_PATH/shared"
KEEP_RELEASES=5
# 创建版本目录
RELEASE="$RELEASES_PATH/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$RELEASE"
# 克隆代码
git clone --depth=1 --branch=main \
git@github.com:yourorg/taskflow.git "$RELEASE"
cd "$RELEASE"
# 链接共享目录(.env、storage 跨版本共享)
ln -nfs "$SHARED_PATH/.env" .env
rm -rf storage
ln -nfs "$SHARED_PATH/storage" storage
# 安装依赖
composer install --no-dev --optimize-autoloader --no-interaction
# 框架缓存
php artisan optimize
# 数据库迁移(新版本发布前执行)
php artisan migrate --force
# 原子切换符号链接(这一步不会有请求失败)
ln -nfs "$RELEASE" "$DEPLOY_PATH/current"
# 重启
sudo service php8.3-fpm reload # reload 比 restart 更优雅(等待当前请求完成)
php artisan queue:restart
# 清理旧版本(只保留最近 5 个)
ls -1dt "$RELEASES_PATH"/* | tail -n +$((KEEP_RELEASES + 1)) | xargs rm -rf
echo "✅ 部署完成:$RELEASE"
健康检查端点
// routes/web.php
Route::get('/health', function () {
$checks = [];
// 数据库连接
try {
DB::select('SELECT 1');
$checks['database'] = 'ok';
} catch (\Exception $e) {
$checks['database'] = 'error: ' . $e->getMessage();
}
// Redis 连接
try {
Redis::ping();
$checks['redis'] = 'ok';
} catch (\Exception $e) {
$checks['redis'] = 'error: ' . $e->getMessage();
}
// 队列 Worker(检查最后一次心跳)
$lastHeartbeat = Cache::get('queue:heartbeat');
$checks['queue'] = $lastHeartbeat && now()->diffInMinutes($lastHeartbeat) < 5
? 'ok'
: 'warning: no heartbeat';
$status = collect($checks)->contains(fn ($v) => str_starts_with($v, 'error')) ? 503 : 200;
return response()->json([
'status' => $status === 200 ? 'healthy' : 'unhealthy',
'version' => config('app.version', 'unknown'),
'timestamp' => now()->toISOString(),
'checks' => $checks,
], $status);
})->name('health');
生产部署检查清单
部署前:
□ 所有测试通过(php artisan test)
□ .env 生产变量已配置
□ APP_DEBUG=false
□ APP_KEY 已设置
□ 数据库备份已完成
部署中:
□ composer install --no-dev --optimize-autoloader
□ php artisan migrate --force
□ php artisan optimize(config/route/view 缓存)
□ php artisan queue:restart
部署后验证:
□ GET /health 返回 200
□ 登录功能正常
□ 发送一封测试邮件
□ 队列 Worker 在运行(php artisan horizon:status)
□ Sentry 没有新错误
□ 响应时间正常(< 300ms P95)
回滚方案:
□ Envoyer 一键回滚 / 脚本切回上一版本符号链接
□ 数据库迁移可回滚(php artisan migrate:rollback)
全书完:恭喜!你已经掌握了用 Laravel 构建和部署生产级 SaaS 应用的完整知识体系——从 IoC 容器到 Eloquent ORM,从 Sanctum 认证到 Gate/Policy 权限,从 Queue/Horizon 到 WebSocket 广播,从 Pest 测试到 Docker 容器化部署。TaskFlow 现在是一个真正的生产就绪的应用。