دليل شامل لإعداد OpenTelemetry مع Next.js 15 للمراقبة والتتبع في الإنتاج

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

مقدمة

في عالم التطبيقات الحديثة، لا يكفي أن يعمل تطبيقك — بل يجب أن تعرف كيف يعمل. هل الاستجابات بطيئة؟ أين يقضي الخادم معظم وقته؟ ما هي نقاط الاختناق؟ هنا يأتي دور OpenTelemetry (OTel) — المعيار المفتوح للمراقبة والتتبع الموزع.

في هذا الدليل الشامل، ستتعلم كيفية:

  • إعداد OpenTelemetry مع Next.js 15 من الصفر
  • تتبع طلبات API والصفحات تلقائياً
  • إضافة spans مخصصة لقياس أداء العمليات الحرجة
  • إرسال البيانات إلى Jaeger للتتبع وPrometheus للمقاييس
  • عرض كل شيء في لوحة Grafana متكاملة
  • نشر الإعداد في بيئة الإنتاج مع Docker Compose

المتطلبات المسبقة

قبل البدء، تأكد من توفر التالي:

  • Node.js 20+ مُثبت على جهازك
  • Docker و Docker Compose للخدمات المحلية
  • معرفة أساسية بـ Next.js و TypeScript
  • معرفة بمفاهيم HTTP وREST APIs
  • محرر أكواد (VS Code مُوصى به)

ماذا ستبني؟

ستبني تطبيق Next.js 15 مع نظام مراقبة متكامل يشمل:

  1. تتبع تلقائي لجميع طلبات HTTP والصفحات
  2. Spans مخصصة لعمليات قاعدة البيانات والخدمات الخارجية
  3. مقاييس أداء مثل زمن الاستجابة ومعدل الأخطاء
  4. لوحة مراقبة متكاملة في Grafana
  5. تنبيهات عند تجاوز حدود الأداء

الخطوة 1: إنشاء مشروع Next.js 15

ابدأ بإنشاء مشروع Next.js جديد:

npx create-next-app@latest otel-nextjs-demo --typescript --tailwind --app --src-dir
cd otel-nextjs-demo

اختر الإعدادات الافتراضية عند السؤال. بعد انتهاء التثبيت، تأكد من عمل المشروع:

npm run dev

الخطوة 2: تثبيت حزم OpenTelemetry

قم بتثبيت الحزم المطلوبة لـ OpenTelemetry:

npm install @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/sdk-metrics \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/exporter-metrics-otlp-http \
  @opentelemetry/instrumentation-http \
  @opentelemetry/instrumentation-fetch \
  @vercel/otel

حزمة @vercel/otel توفر تكاملاً مُحسَّناً مع Next.js وتُسهّل الإعداد الأولي بشكل كبير. يمكنك استخدامها مع أي مزود OpenTelemetry وليس فقط Vercel.

الخطوة 3: إعداد OpenTelemetry في Next.js

تفعيل الميزة التجريبية

أولاً، فعّل دعم OpenTelemetry في next.config.ts:

// next.config.ts
import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  experimental: {
    instrumentationHook: true,
  },
};
 
export default nextConfig;

إنشاء ملف الأداة (Instrumentation)

أنشئ ملف src/instrumentation.ts — هذا هو نقطة الدخول لـ OpenTelemetry:

// src/instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { NodeSDK } = await import("@opentelemetry/sdk-node");
    const { OTLPTraceExporter } = await import(
      "@opentelemetry/exporter-trace-otlp-http"
    );
    const { OTLPMetricExporter } = await import(
      "@opentelemetry/exporter-metrics-otlp-http"
    );
    const { PeriodicExportingMetricReader } = await import(
      "@opentelemetry/sdk-metrics"
    );
    const { Resource } = await import("@opentelemetry/resources");
    const {
      ATTR_SERVICE_NAME,
      ATTR_SERVICE_VERSION,
      ATTR_DEPLOYMENT_ENVIRONMENT_NAME,
    } = await import("@opentelemetry/semantic-conventions");
    const { HttpInstrumentation } = await import(
      "@opentelemetry/instrumentation-http"
    );
 
    const resource = new Resource({
      [ATTR_SERVICE_NAME]: "nextjs-otel-demo",
      [ATTR_SERVICE_VERSION]: "1.0.0",
      [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]:
        process.env.NODE_ENV || "development",
    });
 
    const traceExporter = new OTLPTraceExporter({
      url:
        process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
        "http://localhost:4318/v1/traces",
    });
 
    const metricExporter = new OTLPMetricExporter({
      url:
        process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
        "http://localhost:4318/v1/metrics",
    });
 
    const sdk = new NodeSDK({
      resource,
      traceExporter,
      metricReader: new PeriodicExportingMetricReader({
        exporter: metricExporter,
        exportIntervalMillis: 10000,
      }),
      instrumentations: [new HttpInstrumentation()],
    });
 
    sdk.start();
 
    process.on("SIGTERM", () => {
      sdk.shutdown().then(
        () => console.log("OpenTelemetry SDK shut down successfully"),
        (err) => console.error("Error shutting down OpenTelemetry SDK", err)
      );
    });
  }
}

