كيفية تسجيل الدخول في NodeJS بحيث الأولاد في الفناء الاحترام


ما الذي يثير حنقك أكثر عندما تحاول تنظيم سجلات قابلة للقراءة في تطبيق NodeJS الخاص بك؟ شخصيا ، أنا منزعج للغاية بسبب عدم وجود أي معايير ناضجة عاقل لإنشاء معرفات التتبع. في هذه المقالة ، سنتحدث عن خيارات إنشاء معرّف التتبع ، ولنلقِ نظرة على كيفية عمل التخزين المحلي المستمر أو CLS على أصابعنا وندعو قوة Proxy للحصول على كل شيء مع أي مسجل.


لماذا توجد أي مشكلة في NodeJS مع إنشاء معرف تتبع لكل طلب؟


في الأيام القديمة ، القديمة ، عندما كانت الماموث لا تزال تسير على الأرض ، كانت الخوادم جميعها متعددة الخيوط وأنشأت سلسلة جديدة لطلب. في إطار هذا النموذج ، يكون إنشاء معرف التتبع أمرًا بسيطًا ، لأنه يوجد شيء مثل التخزين المحلي لمؤشر الترابط أو TLS ، والذي يسمح لك بوضع بعض البيانات المتاحة لأي وظيفة في هذا الدفق في الذاكرة. في بداية معالجة الطلب ، يمكنك استرداد معرّف التتبع العشوائي ووضعه في TLS ثم قراءته في أي خدمة والقيام بشيء به. المشكلة هي أن هذا لن ينجح في NodeJS.


NodeJS خيط واحد (ليس تمامًا ، نظرًا لظهور العمال ، ولكن في إطار مشكلة معرّف التتبع ، لا يلعب العمال أي دور) ، لذلك يمكنك نسيان TLS. يختلف النموذج هنا - للتوفيق بين مجموعة من عمليات الاسترجاعات المختلفة داخل نفس مؤشر الترابط ، وبمجرد أن ترغب الوظيفة في القيام بشيء غير متزامن ، أرسل هذا الطلب غير المتزامن ، ثم أعط المعالج وقتًا لوظيفة أخرى في قائمة الانتظار (إذا كنت مهتمًا بكيفية عمل هذا الشيء ، تسمى بفكرة Event Loop تحت غطاء محرك السيارة ، أوصي قراءة هذه السلسلة من المقالات ). إذا كنت تفكر في كيفية فهم NodeJS لأي رد اتصال يمكن الاتصال به عنده ، فيمكنك افتراض أن كل واحد منهم يجب أن يتطابق مع بعض المعرفات. علاوة على ذلك ، لدى NodeJS واجهة برمجة تطبيقات تتيح الوصول إلى هذه المعرفات. سوف نستخدمها.


في العصور القديمة ، عندما انقرضت الماموث ، لكن الناس ما زالوا لا يعرفون فوائد الصرف الصحي المركزي ، (NodeJS v0.11.11) كان لدينا addAsyncListener . بناءً عليه ، أنشأ Forrest Norvell أول تطبيق للتخزين المستمر المحلي أو CLS . لكننا لا نتحدث عن كيفية عملها بعد ذلك ، لأن واجهة برمجة التطبيقات هذه (أتحدث عن addAsyncLustener) أمرت بحياة طويلة. توفي بالفعل في NodeJS v0.12.


قبل NodeJS 8 ، لم تكن هناك طريقة رسمية لتتبع قائمة انتظار الأحداث غير المتزامنة. وأخيرًا ، في الإصدار 8 ، استعاد مطورو NodeJS العدالة وقدموا لنا واجهة برمجة تطبيقات async_hooks . إذا كنت تريد معرفة المزيد حول async_hooks ، فإنني أوصي بأن تقرأ هذه المقالة . استنادًا إلى async_hooks ، تمت عملية إعادة بناء تطبيق CLS السابق. تسمى المكتبة cls-hooked .


CLS تحت غطاء محرك السيارة


بشكل عام ، يمكن تمثيل مخطط تشغيل CLS على النحو التالي:


نظرة عامة على CLS


