مشكلة "يعمل على جهازي"
في تطبيق Next.js تعمل شيفرتك في أربعة أماكن مختلفة: المتصفح، وخادم Node.js، وبيئة Edge، ومرحلة البناء. أي خطأ في الإنتاج قد ينشأ في أيٍّ منها، وبشكل افتراضي ستعرف بوقوعه بطريقة واحدة فقط — أن يخبرك مستخدم.
وحتى حين تتوفّر لديك سجلات، فهي عادةً بلا فائدة. أثر مكدّس مُصغَّر يشير إلى chunk-4f2a.js:1:88213 لا يخبرك بشيء. وسجل دالة على Vercel يعرض الاستثناء لكنه لا يعرض استعلامات قاعدة البيانات الثلاثة التي سبقته. وما إن تضيف وكيل ذكاء اصطناعي يُجري ست استدعاءات أدوات لكل طلب، حتى يصبح سؤال "لماذا كان الطلب بطيئاً؟" بلا إجابة من دون تفاصيل على مستوى الـ spans.
يربط هذا الدرس Sentry بتطبيق Next.js 16 يعتمد App Router بحيث تُسدّ كل هذه الثغرات. في نهايته سيكون لديك:
- التقاط الأخطاء من البيئات الثلاث، مع آثار مكدّس مقروءة معادة إلى شيفرة TypeScript الأصلية
- آثار موزّعة تتبع نقرة في المتصفح مروراً بـ Server Action وصولاً إلى قاعدة البيانات
- سجلات مهيكلة مرتبطة بالأثر الذي أنتجها
- Session Replay لمشاهدة العشرين ثانية التي سبقت الانهيار
- مراقبة للمهام المجدولة تنبّهك حين تتوقف مهمة خلفية بصمت
- عدد الرموز واسم النموذج والتكلفة لكل تشغيلة وكيل ذكاء اصطناعي
المتطلبات المسبقة
- Node.js 20 أو أحدث
- مشروع Next.js 16 يستخدم App Router (يعمل أيضاً مع Next.js 15.3+ — ملف تهيئة العميل يتطلب هذه النسخة)
- حساب Sentry مجاني مع منظمة ومشروع منشأ لمنصة "Next.js"
- إلمام أساسي بـ TypeScript
إن كنت تبدأ من الصفر:
npx create-next-app@latest sentry-demo --typescript --app --tailwind
cd sentry-demoما الذي ستبنيه
تطبيق لوحة تحكم صغير بثلاثة أسطح هشّة عن قصد — Server Action يتصل بقاعدة البيانات، ومسار API يستدعي نموذجاً لغوياً، ومكوّن عميل قد يرمي خطأً أثناء العرض — كلها مُجهّزة بالكامل بأدوات المراقبة. كل خطوة أدناه تراكمية، فيمكنك التوقف عند أي نقطة والاحتفاظ بإعداد صالح للعمل.
الخطوة 1: تثبيت الحزمة وربط المشروع
ثبّت الحزمة:
npm install @sentry/nextjsأسرع طريق هو المعالج التفاعلي، الذي ينشئ ملفات التهيئة ويكتب الـ DSN في .env:
npx @sentry/wizard@latest -i nextjsالمعالج مريح، لكنه يخفي ما يجري فعلياً — وحين ينكسر شيء لاحقاً ستحتاج إلى معرفته. بقية هذا الدرس تنفّذ الإعداد يدوياً. إن استخدمت المعالج فتابع القراءة على أي حال وقارن بما ولّده.
أضف الـ DSN ورمز المصادقة إلى .env.local:
# آمن للكشف — الـ DSN يسمح بكتابة الأحداث فقط، لا بقراءتها
NEXT_PUBLIC_SENTRY_DSN="https://examplePublicKey@o0.ingest.sentry.io/0"
# غير آمن للكشف. يُستخدم وقت البناء فقط لرفع خرائط المصدر.
SENTRY_AUTH_TOKEN="sntrys_your_token_here"أنشئ رمز المصادقة في Sentry من Settings ← Auth Tokens بصلاحيتَي project:releases وorg:read. لا ترفعه إلى المستودع أبداً — أضف .env.local و.sentryclirc إلى .gitignore.
الخطوة 2: تهيئة البيئات الثلاث
يحتاج Sentry إلى تهيئة منفصلة لكل بيئة تشغيل، لأن لكل منها كائنات عامة مختلفة وتكاملات متاحة مختلفة. أنشئ أربعة ملفات في جذر المشروع (أو داخل src/ إن كنت تستخدم ذلك التنظيم).
instrumentation-client.ts
يحلّ هذا الملف محل sentry.client.config.ts القديم. يحمّله Next.js قبل تشغيل أي شيفرة عميل.
// instrumentation-client.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// إرفاق ترويسات الطلب وعنوان IP بالأحداث.
// أوقف هذا إن كانت لديك متطلبات صارمة بشأن البيانات الشخصية.
sendDefaultPii: true,
// الأداء: التقاط كل المعاملات في التطوير، وشريحة منها في الإنتاج.
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
// تمرير استدعاءات console.* و Sentry.logger.* كسجلات مهيكلة
enableLogs: true,
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
Sentry.feedbackIntegration({ colorScheme: "system" }),
],
// تسجيل 10% من الجلسات، و100% من الجلسات التي وقع فيها خطأ.
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? "development",
});
// ضروري كي تصبح تنقّلات App Router على جهة العميل معاملات مستقلة
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;هذا السطر الأخير أهم مما يبدو. من دونه، يُدمج التنقّل السلس من /dashboard إلى /settings ضمن معاملة تحميل الصفحة السابقة، فتصبح بيانات توقيت التنقّل بلا معنى.
sentry.server.config.ts
// sentry.server.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
sendDefaultPii: true,
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
enableLogs: true,
environment: process.env.VERCEL_ENV ?? "development",
// إرسال معرّف الإصدار كي تُطابَق آثار المكدّس مع خرائط المصدر الصحيحة
release: process.env.VERCEL_GIT_COMMIT_SHA,
});sentry.edge.config.ts
تعمل هنا الـ middleware وأي مسار يحمل export const runtime = "edge". بيئة Edge لا تملك واجهات Node.js، لذا تتعطّل عدة تكاملات — أبقِ هذا الملف بسيطاً.
// sentry.edge.config.ts
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
enableLogs: true,
environment: process.env.VERCEL_ENV ?? "development",
});instrumentation.ts
يستدعي Next.js الدالة register() مرة واحدة لكل عملية خادم. هنا تختار التهيئة المناسبة بحسب بيئة التشغيل، وهنا تربط آلية الإبلاغ عن الأخطاء في إطار العمل.
// instrumentation.ts
import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("./sentry.edge.config");
}
}
// يستدعي Next.js هذه الدالة لكل خطأ يُرمى داخل Server Component
// أو Server Action أو معالج مسار أو middleware.
export const onRequestError = Sentry.captureRequestError;تصدير onRequestError هو الخطوة الأكثر إغفالاً على الإطلاق. من دونه يبتلع Next.js الأخطاء المرمية داخل React Server Components ولا تصل إلى Sentry أبداً — سترى خطأ 500 عاماً في سجلاتك ولا شيء في لوحتك.
الخطوة 3: تغليف next.config.ts
تتولى withSentryConfig النصف الخاص بوقت البناء: تحقن إضافة Sentry في webpack أو Turbopack، وترفع خرائط المصدر، وتُعدّ اختيارياً مسار نفق.
// next.config.ts
import type { NextConfig } from "next";
import { withSentryConfig } from "@sentry/nextjs";
const nextConfig: NextConfig = {
// تهيئتك الحالية
};
export default withSentryConfig(nextConfig, {
org: "your-org-slug",
project: "your-project-slug",
authToken: process.env.SENTRY_AUTH_TOKEN,
// طباعة سجلات الرفع في CI فقط
silent: !process.env.CI,
// تمرير طلبات Sentry عبر نطاقك الخاص كي لا تحجب مانعات الإعلانات
// ما بين 30 و50 بالمئة من أحداث المتصفح.
tunnelRoute: "/monitoring-tunnel",
sourcemaps: {
// ارفع الخرائط إلى Sentry ثم احذفها من الحزمة المنشورة
// كي لا يتمكن أحد من قراءة شيفرتك من المتصفح.
deleteSourcemapsAfterUpload: true,
},
// إزالة سجلات تنقيح Sentry نفسها من حزمة الإنتاج
disableLogger: true,
// مراقبة مهام Vercel Cron المعرّفة في vercel.json تلقائياً
automaticVercelMonitors: true,
});خياران من هذه القائمة يثبتان قيمتهما فوراً.
tunnelRoute ينشئ مسار API على نطاقك يعيد توجيه الأحداث إلى Sentry. نحو ثلث حركة المتصفح تعمل خلف مانع إعلانات يتعرّف على *.ingest.sentry.io ويحجبه مباشرة، لذا من دون نفق تكون أرقام أخطاء جهة العميل خاطئة بشكل منهجي — ومنحازة تحديداً ضد المستخدمين التقنيين الأكثر عرضة لمواجهة الحالات الحديّة.
deleteSourcemapsAfterUpload هو الفارق بين آثار مقروءة وشيفرة مصدرية مسرَّبة. Sentry يحتاج الخرائط؛ مستخدموك لا.
ملاحظة واحدة: إن كنت تستخدم middleware.ts مع matcher، فاستثنِ مسار النفق وإلا ستعمل الـ middleware مع كل رفع حدث:
// middleware.ts
export const config = {
matcher: ["/((?!_next/static|_next/image|monitoring-tunnel|favicon.ico).*)"],
};الخطوة 4: التقاط ما تبتلعه React
في Next.js حدّان للأخطاء يجب ربطهما يدوياً، لأن React تلتقط تلك الأخطاء قبل وصولها إلى أي معالج عام.
app/global-error.tsx
هذا خط الدفاع الأخير — يلتقط الأخطاء داخل التخطيط الجذري نفسه.
"use client";
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
import NextError from "next/error";
export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<NextError statusCode={0} />
</body>
</html>
);
}app/dashboard/error.tsx
حدود الأخطاء على مستوى القطاع تستحق المعاملة نفسها، إضافةً إلى وسيلة تتيح للمستخدم إخبارك بما كان يفعله:
"use client";
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
Sentry.captureException(error, {
tags: { section: "dashboard" },
});
}, [error]);
return (
<div className="p-8">
<h2>حدث خطأ أثناء تحميل لوحة التحكم.</h2>
<button onClick={reset}>أعد المحاولة</button>
<button onClick={() => Sentry.showReportDialog()}>
أخبرنا بما حدث
</button>
</div>
);
}الخاصية digest تستحق الانتباه. في الإنتاج يستبدل Next.js رسائل أخطاء الخادم بتجزئة مبهمة قبل إرسالها إلى المتصفح. يلتقط Sentry الطرفين معاً، لذا فالبحث في مشكلاتك عن تلك التجزئة يربط ما رآه المستخدم بالاستثناء الحقيقي على الخادم.
Server Actions
الـ Server Actions التي تلتقط أخطاءها بنفسها تحتاج إلى التقاط صريح، وإلا فلن تُستدعى onRequestError أبداً:
"use server";
import * as Sentry from "@sentry/nextjs";
import { db } from "@/lib/db";
export async function updateProfile(formData: FormData) {
try {
await db.user.update({
where: { id: formData.get("id") as string },
data: { name: formData.get("name") as string },
});
return { ok: true };
} catch (error) {
Sentry.captureException(error, {
tags: { action: "updateProfile" },
extra: { userId: formData.get("id") },
});
return { ok: false, message: "تعذّر حفظ ملفك الشخصي." };
}
}القاعدة العملية: كل كتلة catch تُعيد رسالة ودّية للمستخدم هي موضع يختفي فيه خطأ لولا ذلك. التقط الخطأ هناك.
الخطوة 5: اجعل الآثار تجيب عن أسئلة حقيقية
أخذ عيّنات بنسبة 10% نقطة انطلاق معيارية، لكن المعدّل الثابت يهدر حصتك على حركة سليمة ويفوّت المسارات البطيئة النادرة. استبدل tracesSampleRate بـ tracesSampler لتحكّم أدقّ:
// sentry.server.config.ts
Sentry.init({
// ...
tracesSampler: (samplingContext) => {
const name = samplingContext.name ?? "";
// لا تأخذ عيّنات من فحوص الصحة إطلاقاً — ضجيج خالص
if (name.includes("/api/health")) return 0;
// خذ عيّنة دائماً من الدفع: حجم منخفض وقيمة عالية
if (name.includes("/api/checkout")) return 1.0;
// خذ عيّنة دائماً من مسارات الذكاء الاصطناعي لتتمكن من تنقيح سلوك الوكيل
if (name.includes("/api/agent")) return 1.0;
// ورّث قرار الأثر الأب في الآثار الموزّعة
if (samplingContext.parentSampled !== undefined) {
return samplingContext.parentSampled ? 1.0 : 0;
}
return 0.05;
},
});spans مخصّصة
يغطي التجهيز التلقائي بروتوكول HTTP ومشغّلات قواعد البيانات وعرض React. أما ما كتبته بنفسك فيبقى غير مرئي حتى تغلّفه:
import * as Sentry from "@sentry/nextjs";
export async function generateMonthlyReport(orgId: string) {
return Sentry.startSpan(
{
name: "generateMonthlyReport",
op: "task.report",
attributes: { orgId },
},
async (span) => {
const rows = await Sentry.startSpan(
{ name: "fetch invoice rows", op: "db.query" },
() => db.invoice.findMany({ where: { orgId } }),
);
span.setAttribute("row_count", rows.length);
const pdf = await Sentry.startSpan(
{ name: "render pdf", op: "task.render" },
() => renderPdf(rows),
);
return pdf;
},
);
}الآن لم يعد التقرير البطيء "استغرقت نقطة النهاية تسع ثوانٍ"، بل "استغرق استعلام قاعدة البيانات 400 مللي ثانية واستغرق توليد PDF نحو 8.6 ثانية". هذا فارق قابل للتنفيذ. وإن كنت تشغّل OpenTelemetry أصلاً فإن Sentry يستهلك spans الخاصة به مباشرة؛ ويغطي دليل التتبّع بـ OpenTelemetry في Next.js هذا المسار.
الخطوة 6: سجلات مهيكلة مرتبطة بالآثار
مع enableLogs: true يمنحك Sentry مسجِّلاً تُربط مخرجاته تلقائياً بالأثر النشط:
import * as Sentry from "@sentry/nextjs";
const { logger } = Sentry;
export async function processPayment(orderId: string, amountCents: number) {
logger.info("payment started", { orderId, amountCents });
try {
const result = await stripe.paymentIntents.create({
amount: amountCents,
currency: "usd",
metadata: { orderId },
});
logger.info(logger.fmt`payment ${result.id} succeeded for order ${orderId}`);
return result;
} catch (error) {
logger.error("payment failed", {
orderId,
code: (error as { code?: string }).code,
});
throw error;
}
}ولالتقاط استدعاءات console.* القائمة من دون إعادة كتابتها، أضف تكامل الـ console:
integrations: [
Sentry.consoleLoggingIntegration({ levels: ["warn", "error"] }),
],أبقِ console.log خارج تلك القائمة. سجلات التنقيح بحجم الإنتاج ستستهلك حصتك في يوم واحد، كما أن قواعد جودة الشيفرة تدفعك أصلاً نحو مسجِّل حقيقي بدل استدعاءات console.log المتناثرة.
الخطوة 7: Session Replay من دون تسريب البيانات
الـ Replay هي الميزة التي تحوّل "المستخدم يقول إن الزر لم يفعل شيئاً" إلى مقطع مدته ثلاثون ثانية يُظهر ذلك بالضبط. وهي أيضاً الميزة الأكثر احتمالاً لأن توقعك في مشكلة مع مراجعة الخصوصية، لذا اضبطها بوعي.
الإعدادات الافتراضية في الخطوة 2 تُخفي كل النصوص وتحجب كل الوسائط. هذه نقطة الانطلاق الصحيحة: أخفِ كل شيء، ثم أظهر ما هو آمن بشكل انتقائي.
// النص داخل هذا العنصر سيكون مرئياً في التسجيلات
<h1 data-sentry-unmask>Monthly Revenue</h1>
// إخفاء قيمة صراحةً حتى لو كان الإظهار مفعّلاً في مكان آخر
<span data-sentry-mask>{user.taxId}</span>
// إزالة عنصر من التسجيل بالكامل
<div data-sentry-block>
<CreditCardForm />
</div>بالنسبة للحقول، فإن maskAllInputs مفعّل افتراضياً — اتركه كذلك. تسجيل يلتقط حقل كلمة مرور هو حادث أمني، لا أداة تنقيح.
الخطوة 8: مراقبة وكلاء الذكاء الاصطناعي
هنا تجاوز Sentry أدوات مراقبة الأداء التقليدية بمسافة. إن كان تطبيقك يستدعي نموذجاً لغوياً — خصوصاً عبر حلقة وكيل مع استدعاءات أدوات — فأنت بحاجة إلى رؤية النموذج وعدد الرموز والتكلفة، وأي استدعاء أداة جعل التشغيلة تستغرق أربعين ثانية.
فعّل تكامل Vercel AI في تهيئة الخادم:
// sentry.server.config.ts
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
enableLogs: true,
integrations: [
Sentry.vercelAIIntegration({
recordInputs: process.env.NODE_ENV !== "production",
recordOutputs: process.env.NODE_ENV !== "production",
}),
],
});ثم فعّل القياس على كل استدعاء لـ AI SDK. القيمة functionId هي ما يجمع التشغيلات معاً في اللوحة، لذا حافظ على ثباتها:
// app/api/agent/route.ts
import { generateText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
export async function POST(req: Request) {
const { question } = await req.json();
const result = await generateText({
model: anthropic("claude-sonnet-5"),
prompt: question,
tools: {
lookupOrder: tool({
description: "Look up an order by its ID",
inputSchema: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => db.order.findUnique({ where: { id: orderId } }),
}),
},
stopWhen: ({ steps }) => steps.length >= 8,
experimental_telemetry: {
isEnabled: true,
functionId: "support-agent",
metadata: { tenant: "acme" },
},
});
return Response.json({ answer: result.text });
}ما تحصل عليه في واجهة AI Agents: span لكل استدعاء نموذج مع عدد رموز الإدخال والإخراج، وspan لكل استدعاء أداة مع وسائطه ومدّته، وتكلفة محسوبة لكل تشغيلة بناءً على تسعير النموذج. وحين تكلّف تشغيلة الوكيل أربعين سنتاً بدل ثلاثة، يمكنك أن ترى أي خطوة فعلت ذلك.
ملاحظتان إنتاجيتان. أولاً، اضبط recordInputs وrecordOutputs على false في الإنتاج ما لم تكن قد قرّرت أن تخزين المطالبات والإكمالات آمن — مطالبات المستخدمين تحتوي روتينياً على بيانات شخصية. ثانياً، spans استدعاءات الأدوات هي المشتبه المعتاد وراء بطء الوكلاء؛ استعلام قاعدة بيانات واحد بلا فهرس داخل أداة تُنفَّذ ثماني مرات في التشغيلة نمط شائع جداً.
ولتقييم المطالبات وإدارة نسخها بدل تتبّع البنية التحتية، اجمع هذا مع مراقبة النماذج اللغوية عبر Langfuse — فكلاهما يجيب عن سؤال مختلف ويتعايشان بلا تعارض.
الخطوة 9: مراقبة المهام الخلفية
الخطأ الذي لا يقع أبداً هو الأصعب ملاحظةً. إن توقّفت مهمة الفوترة الليلية عن العمل فلن يُرمى أي استثناء — المهمة ببساطة غير موجودة. تحلّ مراقبة الـ cron هذه المشكلة بالتنبيه عند الغياب.
// app/api/cron/reconcile/route.ts
import * as Sentry from "@sentry/nextjs";
export async function GET() {
return Sentry.withMonitor(
"nightly-reconcile",
async () => {
const count = await reconcileInvoices();
return Response.json({ reconciled: count });
},
{
schedule: { type: "crontab", value: "0 3 * * *" },
checkinMargin: 10, // نبّه إن لم تبدأ بعد عشر دقائق من موعدها
maxRuntime: 30, // نبّه إن تجاوز تشغيلها ثلاثين دقيقة
timezone: "Africa/Tunis",
},
);
}صار Sentry الآن يعرف أن المهمة متوقعة عند الساعة 03:00 يومياً، ويفتح مشكلة إن فاتت تشغيلة أو فشلت أو تعلّقت. ومع automaticVercelMonitors: true من الخطوة 3 تُنشأ مراقبات تلقائياً للمهام المعرّفة في vercel.json. وللتنسيق الأثقل راجع درسَي مهام Trigger.dev v4 الخلفية ودوال Inngest الدائمة، وكلاهما يُبلغ Sentry عبر تهيئة الخادم نفسها.
الخطوة 10: الإصدارات وخرائط المصدر في CI
آثار المكدّس لا تكون مقروءة إلا إذا طابقت خرائط المصدر المرفوعة الحزمة المنشورة بالضبط. اربط الاثنين بمعرّف إصدار — وتجزئة الـ commit هي الخيار البديهي:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # مطلوب لربط الـ commits
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: your-org-slug
SENTRY_PROJECT: your-project-slug
NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}لأن withSentryConfig تعمل داخل next build، يحدث رفع خرائط المصدر تلقائياً ما دام SENTRY_AUTH_TOKEN متوفراً. وإن كان المتغير مفقوداً فسينجح البناء رغم ذلك — لكنه سيشحن بصمت آثاراً غير مقروءة، ولهذا يهمّ silent: !process.env.CI: في CI تريد سجل الرفع ظاهراً. ويغطي درس CI/CD عبر GitHub Actions خط الأنابيب المحيط بتفصيل أكبر.
الخطوة 11: التحكم في الضجيج والتكلفة
تثبيت Sentry بإعداداته الافتراضية على تطبيق مزدحم سيغرقك خلال أسبوع. ثلاثة مرشّحات تنجز معظم العمل.
Sentry.init({
// 1. تجاهل الأخطاء المعروفة غير المهمة قبل مغادرتها المتصفح
ignoreErrors: [
"ResizeObserver loop limit exceeded",
"Non-Error promise rejection captured",
/^Network request failed$/,
"AbortError",
],
// 2. تجاهل الأخطاء الناشئة عن سكربتات طرف ثالث
denyUrls: [/extensions\//i, /^chrome:\/\//i, /googletagmanager\.com/],
// 3. التنظيف والتصفية برمجياً
beforeSend(event, hint) {
const error = hint.originalException;
// لا تُبلغ أبداً عن عمليات إعادة التوجيه المتوقعة
if (error instanceof Error && error.message.includes("NEXT_REDIRECT")) {
return null;
}
// إزالة ترويسة مصادقة تسرّبت إلى بيانات الطلب
if (event.request?.headers) {
delete event.request.headers["authorization"];
delete event.request.headers["cookie"];
}
return event;
},
});يستحق NEXT_REDIRECT وNEXT_NOT_FOUND انتباهاً خاصاً: ينفّذ Next.js الدالتين redirect() وnotFound() عبر رمي استثناء. تُرشّح النسخ الحديثة من الحزمة هذه الحالات تلقائياً، لكنها قد تعود كأخطاء وهمية إن أعدت رميها من كتل catch خاصة بك. أعد الرمي دائماً بدل الالتقاط:
try {
await doWork();
} catch (error) {
// اترك أخطاء التحكم في تدفق إطار العمل تمرّ دون مساس
if (error instanceof Error && error.message.startsWith("NEXT_")) throw error;
Sentry.captureException(error);
throw error;
}اختبار التنفيذ
أضف مساراً يفشل عمداً:
// app/api/sentry-check/route.ts
export async function GET() {
throw new Error("Sentry server test — safe to ignore");
}وزرّاً على جهة العميل:
"use client";
export function BreakThings() {
return (
<button onClick={() => { throw new Error("Sentry client test"); }}>
Break things
</button>
);
}ثم امرّ على قائمة التحقق التالية:
- شغّل
npm run devوافتح/api/sentry-check— تظهر مشكلة خادم خلال ثوانٍ. - اضغط الزر — تظهر مشكلة عميل مرفقة بتسجيل جلسة.
- افتح المشكلة وتأكد أن أثر المكدّس يعرض شيفرة
.tsالأصلية لا مخرجات مُصغَّرة. إن لم يفعل فخرائط المصدر لم تُرفع. - افتح Traces وتأكد أن معاملة تحميل الصفحة تحتوي spans فرعية لاستعلامات قاعدة بياناتك.
- افتح Logs وتأكد من ظهور استدعاءات
logger.infoمرتبطة بالأثر. - شغّل مسار الذكاء الاصطناعي وتأكد أن واجهة AI Agents تعرض النموذج والرموز وspans الأدوات.
- ابنِ نسخة إنتاج وتحقق أن مجلد
_next/staticالمنشور لا يحتوي أي ملفات.map.
حل المشكلات
لا يصل شيء من المتصفح. غالباً مانع إعلانات. تأكد بفتح تبويب الشبكة والبحث عن طلب محجوب إلى ingest.sentry.io، ثم اضبط tunnelRoute.
أخطاء Server Components لا تظهر أبداً. ينقصك export const onRequestError = Sentry.captureRequestError في instrumentation.ts.
آثار المكدّس مُصغَّرة. إما أن SENTRY_AUTH_TOKEN كان غائباً وقت البناء، أو أن قيمة release تختلف بين البناء ووقت التشغيل. راجع Settings ← Source Maps في Sentry، فهي تعرض الملفات المرفوعة لكل إصدار.
الآثار بلا spans فرعية. إما أن tracesSampleRate يساوي صفراً في تلك البيئة، أو أن التجهيز التلقائي لا يستطيع تعديل عميل قاعدة بياناتك لأنه استُورد قبل تنفيذ Sentry.init. استيراد العميل بشكل كسول داخل الدالة يحل ذلك عادةً.
بيئة Edge ترمي أخطاءً عن واجهات Node مفقودة. وضعت تكاملاً خاصاً بـ Node في sentry.edge.config.ts. أبقِ ذلك الملف بسيطاً.
استنفاد الحصة في منتصف الشهر. خفّض tracesSampleRate، واضبط replaysSessionSampleRate على صفر واعتمد على replaysOnErrorSampleRate، وأضف أكثر الأخطاء ضجيجاً إلى ignoreErrors. تُطبَّق نسب أخذ العيّنات لكل نوع حدث على حدة، فاضبط كلاً منها بشكل مستقل.
الخطوات التالية
- أضف تحليلات المنتج إلى جانب بيانات الأخطاء عبر درس PostHog للتحليلات وأعلام الميزات — سياق العلم على مشكلة في Sentry يخبرك إن كان إطلاق تدريجي هو السبب.
- اربط فحوص الجاهزية والاختبارات الاصطناعية عبر اختبارات Playwright الشاملة في CI كي تُلتقط الأعطال قبل أن يجدها المستخدمون.
- وسّع تجهيز الوكلاء عبر دليل Vercel AI SDK 7 مع الـ harness وبيئة الـ sandbox.
- اضبط قواعد التنبيه: قاعدة كشف ارتفاع مفاجئ في معدل الأخطاء وقاعدة عتبة على زمن المعاملة عند المئين 95 تغطيان معظم الحوادث الحقيقية.
الخلاصة
الإعداد هنا نحو أربعين سطر تهيئة موزّعة على خمسة ملفات، وهو يغيّر شكل كل حادث إنتاج ستتعامل معه. فبدل إعادة بناء ما حدث من بلاغات المستخدمين وgrep، تفتح المشكلة فتجد أثر المكدّس، وشلال الأثر، وسجلات ذلك الطلب بالتحديد، وتسجيلاً لجلسة المستخدم.
ثلاثة أمور تستحق التنفيذ اليوم حتى لو تجاوزت كل ما عداها: صدّر onRequestError كي تُلتقط أخطاء Server Components أصلاً، واضبط tunnelRoute كي تكون أرقام المتصفح صادقة، وتأكد أن خرائط المصدر تُرفع فعلاً. هذه الثلاثة تفسّر معظم الفرق بين تثبيت Sentry يعمل وآخر لا يعمل بصمت.