الشرط process.env.NEXT_RUNTIME === "nodejs" ضروري لأن OpenTelemetry SDK يعمل فقط في بيئة Node.js ولا يدعم Edge Runtime. بدون هذا الشرط، سيفشل التطبيق عند استخدام Middleware أو Edge Functions.

الخطوة 4: إعداد خدمات المراقبة بـ Docker Compose

أنشئ ملف docker-compose.yml في جذر المشروع لتشغيل Jaeger وPrometheus وGrafana وOpenTelemetry Collector:

# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4317:4317"   # gRPC
      - "4318:4318"   # HTTP
      - "8889:8889"   # Prometheus metrics
    depends_on:
      - jaeger
 
  jaeger:
    image: jaegertracing/all-in-one:latest
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - "16686:16686" # Jaeger UI
      - "14268:14268" # Jaeger collector
 
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"
 
  grafana:
    image: grafana/grafana:latest
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin123
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "3001:3000"
    depends_on:
      - prometheus
      - jaeger
 
volumes:
  grafana-data:

إعداد OpenTelemetry Collector

أنشئ ملف otel-collector-config.yml:

# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
 
processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
 
exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "nextjs"
 
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/jaeger]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]

إعداد Prometheus

أنشئ ملف prometheus.yml:

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8889"]
  - job_name: "nextjs-app"
    static_configs:
      - targets: ["host.docker.internal:3000"]

شغّل جميع الخدمات:

docker compose up -d

الخطوة 5: إضافة Spans مخصصة

إنشاء مساعد التتبع

أنشئ ملف src/lib/tracing.ts لتوفير أدوات تتبع سهلة الاستخدام:

// src/lib/tracing.ts
import { trace, SpanStatusCode, Span, context } from "@opentelemetry/api";
 
const tracer = trace.getTracer("nextjs-app", "1.0.0");
 
export function withSpan<T>(
  name: string,
  fn: (span: Span) => Promise<T>,
  attributes?: Record<string, string | number | boolean>
): Promise<T> {
  return tracer.startActiveSpan(name, async (span) => {
    try {
      if (attributes) {
        Object.entries(attributes).forEach(([key, value]) => {
          span.setAttribute(key, value);
        });
      }
      const result = await fn(span);
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error instanceof Error ? error.message : "Unknown error",
      });
      span.recordException(error as Error);
      throw error;
    } finally {
      span.end();
    }
  });
}
 
export function createDbSpan<T>(
  operation: string,
  table: string,
  fn: (span: Span) => Promise<T>
): Promise<T> {
  return withSpan(`db.${operation}`, fn, {
    "db.system": "postgresql",
    "db.operation": operation,
    "db.sql.table": table,
  });
}
 
export function createExternalApiSpan<T>(
  service: string,
  endpoint: string,
  fn: (span: Span) => Promise<T>
): Promise<T> {
  return withSpan(`external.${service}`, fn, {
    "http.url": endpoint,
    "peer.service": service,
  });
}
 
export { tracer };

استخدام Spans في API Routes

أنشئ مسار API يستخدم التتبع المخصص:

// src/app/api/users/route.ts
import { NextResponse } from "next/server";
import { withSpan, createDbSpan } from "@/lib/tracing";
 
// محاكاة قاعدة بيانات
async function fetchUsersFromDb() {
  return createDbSpan("SELECT", "users", async (span) => {
    // محاكاة تأخير قاعدة البيانات
    await new Promise((resolve) => setTimeout(resolve, 50));
 
    span.setAttribute("db.rows_affected", 10);
 
    return [
      { id: 1, name: "أحمد", email: "ahmed@example.com" },
      { id: 2, name: "سارة", email: "sara@example.com" },
      { id: 3, name: "محمد", email: "mohamed@example.com" },
    ];
  });
}
 
async function enrichUserData(users: any[]) {
  return withSpan(
    "enrichUserData",
    async (span) => {
      span.setAttribute("users.count", users.length);
 
      // محاكاة إثراء البيانات من خدمة خارجية
      await new Promise((resolve) => setTimeout(resolve, 30));
 
      return users.map((user) => ({
        ...user,
        avatar: `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`,
      }));
    },
    { "enrichment.source": "dicebear" }
  );
}
 
export async function GET() {
  return withSpan("GET /api/users", async (span) => {
    span.setAttribute("http.method", "GET");
    span.setAttribute("http.route", "/api/users");
 
    const users = await fetchUsersFromDb();
    const enrichedUsers = await enrichUserData(users);
 
    span.setAttribute("response.count", enrichedUsers.length);
 
    return NextResponse.json({
      users: enrichedUsers,
      total: enrichedUsers.length,
    });
  });
}

الخطوة 6: إضافة مقاييس مخصصة

أنشئ ملف src/lib/metrics.ts لتعريف المقاييس:

// src/lib/metrics.ts
import { metrics } from "@opentelemetry/api";
 
const meter = metrics.getMeter("nextjs-app", "1.0.0");
 
// عداد الطلبات
export const requestCounter = meter.createCounter("http_requests_total", {
  description: "إجمالي عدد طلبات HTTP",
  unit: "requests",
});
 
// مقياس زمن الاستجابة
export const responseTimeHistogram = meter.createHistogram(
  "http_response_duration_ms",
  {
    description: "مدة استجابة HTTP بالميلي ثانية",
    unit: "ms",
  }
);
 
// عداد الأخطاء
export const errorCounter = meter.createCounter("http_errors_total", {
  description: "إجمالي عدد أخطاء HTTP",
  unit: "errors",
});
 
// مقياس الاتصالات النشطة
export const activeConnections = meter.createUpDownCounter(
  "active_connections",
  {
    description: "عدد الاتصالات النشطة حالياً",
    unit: "connections",
  }
);

دمج المقاييس في Middleware

أنشئ middleware لتسجيل المقاييس تلقائياً:

// src/middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
 
export function middleware(request: NextRequest) {
  const start = Date.now();
  const response = NextResponse.next();
 
  // إضافة رؤوس التتبع
  response.headers.set("X-Request-Start", start.toString());
  response.headers.set(
    "X-Request-Id",
    crypto.randomUUID()
  );
 
  return response;
}
 
export const config = {
  matcher: ["/api/:path*", "/((?!_next/static|_next/image|favicon.ico).*)"],
};

إنشاء API Route مع مقاييس كاملة

// src/app/api/health/route.ts
import { NextResponse } from "next/server";
import {
  requestCounter,
  responseTimeHistogram,
  errorCounter,
} from "@/lib/metrics";
import { withSpan } from "@/lib/tracing";
 
export async function GET() {
  const start = Date.now();
 
  return withSpan("GET /api/health", async (span) => {
    try {
      // تسجيل الطلب
      requestCounter.add(1, {
        method: "GET",
        route: "/api/health",
      });
 
      // محاكاة فحص صحة الخدمات
      const checks = {
        database: "healthy",
        cache: "healthy",
        external_api: "healthy",
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
      };
 
      // تسجيل زمن الاستجابة
      const duration = Date.now() - start;
      responseTimeHistogram.record(duration, {
        method: "GET",
        route: "/api/health",
        status: "200",
      });
 
      span.setAttribute("health.status", "healthy");
      span.setAttribute("health.duration_ms", duration);
 
      return NextResponse.json(checks);
    } catch (error) {
      errorCounter.add(1, {
        method: "GET",
        route: "/api/health",
        error_type: "health_check_failed",
      });
      throw error;
    }
  });
}

الخطوة 7: إنشاء صفحة لوحة المراقبة

أنشئ صفحة بسيطة لعرض حالة المراقبة:

// src/app/dashboard/page.tsx
"use client";
 
import { useEffect, useState } from "react";
 
