Mohammad Jawad Barati
Mohammad Jawad Barati
خواندن ۵ دقیقه·۴ سال پیش

لاگ گیری تو نود.جی‌اس توسط winston

با دقت بخونید.

خب بیاید خیلی راحت بهتون یه چیزی رو همین اولش بگم. وقتی یه وب اپی مینویسی که ازش انتظار میره تو محیط production کار بکنه و نیازاتون رو برطرف بکنه نباید لاگ گیری رو یه console.log ساده ببینید.

کی باید لاگ گرفت؟

  1. وقتی که یه درخواست به یه route میاد.
  2. وقتی که یه اروری رخ میده
  3. وقتی که یه عملیات خاصی رخ میده.

لاگ باید شامل چه اطلاعاتی باشه؟

  • زمان (timestamp)
  • مشخص کردن نوع لاگ: اروره، اطلاعاته، اخطاره و ...
  • متد یا همون verb ای که باهاش اومده (GET ،POST یا ...)
  • آی پی سیستمی که درخواست رو ارسال کرده
  • متن ارور و stack trace اون
  • متا دیتا ها
  • در کل هر چقدر بتونی بهتر و با ساختار قشنگ تری لاگ تولید بکنی بعدا برای دیباگ کردن و trace کردن برنامت دستت باز تره.

لاگ باید شامل چه اطلاعاتی نباشه؟

اطلاعات حساس مثل: رمز عبور، secret key ها، اطلاعات شخصی

چجور لاگ باید گرفت؟

برای ماشین ها لاگ بگیر، نه برای انسان ها. چرا؟ چونکه خیلی بهتر از انسان ها عمل می‌کنن تو حجم بالای اطلاعات. winston لاگ هاشو تو JSON میریزه و اکثر ابزار های دیگه هم از JSON دارن استفاده می‌کنن.

ارزش لاگ ها تا زمانی که تو یه فایل یا تو ترمینال هستن زیاد نیست. ولی وقتی از ابزاری مثل Retrace استفاده بکنی ارزش لاگ هایی که میگرفتی مشخص میشه (قصد من این نیست که Retrace یا هر ابزار دیگه ای رو بیام توضیح بدم).

یه نکته رو هم که باید بهش توجه بکنی اینه که توی محیط production ارور ها رو یجور دیگه لاگ بکنیم و فرمت بندی هم داریم. میبینید که مسائل داره زیاد میشه. پس باید از یه پکیجی مثل winston استفاده کرد. برای همین موضوع تو میتونی یه کلاس تعریف بکنی به اسم Logger.

چرا؟ چونکه اینکار به تو این توانایی رو میده تا بعدا همون کلاس رو جاهای دیگه استفاده بکنی.

const winston = require('winston'); class Logger { constructor (name) { this.logger = winston.createLogger({ level: 'debug', defaultMeta: { service: name }, transports: [ new winston.transports.Console() ] }); }; debug(log, metadata) { this.logger.debug(log, metadata) }; info(log, metadata) { this.logger.info(log, metadata) }; warn(log, metadata) { this.logger.warn(log, metadata) }; error(log, metadata) { this.logger.error(log, metadata) }; }; module.exports = new Logger(appName); module.exports.getLogger = function(name) { return new Logger(name); };

یه مشته توضیح در مورد این کلاس:

متد createLogger یه مشته تنظیمات رو میگیره و بعدش یه آبجکت برمی‌گردونه که باهاش میتونی لاگ بگیری. در ضمن از اونجایی که میخواستیم بعدا بتونیم یه logger جدید هم بسازیم با یه اسم جدید اومدیم getLogger رو هم export کردیم. نکته ای که باید بهش توجه بکنی اینه که winston به صورت پیشفرض از JSON استفاده می‌کنه.

حالا بیاید از این کلاس استفاده بکنیم.

const logger = new require('./logger'); logger.debug('debug log'); logger.info('info log'); logger.warn('warn log'); logger.error('error log1'); logger.error(new Error('error log2'));

