چگونه تمام فراخوانیهای API را لاگ کنیم بدون کاهش سرعت سرور
بذارید رک و راست بگم: وقتی یک API داری، لاگها مثل جعبه سیاه هواپیما هستن. هر اتفاقی که میافته، توی لاگها ثبت میشه.
هر درخواستی که میآد، یه داستان داره:
کی درخواست داده؟
به کدوم endpoint زده؟
چقدر طول کشیده تا جواب بده؟
درست جواب دادیم یا با خطای 500 منفجر شدیم؟
این اطلاعات برای دیباگ، حسابرسی و تحلیل خیلی مهمه. ولی یه مشکل بزرگ داریم:
اگه لاگکردن رو اشتباه انجام بدی، سرورت رو کند میکنی. خیلی کند. لاگکردن میتونه event loop رو قفل کنه، دیسک رو پر کنه و حتی در زمان شلوغی باعث downtime بشه.
چرا لاگکردن سرور رو کند میکنه؟
بیشتر بچهها با همین شروع میکنن:
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});سریع و ساده. کار میکنه. تا وقتی که ترافیک نداری، همه چیز خوبه.
ولی وقتی ترافیک زیاد میشه، مشکلات شروع میشن:
console.log همزمانه و event loop رو قفل میکنه، مخصوصاً اگه بخواد توی فایل بنویسه.
اگه لاگها رو مستقیم توی فایل بریزی، I/O دیسک به گلوگاه تبدیل میشه.
اگه بخوای لاگها رو به سرور دیگهای بفرستی، تأخیر شبکه شروع میشه.
حجم لاگها خیلی سریع رشد میکنه و دیسک رو پر میکنه.
چند میلیثانیه برای هر درخواست زیاد نیست، نه؟ ولی اگه این رو در هزاران درخواست همزمان ضرب کنی، میبینی که API بیشتر وقتش رو صرف لاگکردن میکنه تا پردازش درخواستها.
قانون طلایی
"هرگز نذار handler اصلی منتظر نوشته شدن لاگها بمونه."
یعنی سیستم لاگکردنت باید ناهمزمان باشه. بهتره که اصلاً خارج از مسیر اصلی اجرا بشه.
گام 1: ببین چی رو باید لاگ کنی
قبل از اینکه به بهینهسازی فکر کنی، ببین واقعاً چی رو باید ثبت کنی. لاگکردن بیش از حد یه اشتباه رایجه که منابع رو هدر میده.
چیزهایی که معمولاً برای API لاگ میکنن:
برچسب زمان (Timestamp)
متد HTTP (GET, POST, PUT, DELETE…)
URL / Route
پارامترهای Query و Body (با دادههای حساس پوشیده شده)
آدرس IP کلاینت
User Agent
کد وضعیت پاسخ (Response Status Code)
زمان پاسخ (Response Time - latency)
پیامهای خطا (در صورت وجود)
شناسه کاربر احراز هویت شده (در صورت وجود)
شناسه درخواست / شناسه همبستگی (برای ردیابی در سرویسها)
مثال ورودی لاگ:
{
"time": "2025-08-11T14:23:45.123Z",
"method": "POST",
"url": "/api/orders",
"status": 201,
"responseTimeMs": 123,
"userId": "usr_1a2b3c",
"ip": "203.0.113.42",
"userAgent": "Mozilla/5.0 (Macintosh...)",
"requestId": "req_abcd1234"
}گام 2: از یه Logger غیرمسدودکننده استفاده کن
console.log رو برای production فراموش کن. باید از یه logger استفاده کنی که برای سرعت بالا و نوشتن ناهمزمان طراحی شده.
چندتا کتابخانه خوب برای Node.js:
Pino — خیلی سریعه، تقریباً 10 برابر سریعتر از winston
Bunyan — لاگکردن ساختاریافته با streams
Winston — انعطافپذیر، چندین transport، خوب برای enterprise
Log4js — پر از قابلیت، الهام گرفته از Log4j جاوا
مثال با Pino:
const pino = require('pino');
const logger = pino({
level: 'info',
transport: {
target: 'pino-pretty'
}
});
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logger.info({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
responseTime: Date.now() - start
});
});
next();
});چرا Pino؟ چون:
به صورت پیشفرض ناهمزمانه
از سریالسازی JSON سریع استفاده میکنه
برای throughput بالا ساخته شده
گام 3: لاگها رو توی Buffer بنویس، نه مستقیم
اگه هر خط لاگ رو مستقیم توی دیسک یا سرور دیگهای بنویسی، درخواست تأخیر میکنه.
به جاش:
لاگها رو توی حافظه buffer کن (مثلاً چند ثانیه توی یه آرایه نگه دار)
بعد به صورت دورهای یا وقتی buffer پر شد، flush کن
این کار هزاران نوشتن کوچک رو به نوشتنهای کمتر و بزرگ تبدیل میکنه
مثال:
let logBuffer = [];
const BUFFER_SIZE = 50;
const FLUSH_INTERVAL = 5000; // ms
function flushLogs() {
if (logBuffer.length > 0) {
// تصور کنید در حال نوشتن در فایل یا ارسال به سرور لاگ هستید
logger.info({ batch: logBuffer });
logBuffer = [];
}
}
setInterval(flushLogs, FLUSH_INTERVAL);
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logBuffer.push({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
time: Date.now(),
latency: Date.now() - start
});
if (logBuffer.length >= BUFFER_SIZE) {
flushLogs();
}
});
next();
});گام 4: لاگکردن رو به یه Worker Process منتقل کن
برای APIهای با ترافیک سنگین، حتی buffer کردن توی همون فرآیند ممکنه کندت کنه.
بهتره این کار رو بکنی:
لاگها رو به یه سرویس لاگ جداگانه یا worker پسزمینه بفرست
فرآیند اصلی API دادههای لاگ رو به یه صف پیام (مثلاً RabbitMQ, Kafka, Redis Streams) push میکنه
Worker لاگها رو میخونه و به صورت ناهمزمان توی storage مینویسه
مثال استفاده از Redis Pub/Sub:
const Redis = require('ioredis');
const pub = new Redis();
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
pub.publish('api_logs', JSON.stringify({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
latency: Date.now() - start
}));
});
next();
});Worker:
const sub = new Redis();
sub.subscribe('api_logs');
sub.on('message', (channel, message) => {
const logData = JSON.parse(message);
// logData را در دیتابیس یا storage لاگ بنویسید
});گام 5: از لاگکردن راهدور ناهمزمان استفاده کن
اگه باید لاگها رو به یه سیستم متمرکز بفرستی مثل:
ELK Stack (Elasticsearch, Logstash, Kibana)
Datadog
Graylog
Loggly
AWS CloudWatch
مطمئن بشو که لاگها رو به صورت batch یا ناهمزمان میفرستی تا درخواستها قفل نشن.
مثال با Winston + CloudWatch:
const winston = require('winston');
require('winston-cloudwatch');
winston.add(new winston.transports.CloudWatch({
logGroupName: 'my-api-logs',
logStreamName: 'production',
awsRegion: 'us-east-1',
jsonMessage: true
}));
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
winston.info({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
latency: Date.now() - start
});
});
next();
});گام 6: دادههای حساس رو پوشش بده
باید حریم خصوصی و امنیت کاربر رو توی لاگکردن حفظ کنی.
هیچ وقت لاگ نکن:
رمزهای عبور
شماره کارتهای اعتباری
کلیدهای API / توکنها
اطلاعات قابل شناسایی شخصی (PII) بدون رمزگذاری
مثال پوشش:
function maskSensitive(obj) {
const clone = { ...obj };
if (clone.password) clone.password = '******';
if (clone.cardNumber) clone.cardNumber = '**** **** **** ' + clone.cardNumber.slice(-4);
return clone;
}
app.use(express.json());
app.use((req, res, next) => {
req.body = maskSensitive(req.body);
next();
});گام 7: سربار لاگکردن رو اندازهگیری و تنظیم کن
نمیدونی لاگکردن کندت میکنه یا نه، مگه اینکه اندازهگیری کنی.
این چیزها رو ردیابی کن:
میانگین زمان پاسخ قبل و بعد از لاگکردن
استفاده از CPU در زمان شلوغی
استفاده از حافظه (bufferها نباید بیکنترل رشد کنن)
آمار I/O دیسک
ابزارها:
ab (ApacheBench)
autocannon (برای Node)
ابزارهای APM (New Relic, Datadog, AppDynamics)
گام 8: لاگها رو چرخش بده و آرشیو کن
اگه تمام فراخوانیهای API رو لاگ کنی، لاگها خیلی سریع رشد میکنن.
چرخش لاگ رو تنظیم کن:
روزانه یا وقتی فایل به حد اندازه رسید (مثلاً 100 مگابایت) چرخش بده
لاگهای قدیمی رو فشرده کن
اونا رو به storage ارزانتر منتقل کن (AWS S3, Glacier)
با Pino:
pino server.js | tee >(pino-pretty) | rotatelogs ./logs/api-%Y-%m-%d.log 86400گام 9: برای مقیاس بالا توزیعشده برو
در مقیاس خیلی بالا:
از نوشتن لاگها به صورت محلی کاملاً دوری کن (از جمعآوریکنندههای راهدور استفاده کن)
از log shipperهایی مثل Filebeat یا Fluent Bit برای فرستادن لاگها استفاده کن
Kafka + ELK رو برای پردازش میلیونها رویداد لاگ در دقیقه در نظر بگیر
اگه هر درخواستی نیاز به جزئیات کامل نداره، sampling اعمال کن (مثلاً فقط 1% از 200 OKهای موفق رو لاگ کن ولی 100% از خطاهای 500 رو)
گام 10: یه تنظیمات Production واقعی
یه pipeline لاگکردن API با مقیاس بالا و تأخیر کم این شکلیه:
API Gateway (مثلاً Nginx, Kong) metadata درخواست رو لاگ میکنه → به Kafka میفرسته
App Server اطلاعات غنیشده رو لاگ میکنه (شناسههای کاربر، دادههای تجاری) → به صورت ناهمزمان به Kafka میفرسته
Kafka Consumers لاگها رو پردازش میکنن → توی Elasticsearch برای جستجو مینویسن
داشبوردهای Kibana / Grafana لاگها رو به صورت real-time نشون میدن
هشدارها در مورد افزایش خطا، endpointهای کند یا الگوهای ترافیک غیرعادی فعال میشن
این تنظیمات باعث میشه API تو هیچ وقت توسط سربار لاگکردن قفل نشه، حتی در زمان شلوغی.
نکات کلیدی
مسیر درخواست رو قفل نکن — به صورت ناهمزمان لاگ کن.
از یه logger سریع مثل Pino برای Node.js استفاده کن.
لاگها رو قبل از نوشتن یا فرستادن buffer و batch کن.
لاگها رو به workerها یا صفهای راهدور منتقل کن.
دادههای حساس رو قبل از ذخیرهسازی پوشش بده.
لاگها رو چرخش بده و دادههای قدیمی رو آرشیو کن تا storage رو ذخیره کنی.
عملکرد رو نظارت کن تا مطمئن بشی لاگکردن به گلوگاه تبدیل نمیشه.