لنأخذها بالتفصيل أكثر قليلاً:


  1. افترض أن لدينا خادم ويب Express نموذجي. أولاً ، قم بإنشاء مساحة اسم CLS جديدة. مرة وإلى الأبد مدى الحياة للتطبيق.
  2. ثانياً ، سنقوم بإنشاء برامج وسيطة ، والتي ستنشئ سياق CLS خاص بنا لكل طلب.
  3. عند وصول طلب جديد ، يتم استدعاء هذه الوسيطة (الوظيفة رقم 1).
  4. في هذه الوظيفة ، قم بإنشاء سياق CLS جديد (كخيار واحد ، يمكنك استخدام Namespace.run ). في Namespace.run نمرر وظيفة ستنفذ في نطاق سياقنا.
  5. تضيف CLS سياقًا تم إنشاؤه حديثًا إلى Map مع سياقات باستخدام مفتاح معرف التنفيذ الحالي .
  6. كل مساحة اسم CLS لها خاصية active . CLS يعين هذه الخاصية إشارة إلى سياقنا.
  7. في نطاق السياق ، نقوم بعمل نوع من الاستعلام غير المتزامن ، على سبيل المثال ، في قاعدة البيانات. نقوم بتمرير رد الاتصال إلى برنامج تشغيل قاعدة البيانات ، والذي سيتم استدعاؤه عند اكتمال الطلب.
  8. حرائق الخطاف غير متزامن. يضيف السياق الحالي إلى الخريطة مع السياقات بمعرف غير متزامن (معرف العملية غير المتزامنة الجديدة).
  9. بسبب لم تعد وظيفتنا تحتوي على أي تعليمات إضافية ؛ إنها تكمل التنفيذ.
  10. غير متزامن بعد هوك يعمل لها. يعين الخاصية active إلى مساحة الاسم undefined (في الواقع ، ليس دائمًا ، لأنه يمكن أن يكون لدينا العديد من السياقات المتداخلة ، ولكن أبسط الحالات هي).
  11. تدمير حرائق الخطاف غير المتزامن لأول عملية غير متزامنة. يزيل السياق من الخريطة مع السياقات بمعرف المزامنة لهذه العملية (وهو نفس معرف التنفيذ الحالي لرد الاتصال الأول).
  12. اكتمال الاستعلام في قاعدة البيانات ويسمى رد الاتصال الثاني.
  13. هوك غير متزامن من قبل . معرف التنفيذ الحالي الخاص به هو نفسه معرف المزامنة للعملية الثانية (استعلام قاعدة البيانات). يتم تعيين الخاصية active لمساحة الاسم في السياق الموجود في الخريطة مع السياقات بواسطة معرف التنفيذ الحالي. هذا هو السياق الذي أنشأناه من قبل.
  14. الآن يتم تنفيذ رد الاتصال الثاني. هناك نوع من منطق العمل يعمل ، الشياطين يرقصون ، الفودكا تتدفق. داخل هذا ، يمكننا الحصول على أي قيمة من السياق بالمفتاح . سيحاول CLS العثور على المفتاح المحدد في السياق الحالي أو العودة undefined .
  15. يتم تشغيل غير متزامن بعد ربط رد الاتصال هذا عند اكتماله. يقوم بتعيين الخاصية active لمساحة الاسم إلى undefined .
  16. تدمير حرائق الخطاف غير المتزامن لهذه العملية. يزيل السياق من الخريطة مع السياقات بواسطة معرف المزامنة لهذه العملية (وهو نفس معرف التنفيذ الحالي لرد الاتصال الثاني).
  17. أداة تجميع مجمعي البيانات المهملة (GC) تحرر الذاكرة المرتبطة بكائن السياق ، لأن في طلبنا لا يوجد المزيد من الروابط لذلك.

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


إنشاء معرف التتبع


لذا ، بعد التعامل مع CLS ، سنحاول استخدام هذا الشيء لصالح الإنسانية. لنقم بإنشاء برنامج وسيط ، يقوم كل طلب بإنشاء سياق CLS الخاص به ، ويقوم بإنشاء معرف تتبع عشوائي traceID إلى السياق باستخدام traceID key. ثم داخل ofigilliard من وحدات التحكم والخدمات لدينا نحصل على معرف التتبع هذا.


