أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

AsyncLocalStorage بالعربي: requestId في كل log بدون تمريره يدويًا

📅 ٢٥ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
AsyncLocalStorage بالعربي: requestId في كل log بدون تمريره يدويًا

AsyncLocalStorage بالعربي: requestId في كل log بدون تمريره يدويًا

هتكسب من المقال ده طريقة عملية تربط كل log في طلب Node.js بنفس requestId، بدل ما تفضل تمرره يدويًا بين controllers وservices وdatabase calls.

مستوى القارئ: متوسط

المشكلة باختصار

اللي بيحصل فعلاً في APIs الكبيرة إن الطلب الواحد يدخل Express، يعدّي على middleware، ينادي service، ينادي database، وبعدها يظهر خطأ في log بعيد عن بداية الطلب. لو اللوجات مش مرتبطة بنفس requestId، التشخيص بيتحول لتخمين.

سيناريو واقعي: عندك API بيخدم 50 ألف طلب في اليوم. 2% فقط بيرجعوا 500، يعني حوالي 1000 طلب محتاج تتبّع. لو 38% من اللوجات ناقصها requestId، فريقك هيضيّع وقت في ربط الأحداث يدويًا. في فريق صغير، ده ممكن يحول bug مدته 12 دقيقة إلى 45 دقيقة.

مخطط يوضح انتقال requestId من Express middleware إلى AsyncLocalStorage ثم طبقات الخدمة واللوجات

مثال بسيط قبل التعريف العلمي

ركز في المثال ده: بدل ما تبعت requestId كوسيط لكل دالة، بتحطه مرة واحدة في بداية الطلب. بعد كده أي كود asynchronous داخل نفس الطلب يقدر يقرأه.

JavaScript
import express from 'express';
import crypto from 'node:crypto';
import { AsyncLocalStorage } from 'node:async_hooks';

const app = express();
const requestContext = new AsyncLocalStorage();

function log(message, fields = {}) {
  const store = requestContext.getStore() || {};
  console.log(JSON.stringify({
    level: 'info',
    requestId: store.requestId,
    ...fields,
    message
  }));
}

app.use((req, res, next) => {
  const requestId = req.get('x-request-id') || crypto.randomUUID();
  requestContext.run({ requestId }, () => next());
});

async function loadUser(userId) {
  log('loading user', { userId });
  return { id: userId, plan: 'pro' };
}

app.get('/users/:id', async (req, res) => {
  const user = await loadUser(req.params.id);
  log('user loaded');
  res.json(user);
});

app.listen(3000);

لو طلبين دخلوا في نفس الوقت، كل واحد هيشوف requestId الخاص به. Node.js يحافظ على السياق خلال promise chains وcallbacks المرتبطة بنفس العملية asynchronous.

ما هو AsyncLocalStorage بالظبط؟

AsyncLocalStorage كلاس رسمي في Node.js داخل node:async_hooks. فكرته قريبة من thread-local storage في لغات تانية، لكن مناسبة لطبيعة Node.js القائمة على event loop وasync callbacks.

التعريف العلمي: هو مساحة تخزين مرتبطة بسياق asynchronous معين. لما تستدعي run(store, callback)، أي كود asynchronous يبدأ من داخل هذا callback يقدر يسترجع نفس store باستخدام getStore(). ده مفيد للـ request context، tracing، tenantId، correlationId، وتجميع metadata للوجات.

الطريقة الشائعة الغلط هي إنك تمرر requestId في كل function signature. الطريقة دي بتفشل لما الكود يكبر، لأنك بتلوّث signatures، وبتنسى تمرر القيمة في helper واحد، فتظهر لوجات ناقصة في أسوأ وقت.

أفضل طريقة لاستخدامه في API حقيقي

  1. استقبل x-request-id من gateway أو load balancer لو موجود.
  2. لو مش موجود، أنشئ UUID في أول middleware.
  3. استخدم requestContext.run({ requestId }, () => next()) داخل middleware واحد فقط.
  4. اكتب wrapper صغير للوجات يقرأ getStore().
  5. اختبر endpoint بطلبين متوازيين وتأكد إن كل response له requestId مختلف.

لو بتستخدم Pino، ممكن تعمل logger wrapper يضيف requestId كحقل JSON. المكسب: البحث في Loki أو Datadog أو Elastic يبقى مباشرًا. التكلفة: لازم تمنع أي كود من استخدام console.log الخام داخل المشروع.

قياس قبل وبعد

في API متوسط، القياس العملي ممكن يكون بسيطًا: شغّل load test صغير بـ 1000 طلب، وافتح عينة من اللوجات. قبل AsyncLocalStorage قد تلاقي 30% إلى 40% من لوجات الخدمات العميقة بدون requestId. بعد تطبيق wrapper موحد، النسبة المفروض تنزل لأقل من 5%، وغالبًا أقل من 2% لو منعت اللوج الخام.

رسم أعمدة يقارن نسبة اللوجات بدون requestId وزمن التشخيص قبل وبعد استخدام AsyncLocalStorage

الـ trade-off هنا واضح: بتكسب تتبعًا أنظف وdebug أسرع، وبتخسر جزءًا بسيطًا من البساطة لأن عندك context global على مستوى module. لذلك خليه للبيانات التشغيلية العابرة، مش business state.

متى لا تستخدم هذه الطريقة

لا تستخدم AsyncLocalStorage لتخزين بيانات كبيرة مثل user object كامل أو cart كامل. خزن معرفات صغيرة فقط: requestId، tenantId، userId. ولا تعتمد عليه كبديل عن تمرير البيانات المهمة للـ domain logic. لو الدالة تحتاج userId لاتخاذ قرار تجاري، مرره صراحة. AsyncLocalStorage مناسب للتتبع واللوجات، مش لإخفاء dependencies.

كمان خليك حذر مع بعض المكتبات القديمة أو callbacks غير المعتادة. لو السياق ضاع، راجع توثيق Node.js الخاص بفقدان السياق واستخدم AsyncResource عند الحاجة.

مصادر راجعت عليها

  • Node.js Async Context وAsyncLocalStorage: https://nodejs.org/api/async_context.html
  • Express middleware guide: https://expressjs.com/en/guide/using-middleware.html
  • Pino API وlogger fields: https://github.com/pinojs/pino/blob/main/docs/api.md

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

افتح API واحدة عندك، أضف middleware للـ requestId، وبعدها امنع console.log الخام في طبقة services. لو لقيت أي log بدون requestId بعد 1000 طلب، ابدأ من المكان ده بالظبط.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة