یکی از رایج‌ترین دلایل از کار افتادن ناگهانی سایت‌ها نه حمله است و نه باگ کد، بلکه پر شدن دیسک سرور با فایل‌های لاگ است. وقتی access.log وب‌سرور به چند گیگابایت می‌رسد، MySQL نمی‌تواند فایل موقت بسازد، PHP نمی‌تواند session بنویسد و حتی ورود با SSH کند می‌شود. ابزار logrotate دقیقاً برای همین مشکل ساخته شده است: لاگ‌ها را در بازه‌های زمانی مشخص یا پس از رسیدن به حجم معین بایگانی، فشرده و پس از مدتی حذف می‌کند. در این مقاله سازوکار logrotate، دستورالعمل‌های مهم پیکربندی، نمونه‌های واقعی برای Nginx و Laravel، روش تست و اشتباهاتی که چرخش لاگ را بی‌صدا از کار می‌اندازند را بررسی می‌کنیم.

چرا لاگ‌ها دیسک سرور را پر می‌کنند؟

هر درخواست HTTP یک خط در access.log می‌نویسد؛ سایتی با یک میلیون بازدید ماهانه و ربات‌های خزنده‌ای که چند برابر کاربران واقعی درخواست می‌فرستند، به‌راحتی روزانه چند صد مگابایت لاگ تولید می‌کند. اگر PHP-FPM روی سطح notice تنظیم شده باشد یا یک افزونه وردپرس مدام هشدار deprecated بنویسد، error.log هم با همان سرعت رشد می‌کند. در سرورهای مجازی با دیسک 25 یا 40 گیگابایتی، بدون چرخش منظم، لاگ‌ها در چند هفته کل فضای آزاد را می‌بلعند.

پرمصرف‌ترین لاگ‌ها در سرورهای وب

  • access.log و error.log در Nginx و Apache، به‌ویژه وقتی هر دامنه فایل جداگانه دارد.
  • Slow query log و فایل‌های binlog در MySQL و MariaDB که زمان انقضای آن‌ها تنظیم نشده است.
  • لاگ اپلیکیشن مانند storage/logs/laravel.log که در کانال single بدون محدودیت رشد می‌کند.

علائم پر شدن دیسک

خطای «No space left on device»، پاسخ 500 از PHP بدون دلیل روشن و پیام «Can't create/write to file» در MySQL نشانه‌های کلاسیک هستند. دستور df -h وضعیت کلی را نشان می‌دهد و du -sh /var/log/* با مرتب‌سازی sort -h مشخص می‌کند کدام لاگ مقصر است.

logrotate چگونه کار می‌کند؟

logrotate دیمن نیست؛ یک برنامه معمولی است که روزی یک بار (از طریق logrotate.timer یا /etc/cron.daily) اجرا می‌شود، فایل اصلی /etc/logrotate.conf و همه فایل‌های پوشه /etc/logrotate.d/ را می‌خواند، برای هر لاگ تصمیم می‌گیرد آیا موعد چرخش رسیده است و نتیجه را در /var/lib/logrotate/status ثبت می‌کند. هر بسته‌ای که لاگ تولید می‌کند هنگام نصب یک فایل پیکربندی در همین پوشه می‌گذارد؛ بنابراین مشکل معمولاً در لاگ‌های اپلیکیشن یا تنظیمات ناکافی است.

ترتیب چرخش فایل‌ها

در هر چرخش، access.log به access.log.1 تغییر نام می‌دهد، فایل قبلی به access.log.2.gz فشرده می‌شود و همین‌طور تا رسیدن به عدد تعیین‌شده در rotate که پس از آن قدیمی‌ترین فایل حذف می‌شود. با دستورالعمل dateext به جای شماره، تاریخ به نام فایل اضافه می‌شود (access.log-20261101) که برای بایگانی خواناتر است.

چرا برنامه باید از چرخش باخبر شود؟

وقتی لینوکس فایلی را تغییر نام می‌دهد، پروسه‌ای که آن را باز کرده همچنان به همان inode می‌نویسد؛ یعنی Nginx بعد از چرخش به نوشتن در access.log.1 ادامه می‌دهد و فایل جدید خالی می‌ماند. به همین دلیل پیکربندی هر دیمن باید در بخش postrotate سیگنالی بفرستد تا برنامه فایل لاگ را دوباره باز کند، یا از copytruncate استفاده کند.

دستورالعمل‌های کلیدی پیکربندی logrotate

جدول زیر گزینه‌هایی را که در عمل بیشترین کاربرد را دارند با توضیح رفتار دقیق هر یک نشان می‌دهد. گزینه‌های داخل بلوک هر لاگ بر مقادیر سراسری /etc/logrotate.conf اولویت دارند.