بالنسبة لـ Express ، قد تبدو الوسيطة المشابهة كما يلي:


 const cls = require('cls-hooked') const uuidv4 = require('uuid/v4') const clsNamespace = cls.createNamespace('app') const clsMiddleware = (req, res, next) => { // req  res -  event emitters.      CLS     clsNamespace.bind(req) clsNamespace.bind(res) const traceID = uuidv4() clsNamespace.run(() => { clsNamespace.set('traceID', traceID) next() }) } 

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


 const controller = (req, res, next) => { const traceID = clsNamespace.get('traceID') } 

صحيح ، دون إضافة معرف التتبع هذا إلى السجلات ، فإنه يستفيد منه ، مثل منفاخ الثلج في فصل الصيف.


دعنا نكتب منسق winston بسيط سيضيف معرف التتبع تلقائيًا.


 const { createLogger, format, transports } = require('winston') const addTraceId = printf((info) => { let message = info.message const traceID = clsNamespace.get('taceID') if (traceID) { message = `[TraceID: ${traceID}]: ${message}` } return message }) const logger = createLogger({ format: addTraceId, transports: [new transports.Console()], }) 

وإذا كانت جميع أدوات قطع الأشجار تدعم المنسق المخصص في شكل وظائف (كثير منهم لديهم أسباب لعدم القيام بذلك) ، فمن المحتمل ألا يحدث هذا المقال. فكيف يمكنك إضافة معرف تتبع لسجلات بينو المعشوق؟


نحن ندعو وكيل من أجل تكوين صداقات أي مسجل و CLS


بضع كلمات عن Proxy نفسها: هذا شيء يلف كائننا الأصلي ويسمح لنا بإعادة تعريف سلوكه في مواقف معينة. في قائمة محدودة محددة بدقة من الحالات (في العلوم يطلق عليهم traps ). يمكنك العثور على القائمة الكاملة هنا ، ونحن مهتمون فقط في الحصول على فخ. إنها تتيح لنا الفرصة لتجاوز قيمة الإرجاع عند الوصول إلى خاصية الكائن ، أي إذا أخذنا الكائن const a = { prop: 1 } ولفناه في Proxy ، فعندئذٍ بمساعدة trap get يمكننا إرجاع كل ما نود عند الوصول إلى a.prop .


في حالة pino الفكر التالي: نقوم بإنشاء معرف تتبع عشوائي لكل طلب ، وإنشاء مثيل تابع لـ pino ننقل إليه معرف التتبع هذا ، ونضع هذا المثال الفرعي في CLS. بعد ذلك ، نقوم بملء ملف التسجيل المصدر الخاص بنا في Proxy ، والذي سيستخدم نفس المثيل الفرعي للتسجيل إذا كان هناك سياق نشط وهناك مسجل فرعي فيه ، أو استخدم المسجل الأصلي.


في مثل هذه الحالة ، سيبدو الوكيل كما يلي:


 const pino = require('pino') const logger = pino() const loggerCls = new Proxy(logger, { get(target, property, receiver) { //    CLS  ,   target = clsNamespace.get('loggerCls') || target return Reflect.get(target, property, receiver) }, }) 

سوف تبدو الوسيطة لدينا كما يلي:


 const cls = require('cls-hooked') const uuidv4 = require('uuid/v4') const clsMiddleware = (req, res, next) => { // req  res -  event emitters.      CLS     clsNamespace.bind(req) clsNamespace.bind(res) const traceID = uuidv4() const loggerWithTraceId = logger.child({ traceID }) clsNamespace.run(() => { clsNamespace.set('loggerCls', loggerWithTraceId) next() }) } 

ويمكننا استخدام المسجل مثل هذا:


 const controller = (req, res, next) => { loggerCls.info('Long live rocknroll!') //  // {"level":30,"time":1551385666046,"msg":"Long live rocknroll!","pid":25,"hostname":"eb6a6c70f5c4","traceID":"9ba393f0-ec8c-4396-8092-b7e4b6f375b5","v":1} } 

cls-- proxify


بناءً على الفكرة أعلاه ، تم إنشاء مكتبة cls-proxify صغيرة . إنها تعمل من خارج منطقة الجزاء مع التعبير عن الكو والتثبيت . بالإضافة إلى إنشاء مصيدة لـ get ، فإنها تنشئ مصائد أخرى لمنح المطور مزيدًا من الحرية. لهذا السبب ، يمكننا استخدام "بروكسي" للالتفاف على الوظائف ، والفئات ، وأكثر من ذلك. هناك عرض حي حول كيفية دمج بينو وتثبيته ، بينو والتعبير .


أرجو ألا تضيع الوقت دون جدوى ، وكانت المقالة مفيدة لك على الأقل. يرجى ركلة وانتقاد. سوف نتعلم أن نرمز معا بشكل أفضل.

Source: https://habr.com/ru/post/ar442452/


All Articles