interface HealthStatus {
  database: string;
  cache: string;
  external_api: string;
  uptime: number;
  timestamp: string;
}
 
export default function DashboardPage() {
  const [health, setHealth] = useState<HealthStatus | null>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    const fetchHealth = async () => {
      try {
        const res = await fetch("/api/health");
        const data = await res.json();
        setHealth(data);
      } catch (err) {
        console.error("Failed to fetch health:", err);
      } finally {
        setLoading(false);
      }
    };
 
    fetchHealth();
    const interval = setInterval(fetchHealth, 5000);
    return () => clearInterval(interval);
  }, []);
 
  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
      </div>
    );
  }
 
  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">
        لوحة المراقبة
      </h1>
 
      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
        {health && (
          <>
            <StatusCard
              title="قاعدة البيانات"
              status={health.database}
            />
            <StatusCard
              title="التخزين المؤقت"
              status={health.cache}
            />
            <StatusCard
              title="API الخارجي"
              status={health.external_api}
            />
          </>
        )}
      </div>
 
      <div className="bg-gray-900 rounded-lg p-6">
        <h2 className="text-xl font-semibold mb-4">
          روابط سريعة
        </h2>
        <div className="space-y-3">
          <ExternalLink
            href="http://localhost:16686"
            label="Jaeger UI — عرض التتبعات"
          />
          <ExternalLink
            href="http://localhost:9090"
            label="Prometheus — استعلام المقاييس"
          />
          <ExternalLink
            href="http://localhost:3001"
            label="Grafana — لوحات المراقبة"
          />
        </div>
      </div>
    </div>
  );
}
 
function StatusCard({
  title,
  status,
}: {
  title: string;
  status: string;
}) {
  const isHealthy = status === "healthy";
  return (
    <div className="bg-gray-800 rounded-lg p-6">
      <h3 className="text-sm font-medium text-gray-400 mb-2">
        {title}
      </h3>
      <div className="flex items-center gap-2">
        <div
          className={`w-3 h-3 rounded-full ${
            isHealthy ? "bg-green-500" : "bg-red-500"
          }`}
        />
        <span
          className={
            isHealthy ? "text-green-400" : "text-red-400"
          }
        >
          {isHealthy ? "سليم" : "معطل"}
        </span>
      </div>
    </div>
  );
}
 
function ExternalLink({
  href,
  label,
}: {
  href: string;
  label: string;
}) {
  return (
    <a
      href={href}
      target="_blank"
      rel="noopener noreferrer"
      className="flex items-center gap-2 text-blue-400 hover:text-blue-300 transition-colors"
    >
      <span>←</span>
      {label}
    </a>
  );
}

الخطوة 8: إعداد Grafana Dashboard

بعد تشغيل الخدمات بـ docker compose up -d، افتح Grafana على http://localhost:3001 وسجّل الدخول بـ admin / admin123.

إضافة مصادر البيانات

  1. اذهب إلى Configuration ثم Data Sources
  2. أضف Prometheus بعنوان: http://prometheus:9090
  3. أضف Jaeger بعنوان: http://jaeger:16686

إنشاء لوحة مراقبة مخصصة

أنشئ ملف grafana-dashboard.json لاستيراده في Grafana:

{
  "dashboard": {
    "title": "Next.js Application Monitoring",
    "panels": [
      {
        "title": "Request Rate",
        "type": "timeseries",
        "targets": [
          {
            "expr": "rate(nextjs_http_requests_total[5m])",
            "legendFormat": "Requests/sec"
          }
        ]
      },
      {
        "title": "Response Time (p95)",
        "type": "gauge",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m]))",
            "legendFormat": "p95 Latency"
          }
        ]
      },
      {
        "title": "Error Rate",
        "type": "stat",
        "targets": [
          {
            "expr": "rate(nextjs_http_errors_total[5m])",
            "legendFormat": "Errors/sec"
          }
        ]
      }
    ]
  }
}

يمكنك استيراد هذا الملف من خلال Grafana UI عبر القائمة Dashboards ثم Import ثم لصق محتوى JSON. ستحصل على لوحة مراقبة جاهزة مع ثلاث لوحات فرعية.

الخطوة 9: التتبع المتقدم — ربط Frontend بـ Backend

لربط تتبعات المتصفح بتتبعات الخادم، أنشئ مكون client-side:

// src/lib/client-tracing.ts
export function createTracedFetch(
  originalFetch: typeof fetch
): typeof fetch {
  return async (input, init) => {
    const requestId = crypto.randomUUID();
    const start = performance.now();
 
    const headers = new Headers(init?.headers);
    headers.set("X-Request-Id", requestId);
    headers.set("X-Client-Start", start.toString());
 
    try {
      const response = await originalFetch(input, {
        ...init,
        headers,
      });
 
      const duration = performance.now() - start;
      console.debug(
        `[Trace] ${
          typeof input === "string" ? input : input.url
        } - ${duration.toFixed(1)}ms`
      );
 
      return response;
    } catch (error) {
      const duration = performance.now() - start;
      console.error(
        `[Trace Error] ${
          typeof input === "string" ? input : input.url
        } - ${duration.toFixed(1)}ms`,
        error
      );
      throw error;
    }
  };
}

استخدام الـ Fetch المُتتبع في المكونات

// src/hooks/useTracedFetch.ts
"use client";
 
import { useCallback } from "react";
import { createTracedFetch } from "@/lib/client-tracing";
 
const tracedFetch = createTracedFetch(fetch);
 
export function useTracedFetch() {
  const fetchWithTrace = useCallback(
    async <T>(url: string, options?: RequestInit): Promise<T> => {
      const response = await tracedFetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      return response.json();
    },
    []
  );
 
  return { fetch: fetchWithTrace };
}

الخطوة 10: إعداد التنبيهات

تنبيهات Prometheus

أنشئ ملف alert-rules.yml:

# alert-rules.yml
groups:
  - name: nextjs-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(nextjs_http_errors_total[5m]) > 0.1
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "معدل أخطاء مرتفع في تطبيق Next.js"
          description: "معدل الأخطاء تجاوز 10% خلال آخر 5 دقائق"
 
      - alert: SlowResponseTime
        expr: histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m])) > 2000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "زمن استجابة بطيء"
          description: "زمن الاستجابة p95 تجاوز 2000 ميلي ثانية"
 
      - alert: HighMemoryUsage
        expr: process_resident_memory_bytes > 512 * 1024 * 1024
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "استخدام ذاكرة مرتفع"
          description: "استخدام الذاكرة تجاوز 512 ميغابايت"

حدّث ملف prometheus.yml لتضمين قواعد التنبيهات:

# prometheus.yml (محدّث)
global:
  scrape_interval: 15s
  evaluation_interval: 15s
 
rule_files:
  - "alert-rules.yml"
 
scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:8889"]

الخطوة 11: إعداد متغيرات البيئة

أنشئ ملف .env.local للتطوير المحلي:

# .env.local
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_SERVICE_NAME=nextjs-otel-demo
OTEL_LOG_LEVEL=info
NODE_ENV=development

ولبيئة الإنتاج، أنشئ .env.production:

# .env.production
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel-collector.yourdomain.com
OTEL_SERVICE_NAME=nextjs-otel-production
OTEL_LOG_LEVEL=warn
NODE_ENV=production

الخطوة 12: اختبار النظام بالكامل

تشغيل الخدمات

# تشغيل خدمات المراقبة
docker compose up -d
 
# تشغيل تطبيق Next.js
npm run dev

توليد بيانات تتبع

استخدم curl أو المتصفح لإرسال طلبات:

# طلب واحد
curl http://localhost:3000/api/users
 
# طلب صحة النظام
curl http://localhost:3000/api/health
 
# توليد حمل خفيف (10 طلبات)
for i in $(seq 1 10); do
  curl -s http://localhost:3000/api/users > /dev/null
  echo "Request $i sent"
done

التحقق من النتائج

  1. Jaeger — افتح http://localhost:16686

    • اختر الخدمة nextjs-otel-demo
    • سترى تتبعات لكل طلب مع التوقيت التفصيلي
    • انقر على أي تتبع لرؤية شجرة الـ Spans
  2. Prometheus — افتح http://localhost:9090

    • جرّب الاستعلام: nextjs_http_requests_total
    • جرّب: histogram_quantile(0.95, rate(nextjs_http_response_duration_ms_bucket[5m]))
  3. Grafana — افتح http://localhost:3001

    • استورد لوحة المراقبة من ملف JSON
    • شاهد المقاييس في الوقت الفعلي