دستورالعملرفتار
daily / weekly / monthlyبازه زمانی چرخش؛ در هر اجرا بررسی می‌شود آیا از آخرین چرخش به اندازه کافی گذشته است
size 100M / maxsizeچرخش بر اساس حجم؛ maxsize با بازه زمانی ترکیب می‌شود و هر کدام زودتر برسد اعمال می‌شود
rotate 14تعداد نسخه‌های نگهداری‌شده؛ rotate 0 یعنی حذف فوری بدون بایگانی
compress / delaycompressفشرده‌سازی با gzip؛ delaycompress آخرین نسخه را فشرده نمی‌کند تا برنامه فرصت بستن فایل را داشته باشد
missingok / notifemptyنبود فایل خطا نیست؛ فایل خالی چرخش نمی‌شود
create 0640 www-data admساخت فایل جدید با پرمیشن و مالک مشخص بلافاصله پس از تغییر نام
copytruncateکپی محتوا و خالی کردن فایل اصلی به جای تغییر نام؛ بدون نیاز به سیگنال اما با احتمال از دست رفتن چند خط
su www-data www-dataاجرای چرخش با کاربر مشخص؛ برای پوشه‌هایی که مالک آن‌ها root نیست الزامی است

نمونه پیکربندی برای Nginx و اپلیکیشن Laravel

پیکربندی پیش‌فرض Nginx در Ubuntu چرخش روزانه با 14 نسخه است. برای سایت پرترافیک maxsize را هم اضافه کنید تا یک روز شلوغ دیسک را پر نکند:

/var/log/nginx/*.log {
    daily
    maxsize 500M
    rotate 14
    missingok
    notifempty
    compress
    delaycompress
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /run/nginx.pid ] && kill -USR1 $(cat /run/nginx.pid)
    endscript
}

postrotate و سیگنال USR1

سیگنال USR1 به Nginx می‌گوید فایل‌های لاگ را ببندد و دوباره باز کند، بدون اینکه اتصال‌های فعال قطع شوند. Apache همین کار را با apachectl graceful انجام می‌دهد و PHP-FPM با USR1 به پروسه master. برای MySQL باید از mysqladmin flush-logs استفاده کنید. ری‌استارت کامل سرویس در postrotate اشتباه رایجی است که هر شب قطعی کوتاهی ایجاد می‌کند.

copytruncate: چه زمانی لازم است؟

اپلیکیشن‌هایی مانند Laravel یا اسکریپت‌های Node.js که سیگنال reopen ندارند، باید با copytruncate چرخش شوند. logrotate ابتدا محتوا را به فایل جدید کپی و سپس فایل اصلی را خالی می‌کند؛ بین این دو مرحله چند خط ممکن است از دست برود که برای لاگ اپلیکیشن معمولاً قابل قبول است. اگر برنامه بدون فلگ O_APPEND می‌نویسد، پس از truncate ممکن است فایل با بایت‌های صفر پر شود؛ در این موارد از چرخش داخلی خود برنامه (مانند کانال daily در Laravel) استفاده کنید.

/var/www/app/storage/logs/*.log {
    weekly
    maxsize 200M
    rotate 8
    missingok
    notifempty
    compress
    copytruncate
    su www-data www-data
}

تست و اشکال‌زدایی پیکربندی

اجرای آزمایشی با حالت debug

دستور logrotate -d /etc/logrotate.d/nginx پیکربندی را می‌خواند و بدون هیچ تغییری گزارش می‌دهد برای هر فایل چه تصمیمی می‌گیرد. پیام «log does not need rotating» طبیعی است و یعنی هنوز موعد نرسیده؛ پیام‌های error اما باید فوراً رفع شوند، چون logrotate در صورت خطای نحوی کل آن فایل را نادیده می‌گیرد.

اجرای اجباری با -f

برای مشاهده رفتار واقعی، logrotate -f -v /etc/logrotate.d/nginx چرخش را صرف‌نظر از زمان اجرا می‌کند و جزئیات هر مرحله را چاپ می‌کند. سپس با ls -la /var/log/nginx/ بررسی کنید فایل جدید با مالک درست ساخته شده و با tail -f مطمئن شوید Nginx واقعاً در فایل جدید می‌نویسد. اگر فایل جدید خالی ماند، سیگنال postrotate به مقصد نرسیده است.

خطاهای رایج در خروجی

پیام «skipping because parent directory has insecure permissions» یعنی پوشه لاگ برای کاربر غیر root قابل نوشتن است و باید دستورالعمل su اضافه شود. پیام «duplicate log entry» نشان می‌دهد یک فایل در دو پیکربندی مختلف آمده و فقط اولی اجرا می‌شود. اگر logrotate اصلاً اجرا نمی‌شود، با systemctl list-timers وضعیت logrotate.timer و با journalctl -u logrotate لاگ آخرین اجرا را ببینید.

اشتباهاتی که چرخش لاگ را بی‌اثر می‌کنند

  1. نبود postrotate یا copytruncate: فایل چرخش می‌شود اما برنامه در فایل قدیمی می‌نویسد و حذف آن فضا را آزاد نمی‌کند.
  2. تکیه صرف بر daily برای لاگ‌های پرحجم: یک حمله یا خزش سنگین می‌تواند در چند ساعت دیسک را پر کند؛ همواره maxsize را هم تنظیم کنید.
  3. لاگ‌های اپلیکیشن در مسیرهای غیر استاندارد (/home/user/app/logs) که هیچ قاعده‌ای برای آن‌ها نوشته نشده است.
  4. مقدار rotate بسیار بزرگ برای لاگ روزانه چند صد مگابایتی که مجموع بایگانی را از خود لاگ فعال بزرگ‌تر می‌کند.

وقتی logrotate کافی نیست

برخی منابع لاگ خارج از دامنه logrotate هستند. ژورنال systemd چرخش داخلی خودش را با SystemMaxUse دارد، کانتینرهای Docker به گزینه max-size در daemon.json نیاز دارند و binlog های MySQL با binlog_expire_logs_seconds کنترل می‌شوند. در هاست اشتراکی لینوکس چرخش لاگ‌های وب‌سرور توسط ارائه‌دهنده انجام می‌شود، اما روی سرور مجازی همه این موارد بر عهده شما است. برای چند سرور، ارسال لاگ به سیستم متمرکز مانند Loki راه‌حل پایدارتری است.

جمع‌بندی

logrotate ابزاری ساده است که اگر درست پیکربندی شود، یکی از رایج‌ترین دلایل قطعی سرور را به‌کلی حذف می‌کند. برای هر لاگ بازه زمانی و سقف حجم را همزمان تعیین کنید، با postrotate یا copytruncate مطمئن شوید برنامه به فایل جدید می‌نویسد و پس از هر تغییر با حالت debug و اجرای اجباری نتیجه را بررسی کنید. لاگ‌های اپلیکیشن سفارشی را فراموش نکنید؛ همان‌هایی که هیچ بسته‌ای برایشان پیکربندی ننوشته است.

سوالات متداول

logrotate چند وقت یک بار اجرا می‌شود؟

به‌طور پیش‌فرض روزی یک بار، در Ubuntu و Debian از طریق logrotate.timer و در توزیع‌های قدیمی‌تر از /etc/cron.daily. اگر به چرخش ساعتی نیاز دارید (مثلاً با size برای لاگ بسیار پرحجم)، باید timer را ساعتی کنید یا فایل پیکربندی را در /etc/cron.hourly فراخوانی کنید.

چرا بعد از چرخش لاگ، فضای دیسک آزاد نشد؟

برنامه هنوز فایل قدیمی را باز نگه داشته و به آن می‌نویسد. با lsof +L1 فایل‌های حذف‌شده اما باز را پیدا کنید و سرویس مربوطه را reload کنید. برای جلوگیری از تکرار، بخش postrotate را با سیگنال درست (مثلاً USR1 برای Nginx) تکمیل کنید.

تفاوت copytruncate و create چیست؟

create فایل را تغییر نام می‌دهد و یک فایل جدید می‌سازد که برنامه باید آن را با سیگنال دوباره باز کند. copytruncate محتوا را کپی و فایل اصلی را خالی می‌کند، بنابراین نیازی به سیگنال نیست اما ممکن است چند خط بین کپی و truncate از دست برود. برای دیمن‌ها create و برای اسکریپت‌ها copytruncate مناسب‌تر است.

آیا می‌توان لاگ‌های کاربر غیر root را چرخاند؟

بله، با دستورالعمل su user group در بلوک همان لاگ. بدون آن، logrotate به دلایل امنیتی از چرخش فایل‌هایی در پوشه‌هایی که مالک آن‌ها root نیست خودداری می‌کند و پیام insecure permissions می‌دهد.

چگونه بفهمم logrotate واقعاً کار می‌کند؟

فایل /var/lib/logrotate/status تاریخ آخرین چرخش هر لاگ را نشان می‌دهد. وجود فایل‌های .1 و .gz در کنار لاگ اصلی نشانه کار درست است.