IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API43/51-dars13 daqiqa
Mundarija (30)

Winston logger va log rotatsiya: transport, format va diskni to'ldirmaslik

Qisqacha: Winston — Node'ning eng eski va keng tarqalgan logger'i: createLogger ga format zanjiri (timestamp, json, printf) va transportlar (konsol, fayl, tarmoq) beriladi. U moslashuvchan, lekin teng sharoitda Pino'dan ~1.4 barobar sekin (kanon o'lchovi) va xato obyektida tuzog'i bor: errors() formatisiz log.error(new Error(…)) xabarsiz qator yozdi. Faylga yozilgan log rotatsiyasiz diskni to'ldiradi: winston-daily-rotate-file, Linux'da logrotate, konteynerda Docker log driver. Kanon qarori — faylga umuman yozmaslik: stdout + muhit.

Bu darsda

  • Winston logger'ini format va transport'lar bilan yig'a olasiz.
  • Winston va Pino darajalari teskari ekanini va xato obyekti tuzog'ini bilasiz.
  • Log rotatsiyasi nima uchun kerakligini va uning uch yo'lini tushuntirasiz.
  • Rotatsiya kutubxonasining haqiqiy xulqini o'lchov bilan ko'rasiz.
  • Fayl yoki stdout — qaysi biri qachon, asosli tanlay olasiz.

Oldin bilishingiz kerak: Logging asoslari, Pino chuqur, fs: kataloglar, stat va watch.

1. Nega bu kerak?

Siz yangi ishga kirdingiz. Loyiha 2019-yilda boshlangan, package.json da winston turibdi, logs/ papkasida error.log va combined.log bor. Bir kuni server javob bermay qoladi. Sabab — disk 100 % to'lgan: combined.log uch yilda 40 GB bo'lgan.

Bu darsda ikki narsani o'rganamiz. Birinchisi — Winston'ni o'qish va sozlash: eski va mavjud loyihalarning katta qismi unda. Ikkinchisi — log fayllari diskni yemasligi uchun nima qilinadi.

Hayotiy o'xshatish: do'kondagi chek lentasi. Kassa har xaridni lentaga bosadi. Lenta tugasa — yangisi qo'yiladi, eskisi arxivga ketadi, juda eskilari yo'q qilinadi. Hech kim bitta cheksiz lentani saqlamaydi. Log rotatsiyasi — xuddi shu: fayl ma'lum hajm yoki vaqtga yetganda yangisi ochiladi, eskilari sanoqli qoladi.

2. Winston asoslari

2.1 Birinchi logger

Winston'da logger uch qismdan yig'iladi: daraja, format va transport'lar.

js
import winston from "winston";
const { combine, timestamp, json } = winston.format;

const log = winston.createLogger({
  level: "info",
  defaultMeta: { ilova: "vazifalar-api" },
  format: combine(timestamp(), json()),
  transports: [new winston.transports.Console()],
});

log.info("vazifa yaratildi", { vazifaId: 7 });
log.debug("ko'rinmaydi");
log.warn("sekin so'rov", { ms: 1200 });
log.child({ sorovId: "kor-0001" }).info("child bilan");

Biz ishga tushirdik (winston 3.19.0, Node 24.21.0; vaqt o'zgarmasligi uchun timestamp ga qat'iy qiymat berdik):

text
{"ilova":"vazifalar-api","level":"info","message":"vazifa yaratildi","timestamp":"2026-10-24T06:00:00.000Z","vazifaId":7}
{"ilova":"vazifalar-api","level":"warn","message":"sekin so'rov","ms":1200,"timestamp":"2026-10-24T06:00:00.000Z"}
{"ilova":"vazifalar-api","level":"info","message":"child bilan","sorovId":"kor-0001","timestamp":"2026-10-24T06:00:00.000Z"}

Pino bilan farqlarga qarang:

Pino Winston
Chaqiruv log.info({ vazifaId }, "xabar") log.info("xabar", { vazifaId })
Xabar maydoni msg message
Daraja son (30) matn ("info")
Doimiy maydonlar base defaultMeta

