جعل تسجيل NodeJS الصحيح


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


لماذا هي مشكلة في الحصول على معرف تتبع لكل طلب في NodeJS؟


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


مرة أخرى في الأيام الخوالي (v0.11.11) كان لدينا addAsyncListener مما سمح لنا بتتبع الأحداث غير المتزامنة. بناءً عليه ، بنى فورست نورفيل أول تطبيق للتخزين المحلي المستمر المعروف أيضًا باسم CLS . لن نغطي تنفيذ CLS نظرًا لحقيقة أننا ، كمطورين ، جُرِّدنا من واجهة برمجة التطبيقات هذه في الإصدار v0.12 بالفعل.


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


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


فيما يلي تدفق مبسط لكيفية عمل CLS:



دعنا نقسمها خطوة بخطوة:


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

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


توليد معرفات التتبع


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


للتعبير عن هذه الوسيطة يمكن أن تبدو مثل هذا:


 const cls = require('cls-hooked') const uuidv4 = require('uuid/v4') const clsNamespace = cls.createNamespace('app') const clsMiddleware = (req, res, next) => { // req and res are event emitters. We want to access CLS context inside of their event callbacks 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 هو كائن يلتف كائننا الأصلي مما يسمح لنا بتجاوز سلوكه في مواقف معينة. قائمة هذه المواقف (يطلق عليها فعلاً الفخاخ) محدودة ويمكنك إلقاء نظرة على المجموعة بأكملها هنا ، لكننا مهتمون فقط بفخاخ. يوفر لنا القدرة على اعتراض الوصول إلى الممتلكات. هذا يعني أنه إذا كان لدينا كائن const a = { prop: 1 } a.prop في وكيل ، a.prop get trap ، يمكننا إرجاع أي شيء نريده لـ a.prop .


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


في هذا السيناريو ، قد يبدو وكيلنا كما يلي:


 const pino = require('pino') const logger = pino() const loggerCls = new Proxy(logger, { get(target, property, receiver) { // Fallback to our original logger if there is no child logger in 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 and res are event emitters. We want to access CLS context inside of their event callbacks 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!') // Logs something like // {"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/ar442392/


All Articles