استكشاف الأخطاء وإصلاحها

لا تظهر تتبعات في Jaeger

  • تأكد من تشغيل OpenTelemetry Collector: docker compose logs otel-collector
  • تحقق من أن OTEL_EXPORTER_OTLP_ENDPOINT يشير إلى العنوان الصحيح
  • تأكد من أن instrumentationHook: true مُفعّل في next.config.ts

المقاييس لا تظهر في Prometheus

  • تحقق من أن Collector يصدّر إلى Prometheus: curl http://localhost:8889/metrics
  • تأكد من إعداد scrape_configs بشكل صحيح في prometheus.yml

أخطاء في بيئة Edge Runtime

  • OpenTelemetry SDK لا يدعم Edge Runtime
  • استخدم الشرط NEXT_RUNTIME === "nodejs" في ملف instrumentation
  • لا تستورد حزم OTel في Middleware أو Edge API Routes

ارتفاع استهلاك الذاكرة

  • قلّل send_batch_size في إعدادات Collector
  • زِد exportIntervalMillis لتقليل تكرار الإرسال
  • استخدم sampling لتقليل حجم البيانات في بيئة الإنتاج

أفضل الممارسات للإنتاج

1. استخدام Sampling

في بيئة الإنتاج، لا تحتاج لتتبع كل طلب. استخدم tail-based sampling:

// في instrumentation.ts
import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-node";
 
const sdk = new NodeSDK({
  // تتبع 10% من الطلبات فقط
  sampler: new TraceIdRatioBasedSampler(0.1),
  // ... باقي الإعدادات
});

2. تجنب التتبع الزائد

  • لا تضف spans لعمليات سريعة جداً (أقل من 1ms)
  • تجنب تسجيل بيانات حساسة في attributes
  • استخدم مستوى تسجيل مناسب لكل بيئة

3. تأمين نقاط النهاية

# في الإنتاج، استخدم TLS وAuthentication
exporters:
  otlp/secure:
    endpoint: https://otel.yourdomain.com:4317
    tls:
      cert_file: /etc/ssl/certs/client.crt
      key_file: /etc/ssl/private/client.key
    headers:
      Authorization: "Bearer YOUR_TOKEN"

4. مراقبة OpenTelemetry Collector نفسه

# إضافة تتبع صحة Collector
extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  zpages:
    endpoint: 0.0.0.0:55679
 
service:
  extensions: [health_check, zpages]

الخطوات التالية

بعد إتمام هذا الدليل، يمكنك التوسع في:

  • التتبع الموزع — ربط تتبعات عبر خدمات متعددة (microservices)
  • Custom Metrics — إضافة مقاييس أعمال مخصصة مثل عدد المستخدمين النشطين
  • Log Correlation — ربط السجلات بالتتبعات باستخدام trace_id
  • Real User Monitoring — تتبع أداء المتصفح باستخدام OpenTelemetry Browser SDK
  • CI/CD Integration — إضافة تتبع لخط أنابيب النشر

خلاصة

في هذا الدليل، تعلمت كيفية:

  1. إعداد OpenTelemetry مع Next.js 15 باستخدام instrumentation hook
  2. تتبع الطلبات تلقائياً وإضافة spans مخصصة لعمليات محددة
  3. قياس المقاييس مثل معدل الطلبات وزمن الاستجابة ومعدل الأخطاء
  4. نشر بنية مراقبة متكاملة مع Jaeger وPrometheus وGrafana
  5. إعداد التنبيهات للكشف عن المشاكل قبل أن يلاحظها المستخدمون

المراقبة الجيدة ليست رفاهية — إنها ضرورة لأي تطبيق في بيئة الإنتاج. مع OpenTelemetry، لديك الآن رؤية كاملة لما يحدث داخل تطبيقك.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على عزز تطبيقات الويب الخاصة بك: دليل المبتدئين لـ Voice JavaScript SDK من Twilio.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

Docker Compose للمطورين: Next.js مع PostgreSQL و Redis

تعلم كيفية تغليف تطبيق Next.js كامل مع PostgreSQL و Redis باستخدام Docker Compose. يغطي هذا الدليل العملي تنسيق الخدمات المتعددة وسير عمل التطوير وإعادة التحميل الفوري وفحوصات الصحة والإعدادات الجاهزة للإنتاج.

28 د قراءة·