Argumentlar tartibi teskari — bir loyihadan ikkinchisiga o'tganda eng ko'p adashtiradigan joy.

2.2 Darajalar teskari

js
console.log(winston.config.npm.levels);
text
{ error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 }

Winston'da son qancha kichik — shuncha jiddiy (error: 0). Pino'da teskari (fatal: 60). fatal yo'q, lekin http, verbose, silly bor. Bu "npm darajalari" deb ataladi — npm o'zining logida shunday ishlatgan. level: "warn" bersangiz — error va warn yoziladi, qolgani yo'q.

3. Format zanjiri

3.1 combine: quvur kabi

Format (format) — log yozuvini (info obyekti) qadamma-qadam o'zgartiradigan funksiya. combine ularni ketma-ket ulaydi: timestamp() vaqt qo'shadi, json() oxirida JSON satr yasaydi.

Dev uchun odam o'qiydigan format — printf:

js
const { combine, timestamp, printf } = winston.format;
const dev = winston.createLogger({
  format: combine(
    timestamp(),
    printf((i) => {
      const daraja = i.level.toUpperCase();
      return `${i.timestamp} ${daraja}: ${i.message}`;
    }),
  ),
  transports: [new winston.transports.Console()],
});
dev.info("vazifa yaratildi");

Biz ishga tushirdik (vaqt qat'iy berilgan):

text
11:02:05 INFO: vazifa yaratildi

Pino'da bu ish tashqi pino-pretty da, Winston'da esa logger ichida. Qulay, lekin ishlab chiqarishga tasodifan printf formati bilan chiqib ketish oson.

3.2 Tuzoq: xato obyekti

Endi muhim tajriba. Xatoni uch xil yo'l bilan yozamiz, avval errors() formatisiz:

js
const log = winston.createLogger({
  format: json(),
  transports: [new winston.transports.Console()],
});
log.error(new Error("errors formatisiz"));
log.error("xato", { err: new Error("ichki") });

Biz ishga tushirdik:

text
{"level":"error"}
{"err":{},"level":"error","message":"xato"}

Birinchi qatorda xabar ham, stack ham yo'q — faqat "level":"error". Ikkinchisida "err":{} — bo'sh obyekt. Sabab Pino chuqur darsida aytilgan: Error ning message va stack xususiyatlari sanalmaydigan, JSON.stringify ularni ko'rmaydi. Tunda yiqilgan server logida bitta {"level":"error"} qolsa — nima bo'lganini hech kim bilmaydi.

Yechim — errors({ stack: true }) formati:

js
const log = winston.createLogger({
  format: combine(errors({ stack: true }), json()),
  transports: [new winston.transports.Console()],
});
log.error(x); // x — new Error("ulanish uzildi")
log.error("saqlanmadi", x);
log.error("meta", { err: x });

Biz ishga tushirdik (stack qisqa bo'lsin deb uni qo'lda berdik):

text
{"level":"error","message":"ulanish uzildi","stack":"Error: ulanish uzildi\n    at saqla (ombor.js:12:9)"}
{"level":"error","message":"saqlanmadi ulanish uzildi","stack":"Error: ulanish uzildi\n    at saqla (ombor.js:12:9)"}
{"err":{"level":"error"},"level":"error","message":"meta"}

Birinchi ikki usul endi ishlaydi (ikkinchisida xabarlar qo'shilib ketdi). Uchinchisi — { err: x } — baribir buzuq: "err":{"level":"error"}. errors() faqat yuqori darajadagi xatoni ko'radi. Qoida: Winston'da xatoni birinchi yoki ikkinchi argument qilib bering, { err } ichida emas. Pino'da esa aksincha — { err } to'g'ri yo'l. Ikki kutubxona, ikki xil odat.

Tekshirib ko'ring: Eski loyihada logger.error(err) qatorlari bor va loglarda faqat {"level":"error"} ko'rinyapti. Eng kichik tuzatish nima?

Javob

Logger yaratilgan joyda formatga errors({ stack: true }) ni birinchi qo'shish: combine(errors({ stack: true }), timestamp(), json()). Har logger.error(err) qatorini o'zgartirish shart emas. Tartib muhim: errors() json() dan oldin turishi kerak, aks holda JSON allaqachon bo'sh obyekt bilan yasalgan bo'ladi.

4. Transport'lar

Transport — Winston'da yozuv boradigan joy: Console, File, Http, Stream va jamoa paketlari (bulut xizmatlari). Har transport'ga alohida daraja va format berish mumkin:

js
const log = winston.createLogger({
  format: combine(timestamp(), json()),
  transports: [
    new winston.transports.Console({ level: "info" }),
    new winston.transports.File({
      filename: "logs/error.log",
      level: "error",
    }),
  ],
});

Bu "klassik" Winston sozlamasi: hammasi konsolga, xatolar alohida faylga. Pino'dagi targets ning o'xshashi. Farqi — Winston transport'lari asosiy threadda ishlaydi, Pino transport'lari esa alohida worker thread'da.

Pino va Winston'ni yonma-yon:

Mezon Pino 10.4.0 Winston 3.19.0
100 000 qator faylga 882 ms 1 246 ms
Paketlar / hajm 13 / 1.1 MB 29 / 1.8 MB
Xato obyekti { err } sukut ishlaydi errors() kerak
Format tashqarida (pino-pretty) ichida (printf)

Raqamlar — kanon o'lchovi (t42-log.txt, Windows NTFS, bitta o'lchov). Kanon Pino'ni tanladi: tezroq, kichikroq, xato obyekti sukut bo'yicha to'g'ri. Winston'ni esa o'qiy olish kerak — u ko'p loyihada bor.

5. Log rotatsiya

5.1 Muammo: fayl cheksiz o'sadi

Faylga yozilgan log hech qachon o'z-o'zidan kichraymaydi. Soniyasiga 100 so'rov, har biri ~250 baytli qator — kuniga ~2 GB. Rotatsiya uchun uchta yo'l bor:

flowchart TD
  L[Log qayerga?] --> F[faylga]
  L --> S[stdout]
  F --> W[ilova o'zi: winston-daily-rotate-file]
  F --> R[OS: logrotate]
  S --> D[Docker log driver: max-size, max-file]

Diagrammaga qarang: faylga yozsangiz, rotatsiyani yo ilova, yo OS qiladi. Stdout'ga yozsangiz — konteyner muhiti qiladi.

5.2 winston-daily-rotate-file

Kutubxona har kuni (yoki hajm chegarasida) yangi fayl ochadi va eskilarini o'chiradi:

js
import DailyRotateFile from "winston-daily-rotate-file";

const r = new DailyRotateFile({
  dirname: "rot",
  filename: "ilova-%DATE%.log",
  datePattern: "YYYY-MM-DD",
  maxSize: "20k", // shu hajmda yangi fayl
  maxFiles: 3, // faqat oxirgi 3 tasi qolsin
  auditFile: "rot/audit.json",
});
const log = winston.createLogger({
  format: winston.format.json(),
  transports: [r],
});
for (let i = 0; i < 2000; i++) {
  log.info("vazifa yaratildi", { sorovId: `s-${i}`, vazifaId: i });
}

maxFiles: 3 — demak papkada 3 ta log fayli qolishi kerak. Kanon o'lchadi (t42-log.txt, winston-daily-rotate-file 5.0.0). Ro'yxatda fayl nomlarining oxiriga qarang — .log, .log.1 … .log.8:

text
  audit.json                       986 bayt
  ilova-2026-10-24.log             20486 bayt
  ilova-2026-10-24.log.1           20532 bayt
  …
  ilova-2026-10-24.log.7           20559 bayt
  ilova-2026-10-24.log.8           11481 bayt

To'qqizta fayl qoldi, uchta emas. Kanon buni yozib qo'ydi, lekin sababini aniqlamadi. Biz qayta tekshirdik (o'sha versiyalar, Windows 11; uzun nomlar … bilan qisqartirildi):

text
500 ms dan keyin: 10 fayl — audit.json, ilova-2026-10-24.log, … .log.8
3000 ms dan keyin: 10 fayl — audit.json, ilova-2026-10-24.log, … .log.8
sekin yozish: 4 fayl — audit.json, ilova-2026-10-24.log.6, .log.7, .log.8; logRemoved: 6

Ikki xulosa. Birinchisi — kutish yordam bermadi: 3 soniyadan keyin ham 9 ta log fayli. Demak o'chirish kechikmagan — u bajarilmagan. Ikkinchisi — qatorlarni bo'lib yozganda (100 tadan, orasida 50 ms) kutubxona 6 ta faylni o'chirdi va 3 tasi qoldi, ya'ni maxFiles ishladi. audit.json esa birinchi holatda ham faqat 3 ta faylni "eslab qolgan".

Taxminimiz: juda tez rotatsiyada kutubxona eski faylni u hali ochiq turganda o'chirmoqchi bo'ladi. Windows ochiq faylni o'chirishga ruxsat bermaydi, xato esa jim yutiladi. Bu — taxmin, kutubxona kodida tasdiqlamadik. Linux'da sinalmagan. Amaliy saboq: rotatsiya sozlamasini o'z muhitingizda tekshiring, hujjatga ko'r-ko'rona ishonmang.

5.3 logrotate (Linux)

Linux serverlarda rotatsiyani ko'pincha ilova emas, OS'dagi logrotate dasturi qiladi. Namuna sozlama (/etc/logrotate.d/vazifalar-api), hujjat bo'yicha — biz sinamadik (mashinada Linux yo'q):

text
/var/log/vazifalar-api/*.log {
    daily
    rotate 7
    maxsize 100M
    compress
    missingok
    notifempty
    copytruncate
}
Qator Ma'nosi
daily, rotate 7 har kuni yangi, 7 ta eski qoladi
maxsize 100M kun tugamasa ham, 100 MB da aylantir
compress eskilarini gzip bilan siqish
copytruncate faylni nusxalab, aslini bo'shatish

copytruncate nega kerak? Node fayl deskriptorini ochiq ushlab turadi. logrotate faylni qayta nomlasa, Node eski (nomi o'zgargan) faylga yozishda davom etadi. Nusxalab-bo'shatish bu muammoni chetlab o'tadi, lekin nusxa va bo'shatish orasida bir necha qator yo'qolishi mumkin. Muqobili — ilovaga signal yuborib, faylni qayta ochtirish.

5.4 Docker log driver

Konteynerda ilova stdout'ga yozadi, Docker esa chiqishni o'zi saqlaydi. Sukut drayver (json-file) ham cheksiz o'sadi — chegarani berish kerak. Docker Compose'da (hujjat bo'yicha; Docker'ni 26-qismda o'rnatamiz, prod sozlamasi — 33-qismda):

text
services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Har konteyner uchun eng ko'pi 3 × 10 MB. Ilova kodi hech narsa bilmaydi — u faqat stdout'ga yozadi.

6. Fayl yoki stdout?

Savol Faylga Stdout'ga
Rotatsiya kimda? ilova yoki logrotate Docker / platforma
Bir nechta nusxa ishlasa har birida alohida fayl hammasi bitta oqimda yig'iladi
Konteyner o'chsa fayl u bilan yo'qoladi Docker saqlagan
Kod murakkabligi transport, papka, huquqlar yo'q

Kanon qarori: Pino + pino-http kirdi; Winston, morgan va fayl rotatsiyasi kirmadi. Sabab — vazifalar-api konteynerda ishlaydi (33-qism), u yerda stdout + Docker log driver yetarli. Faylga yozish qachon o'rinli? Konteynersiz eski serverda, yoki auditorlar alohida audit fayli talab qilganda.

7. API qadami o'rniga: tajriba

Bu darsda vazifalar-api ga kod qo'shilmaydi — Winston loyihaga kirmadi. Holat 44 davom etmoqda: commit qilmang, commit keyingi darsda.

O'rniga — 4-mashqda kanon tajribasini o'zingiz takrorlaysiz va o'z muhitingizdagi natijani ko'rasiz.

8. Ko'p uchraydigan xatolar

8.1 errors() formatisiz xato yozish

{"level":"error"} — xabarsiz qator. combine(errors({ stack: true }), …, json()).

8.2 Pino odati bilan { err } berish

Winston'da "err":{}. Xatoni argument sifatida: log.error("saqlanmadi", x).

8.3 Darajalarni aralashtirish

Winston'da level: "verbose" va http darajalari bor, Pino'da yo'q. Kutubxona almashtirilsa, LOG_LEVEL=verbose jarayonni yiqitadi (Pino'da «Noma'lum daraja» xatosi). Config'da ruxsat etilgan qiymatlar ro'yxati.

8.4 Rotatsiyasiz File transport

Disk to'ladi — server javob bermaydi, ba'zan baza ham yiqiladi. Rotatsiya yoki stdout.

8.5 Rotatsiyani tekshirmaslik

«winston-daily-rotate-file» bo'limidagi kabi maxFiles kutilgandek ishlamasligi mumkin. Yuk ostida bir marta sinab, papkadagi fayllar sonini tekshiring.

9. Mashqlar

1-mashq (oson): qaysi qatorlar yoziladi?

Winston logger level: "http" bilan. Chaqiruvlar: error, warn, info, http, verbose, debug. Qaysilari yoziladi?

Yechim

error, warn, info, http — to'rttasi. Winston'da chegara soni http: 3; soni 3 dan kichik yoki teng darajalar yoziladi. verbose (4) va debug (5) — yo'q. Pino'da qoida teskari: chegaradan katta yoki teng.

2-mashq (o'rta): Winston'dan Pino'ga ko'chirish

Har Winston chaqiruvini Pino ko'rinishiga o'tkazing: (a) log.info("vazifa yaratildi", { vazifaId: 7 }); (b) log.error("saqlanmadi", xato); (c) log.verbose("kesh topildi", { kalit: "v:7" }); (d) log.child({ sorovId: "a1" }).warn("sekin").

Yechim

(a) log.info({ vazifaId: 7 }, "vazifa yaratildi") — obyekt birinchi. (b) log.error({ err: xato }, "saqlanmadi") — err kaliti, Pino serializer'i stack va cause ni yozadi. (c) Pino'da verbose yo'q — eng yaqini debug: log.debug({ kalit: "v:7" }, "kesh topildi"). (d) log.child({ sorovId: "a1" }).warn("sekin") — o'zgarmaydi. Ko'chirishni grep bilan qiling (log\.\w+\(") va har natijani qo'lda ko'ring: argumentlar tartibi avtomatik almashtirishda eng ko'p buziladi.

3-mashq (qiyin): o'z rotatsiyangiz

Kutubxonasiz kichik rotatsiya mantiqini yozing — faqat qaror qismini, fayl operatsiyalarisiz. rotatsiyaRejasi(fayllar, maks) — fayllar — ["ilova.log", "ilova.log.1", …] ko'rinishidagi nomlar. Funksiya qaytarsin: { nomlash: [[eski, yangi], …], ochirish: [...] } — har .N → .N+1, asosiy ilova.log → .1, maks dan oshganlari o'chiriladi. Nomlash tartibi muhim: kattasidan boshlang (aks holda .1 ni .2 ga nomlaganda eski .2 ustiga yoziladi).

Yechim
js
function raqam(nom) {
  const m = nom.match(/\.(\d+)$/);
  return m ? Number(m[1]) : 0;
}

function rotatsiyaRejasi(fayllar, maks) {
  const tartib = [...fayllar].sort((a, b) => raqam(b) - raqam(a));
  const nomlash = [];
  const ochirish = [];
  for (const nom of tartib) {
    const n = raqam(nom);
    if (n + 1 > maks) ochirish.push(nom);
    else nomlash.push([nom, `ilova.log.${n + 1}`]);
  }
  return { nomlash, ochirish };
}

const fayllar = ["ilova.log", "ilova.log.1", "ilova.log.2"];
console.log(JSON.stringify(rotatsiyaRejasi(fayllar, 2)));

Konsolda:

text
{"nomlash":[["ilova.log.1","ilova.log.2"],["ilova.log","ilova.log.1"]],"ochirish":["ilova.log.2"]}

Kattasidan boshladik: avval .2 o'chiriladi, keyin .1 → .2, keyin asosiy → .1. Haqiqiy dastur (logrotate) aynan shu tartibda ishlaydi va qo'shimcha ravishda siqadi. Rejani alohida toza funksiya qilish — uni fayllarsiz test qilish imkonini beradi (Mock, stub, spy va fake darsida bu g'oyani davom ettiramiz).

4-mashq: API qadami o'rniga — tajriba

Bo'sh papkada npm i --save-exact winston@3.19.0 winston-daily-rotate-file@5.0.0. «winston-daily-rotate-file» bo'limidagi kodni ishga tushiring va rot/ papkasidagi fayllarni sanang. Keyin qatorlarni 100 tadan, orasida await new Promise((r) => setTimeout(r, 50)) bilan yozing. Natijalaringiz bizniki bilan mos keldimi? Linux yoki macOS'da bo'lsangiz — natija ayniqsa qiziq: u bizning taxminimizni tekshiradi.

Yechim

Windows'da kutiladigan natija — «winston-daily-rotate-file» bo'limidagidek: tez yozishda 9 ta log fayli, bo'lib yozishda 3 ta. Fayllar sonini readdirSync("rot").length bilan sanang. Agar Linux'da tez yozishda ham 3 ta qolsa — demak muammo Windows'ning ochiq faylni o'chirmasligida edi. Natijani vazifalar-api ga kiritmaysiz — bu faqat tajriba.

10. Real ishda

  • Eski loyihalar: 2015–2020 yillardagi Express loyihalarining katta qismida Winston va morgan (HTTP log middleware) birga turadi. Ularni o'qish va keyin Pino'ga ko'chirish — tez-tez beriladigan vazifa.
  • Serverlar: konteynersiz VPS'da logrotate deyarli har doim allaqachon o'rnatilgan — nginx va tizim loglari shu bilan aylanadi.
  • Kubernetes va bulut loglarni faqat stdout'dan oladi; faylga yozgan ilova u yerda "jim" qoladi.
  • Intervyu savollari: "Winston va Pino farqi?", "Log rotatsiya nima va kim qiladi?", "Konteynerda log qayerga yoziladi?", "copytruncate nima uchun?".

Xulosa

  • Winston: createLogger = daraja + format zanjiri (combine) + transport'lar; argumentlar ("xabar", meta).
  • Darajalar teskari: Winston'da error: 0, Pino'da fatal: 60; Winston'da http, verbose, silly bor.
  • errors({ stack: true }) siz log.error(xato) — xabarsiz qator; { err } meta ichida — baribir buzuq.
  • Rotatsiya uch yo'l: ilova (winston-daily-rotate-file), OS (logrotate), konteyner (Docker max-size, max-file).
  • Bizning sinovda tez yozishda maxFiles: 3 ishlamadi (9 fayl) — rotatsiyani o'z muhitingizda tekshiring.
  • Kanon: faylga yozmaydi; Pino → stdout → muhit.

Keyingi dars: Correlation ID va so'rov kuzatuvi — X-Request-Id ni AsyncLocalStorage orqali har log qatoriga yetkazish, servislararo uzatish va W3C traceparent; holat 44 commit'i.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Winston logger va log rotatsiya: transport, format va diskni to'ldirmaslik — IlmHamroh