در قدم بعدی برای فرمت دادن و رنگ و لعاب دادن باید بیای transports بهش اضافه بکنی. پس این تغییرات رو اعمال بکن:

this.logger = winston.createLogger({ level: 'debug', defaultMeta: { service: name }, transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.metadata({fillExcept: [ 'password', 'accessKey', 'refreshKey' ]}), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.prettyPrint(), winston.format.colorize(), winston.format.printf(info => `${info.level} **** ${info.message}`), winston.format.simple() ), new winston.transports.File({ filename: path.join(__dirname, '..', 'logs', 'error.log'), level: 'error', }), new winston.transports.File({ filename: path.join(__dirname, '..', 'logs', 'combined.log') }) }) ]

برای مشخص کردن فرمت از winston.format استفاده می‌کنیم. برای همین منظور توی کلاس Console میایم فرمت رو تعریف می‌کنیم. یادتون هم باشه که ترتیب این متد های winston.format مهمه. یعنی override میشن وقتی بعد از هم میان.

  • توی timestamp اومدیم فرمت timestamp رو مشخص کردیم. ولی به تنهایی کاربردی نداره. یعنی اگه فقط این رو بزارید ولی prettyPrint رو نزارید timestamp نمایش داده نمیشه.
  • توی colorize میایم رنگی می‌کنیم stdout ای که تو ترمینال چاپ میشه.
  • توی metadata از طریق fillExcept میای میگی کدوم key ها اگه تو metadata بودن نمایش داده نشن. (ولی این که برای من نمایش میداد. نمیدونم چرا؟ یا من درست نفهمیدمش)
  • متد printf میاد نحوه‌ی نمایش توی ترمینال رو مشخص میکنه. نکته: توی فایل کماکان یه JSON ذخیره میشه و توی ترمینال دیگه stack trace رو برای ارور ها ندارید.
  • متد simple خروجی رو میاد به صورت رشته توی ترمینال چاپ می‌کنه.
  • با این دوتا new winston.transports.File دستور هم میای محل ذخیره سازی لاگ فایل ها رو مشخص می‌کنی

در ضمن شما در کنار این پکیج میتونید از morgan هم استفاده بکنید.

کانفیگی هم که خودم معمولا استفاده می‌کنم اینه:



ذخیره لاگ ها توی MongoDB

فرض کن نمیخوای لاگ ها رو تو فایل ذخیره بکنی. چکار می‌کنی؟ از من بپرسی گزینه بهتری از MongoDB سراغ ندارم. با پکیج winston-mongodb به راحتی می‌تونی لاگ ها رو تو دیتابیس ذخیره بکنی. برای این کار فقط کافیه که یه نگاه سرسری به ریپوزیتوریم تو گیت هاب که براتون با دقت و با توجه به نیاز های یه پروژه واقعی ساختم بندازید.

یه مشته توضیحات در مورد کاری که انجام شده:

  • الان اینجا اومدیم کلاس Logger رو به صورت singleton یا abstract تعریف نکردیم. چرا؟ چونکه میخواستیم لاگ های هر بخش رو از بقیه بخش ها جدا کنیم. دقت کنید که لاگ ها همشون تو یه کالکشن ذخیره میشه. فقط تنها کاری که تو اینجا می‌کنیم خط زیر هست:

پس ما از کلاس Logger میایم instance میگریم و اسم سرویس رو مشخص می‌کنیم. همون اسم رو با این تنظیمات توی داکیمنت ها هم ذخیره می‌کنیم تا بدونیم هر لاگ مرتبط با کدوم instance از logger هست. این یه روش خوب برای اینه که بعدا لاگ هاتون رو بتونید از هم تفکیک کنید.

منبع ۱ و منبع ۲

لاگ گرفتننود.جی‌اسwinstonآموزش نحوه لاگ گیریnode js
برنانه نویس، مدرس، محقق. عاشق انیمه هستم و دنبال چالش ها جدید.
شاید از این پست‌ها خوشتان بیاید