IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API41/51-dars18 daqiqa
Mundarija (35)

Logging asoslari: log darajalari, JSON log va stdout backend'da

Qisqacha: Serverdagi console.log("xato", e) — ishlab chiqarishda yetarli emas. Professional log darajali (debug, info, warn, error, fatal) va strukturali bo'ladi: har qator — bitta JSON obyekt ({"daraja":"info","time":"…","sorovId":"…","msg":"vazifa yaratildi"}). Ilova logni faylga emas, stdoutga yozadi; uni yig'ish va saqlash — muhitning ishi. Parol, token, Authorization sarlavhasi va shaxsiy ma'lumot logga hech qachon tushmaydi.

Bu darsda

  • Log darajalarini to'g'ri tanlay olasiz va LOG_LEVEL bilan filtrlashni tushuntirasiz.
  • Matnli logni JSON logga aylantirib, uni dastur bilan qidirasiz.
  • Kontekst maydonlari (sorovId, vazifaId, ms) va "o'zgarmas xabar" qoidasini qo'llaysiz.
  • Nega ilova logni stdout'ga yozishi kerakligini (12-factor) tushuntira olasiz.
  • Logga nima tushmasligini bilasiz va log injection hujumini ko'rsata olasiz.

Oldin bilishingiz kerak: Global error handler, Process darajasidagi xatolar, Environment o'zgaruvchilar va .env, Backend'da sana va vaqt.

1. Nega bu kerak?

Tasavvur qiling: kechasi soat 03:00 da vazifalar-api bir nechta foydalanuvchiga 500 qaytardi. Ertalab Jasur aka so'raydi: "Nima bo'ldi? Kimga? Qaysi so'rovda?" Siz kompyuteringizda emassiz, server boshqa joyda. Javob faqat bitta joyda bo'lishi mumkin — logda.

Hozirgacha vazifalar-api (holat 40) xatoni shunday yozardi:

ts
console.error(`[${sorovId}]`, xato);

Bu ishlaydi, lekin uchta muammo bor. Birinchisi — darajasi yo'q: muhim xato va oddiy xabar bir xil ko'rinadi. Ikkinchisi — shakli erkin: har dasturchi o'zicha yozadi, qidirib bo'lmaydi. Uchinchisi — qayerga yozilishi kodga bog'langan.

Hayotiy o'xshatish: kema jurnali (bortjurnal). Kapitan har voqeani bir xil shaklda yozadi: vaqt, koordinata, nima bo'ldi, qanchalik jiddiy. "Shamol kuchaydi" va "kemaga suv kirdi" — ikkalasi ham jurnalda, lekin turli belgida. Bir yildan keyin ham kerakli kunni topish mumkin, chunki har yozuv bir xil tuzilgan. Bugungi dars — serverga shunday jurnal yozishni o'rgatish.

2. Log nima va uni kim o'qiydi

Log (log) — dastur ishlayotganda o'zi haqida yozib qoldiradigan voqealar ketma-ketligi. Har yozuv — bitta voqea: so'rov keldi, vazifa yaratildi, baza javob bermadi.

Logni ikki xil "o'quvchi" o'qiydi:

O'quvchi Nima kerak
Odam (dasturchi, navbatchi) qisqa, tushunarli xabar; dev'da rangli ko'rinish
Dastur (qidiruv, ogohlantirish tizimi) aniq maydonlar: vaqt, daraja, ID, raqamlar

Ishlab chiqarishda (production) loglar soniga minglab qator bo'lishi mumkin. Ularni odam ko'z bilan o'qimaydi — avval dastur filtrlaydi ("oxirgi bir soatdagi error lar", "sorovId si shu bo'lgan hamma qatorlar"), keyin odam natijani ko'radi. Shuning uchun log birinchi navbatda mashina uchun yoziladi.

Har log yozuvi uchta savolga javob beradi:

  1. Qachon? — aniq vaqt, UTC'da (sana va vaqt darsidagi qoida).
  2. Qanchalik muhim? — daraja.
  3. Nima bo'ldi va qayerda? — xabar va kontekst maydonlari.

3. Log darajalari

3.1 Oltita daraja

Log darajasi (log level) — yozuvning jiddiylik belgisi. Node ekotizimida (Pino, keyingi dars) shu oltitasi ishlatiladi; har biriga son mos keladi:

Daraja Son Qachon (vazifalar-api misolida)
fatal 60 jarayon ishlay olmaydi: uncaughtException, port band
error 50 so'rov bajarilmadi, sabab bizda: 500, fon ishi yiqildi
warn 40 g'alati, lekin ishlayapti: 404, sekin javob, webhook rad etildi
info 30 muhim biznes voqea: server ishga tushdi, vazifa yaratildi
debug 20 dasturchi uchun tafsilot: qaysi filtr, qaysi kursor
trace 10 juda mayda qadamlar: funksiyaga kirdi, chiqdi

Son qancha katta bo'lsa — voqea shuncha jiddiy. Qoida oddiy: "Kechasi navbatchini uyg'otish kerakmi?" — fatal va ko'p error lar uchun ha. "Ertalab ko'rib chiqish kerakmi?" — warn. "Faqat tahlil uchunmi?" — info.

3.2 Filtrlash: LOG_LEVEL

Logger'ga bitta chegara beriladi. Undan past darajadagi yozuvlar umuman yozilmaydi. Kichik logger yasab ko'ramiz — u brauzerda ham ishlaydi:

js
const DARAJALAR = {
  trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60,
};

function logYarat(daraja) {
  const chegara = DARAJALAR[daraja];
  const yoz = (nom) => (xabar) => {
    if (DARAJALAR[nom] < chegara) return; // chegaradan past
    console.log(`${nom.toUpperCase()} ${xabar}`);
  };
  return {
    debug: yoz("debug"),
    info: yoz("info"),
    warn: yoz("warn"),
  };
}

const log = logYarat("info");
log.debug("filtr: bajarildi=true"); // yozilmaydi
log.info("vazifa yaratildi");
log.warn("sekin javob");

Konsolda:

text
INFO vazifa yaratildi
WARN sekin javob

debug qatori chiqmadi: chegara info (30), debug esa 20. Chegarani "debug" qilsangiz — uchala qator ham chiqadi. O'zingiz sinab ko'ring.

Chegara koddan emas, muhitdan keladi. vazifalar-api da u env validatsiyasi darsidan beri bor:

ts
LOG_LEVEL: z
  .enum(["fatal", "error", "warn", "info",
    "debug", "trace", "silent"])
  .default("info"),

silent — hech narsa yozmaslik (testlarda qulay). Ishlab chiqarishda odatda info, muammo qidirilganda vaqtincha debug. Kodni o'zgartirish shart emas — faqat muhit o'zgaruvchisi.

Tekshirib ko'ring: Foydalanuvchi mavjud bo'lmagan /v1/vazifalar/99 ni so'radi va 404 oldi. Bu error mi yoki warn?

Javob

warn. Server to'g'ri ishladi — mijoz yo'q narsani so'radi. Agar har 404 error bo'lsa, haqiqiy xatolar ular orasida yo'qolib ketadi va navbatchi bekorga uyg'onadi. error — sabab bizda bo'lganda (500). Kanonda ham shunday: 4xx — warn, 5xx — error.

4. Strukturali log: har qator — bitta JSON

4.1 Matn va JSON yonma-yon

Matnli log odamga qulay: WARN sekin javob 1200 ms. Endi "1 soniyadan sekin hamma so'rovlarni toping" desak-chi? Matndan sonni ajratib olish uchun regex yozish kerak. Har dasturchi formatni biroz o'zgartirsa — regex buziladi.

Strukturali log (structured log) — har yozuv aniq nomli maydonlardan iborat obyekt. Amalda — bitta qatorda bitta JSON. Bu format JSON Lines (yoki NDJSON) deb ataladi.

Kichik loggerimizni JSON'ga o'tkazamiz. Vaqtni soat parametri orqali beramiz — shunda natija har safar bir xil bo'ladi (testlarda ham shu usul):

js
const DARAJALAR = { debug: 20, info: 30, warn: 40, error: 50 };

function logYarat(daraja, soat = () => new Date()) {
  const chegara = DARAJALAR[daraja];
  const yoz = (nom) => (maydonlar, xabar) => {
    if (DARAJALAR[nom] < chegara) return;
    const qator = {
      daraja: nom,
      time: soat().toISOString(),
      ...maydonlar,
      msg: xabar,
    };
    console.log(JSON.stringify(qator));
  };
  return { info: yoz("info"), warn: yoz("warn") };
}

const soat = () => new Date("2026-10-24T06:04:48.964Z");
const log = logYarat("info", soat);
log.info({ vazifaId: 1 }, "vazifa yaratildi");
log.warn({ ms: 1200 }, "sekin javob");

Konsolda:

text
{"daraja":"info","time":"2026-10-24T06:04:48.964Z","vazifaId":1,"msg":"vazifa yaratildi"}
{"daraja":"warn","time":"2026-10-24T06:04:48.964Z","ms":1200,"msg":"sekin javob"}

Odamga biroz og'irroq o'qiladi. Lekin endi dastur har maydonni aniq oladi. Dev'da esa uni chiroyli ko'rinishga aylantiradigan vosita bor — pino-pretty, keyingi darsda.

4.2 Mashina uchun qidiruv

JSON logning kuchi shunda: har qatorni JSON.parse qilib, oddiy JS bilan filtrlaymiz.

js
const loglar = [
  '{"daraja":"info","sorovId":"a1","ms":21,"msg":"POST 201"}',
  '{"daraja":"warn","sorovId":"b2","ms":1340,"msg":"GET 200"}',
  '{"daraja":"warn","sorovId":"c3","ms":3,"msg":"GET 404"}',
  '{"daraja":"info","sorovId":"d4","ms":1100,"msg":"GET 200"}',
];

const sekinlar = loglar
  .map((q) => JSON.parse(q))
  .filter((q) => q.ms >= 1000)
  .map((q) => `${q.sorovId} ${q.ms} ms`);

console.log(sekinlar);

Konsolda:

text
[ 'b2 1340 ms', 'd4 1100 ms' ]

Ishlab chiqarishda bu ishni maxsus tizimlar qiladi (Loki, Elasticsearch, bulut xizmatlari). Ular aynan shunday maydon bo'yicha qidiradi — shuning uchun format JSON. Ular bilan 34-qismda ishlaymiz.

4.3 Kanonda qanday ko'rinadi

vazifalar-api ning yakuniy holatidan (holat 44) haqiqiy qatorlar — server LOG_LEVEL=info bilan, curl so'rovlari (tekshiruv-44.txt):

text
{"daraja":"info","time":"2026-10-24T06:04:48.687Z","ilova":"vazifalar-api","manzil":"http://127.0.0.1:4198","muhit":"development","saqlash":"xotira","msg":"vazifalar-api: http://127.0.0.1:4198"}
{"daraja":"info","time":"2026-10-24T06:04:48.964Z","ilova":"vazifalar-api","sorovId":"kanon-44-0001","vazifaId":1,"msg":"vazifa yaratildi"}
{"daraja":"warn","time":"2026-10-24T06:04:49.051Z","ilova":"vazifalar-api","sorovId":"kanon-44-0002","req":{"method":"GET","url":"/v1/vazifalar/99","ip":"127.0.0.1","userAgent":"curl/8.19.0"},"res":{"status":404},"ms":3,"msg":"GET /v1/vazifalar/99 404"}

Uchta qatorga qarang. Ishga tushish — info, ichida manzil va muhit. Vazifa yaratildi — info, ichida vazifaId. Mavjud bo'lmagan vazifa — warn, ichida metod, yo'l, status va vaqt. Telefonda qatorlar uzun ko'rinadi — loglar shunday, ular ekran uchun emas.

5. Kontekst maydonlari

5.1 Xabar o'zgarmas, ma'lumot — maydonda

Ikki usulni solishtiring:

js
log.info({}, `vazifa ${id} yaratildi`); // ❌ har safar boshqa xabar
log.info({ vazifaId: id }, "vazifa yaratildi"); // ✅

Birinchisida 1000 ta vazifa — 1000 xil xabar. "Bugun nechta vazifa yaratildi?" deb sanash uchun yana regex kerak. Ikkinchisida xabar doim bir xil — msg = "vazifa yaratildi" bo'yicha sanaladi, vazifaId bo'yicha qidiriladi.

Qoida: xabar — voqea nomi, o'zgaruvchi qiymat — maydon.

5.2 Har so'rovda kerak bo'ladigan maydonlar

Maydon Nima uchun
time qachon (ISO, UTC)
daraja qanchalik muhim
ilova bir nechta servis loglari aralashsa — qaysisi
sorovId bitta so'rovning hamma qatorlari (Correlation ID)
ms so'rov qancha davom etdi
err xato: nomi, xabari, stack

5.3 Bola logger: maydonlarni bir marta bog'lash

So'rov ichida har qatorga sorovId ni qo'lda yozish — takror va xatoga moyil. Yechim — bola logger (child logger): asosiy logger'dan yasaladi va o'ziga berilgan maydonlarni har qatorga avtomatik qo'shadi.

js
function logYarat(asos = {}) {
  return {
    info: (maydonlar, xabar) =>
      console.log(
        JSON.stringify({ ...asos, ...maydonlar, msg: xabar }),
      ),
    bola: (qoshimcha) => logYarat({ ...asos, ...qoshimcha }),
  };
}

const log = logYarat({ ilova: "vazifalar-api" });
const sorovLogi = log.bola({ sorovId: "kor-0001" });
sorovLogi.info({ vazifaId: 1 }, "vazifa yaratildi");
sorovLogi.info({ vazifaId: 1 }, "vazifa o'chirildi");

Konsolda:

text
{"ilova":"vazifalar-api","sorovId":"kor-0001","vazifaId":1,"msg":"vazifa yaratildi"}
{"ilova":"vazifalar-api","sorovId":"kor-0001","vazifaId":1,"msg":"vazifa o'chirildi"}

Pino'da bu log.child({ sorovId }). Bola logger'ni so'rov ichidagi chuqur funksiyaga qanday yetkazish — Correlation ID darsining mavzusi.

Tekshirib ko'ring: Quyidagi qatorning nimasi noto'g'ri? log.error({}, "Xato: " + err)

Javob

Ikki narsa. Birinchisi — xato xabarga qo'shilib ketdi: har xato boshqa xabar, guruhlab bo'lmaydi. Ikkinchisi — "Xato: " + err faqat err.toString() ni oladi, ya'ni Error: ulanish uzildi. Stack (xato qayerda chiqqani) va cause yo'qoladi. To'g'risi: log.error({ err }, "kutilmagan xato") — logger xatoni o'zi maydonlarga ajratadi (Pino buni keyingi darsda qanday qilishini ko'ramiz).

6. Qayerga yozish: stdout (12-factor)

6.1 Ilova faylni boshqarmaydi

Logni faylga yozish tabiiy tuyuladi: logs/ilova.log. Lekin bu ilovaga ortiqcha mas'uliyat yuklaydi. Fayl qayerda turadi? To'lib qolsa kim o'chiradi? Server qayta ishga tushsa, konteyner yo'qolsa-chi? Uchta nusxa ishlasa — uchta fayl?

12-factor — bulutdagi ilovalar uchun 12 ta tamoyil (Heroku muhandislari yozgan, 12factor.net). Uning XI tamoyili — "Logs": ilova log oqimini faqat stdoutga yozadi va uni yo'naltirish yoki saqlash haqida qayg'urmaydi. Oqimni qayerga yuborish — muhitning ishi.

Rasmga qarang — bitta ilova, bitta stdout, lekin muhitga qarab har xil yo'l:

flowchart LR
  A[vazifalar-api] -->|JSON qatorlar| S((stdout))
  S -->|dev| P[pino-pretty]
  P --> T[terminal]
  S -->|konteyner| D[Docker log driver]
  D --> L[Loki / qidiruv]
  S -->|test| X[silent]

Dev'da terminalda o'qiysiz. Docker'da konteyner chiqishini Docker o'zi yig'adi va hajmini cheklaydi (33-qism). Undan keyin yig'uvchi tizim hamma serverlardan loglarni bir joyga to'playdi. Kod hech qayerda o'zgarmaydi.

6.2 stdout va stderr

Jarayonning ikkita chiqish oqimi bor: stdout (1) va stderr (2) — process obyekti darsida ko'rgansiz. Klassik CLI qoidasi: natija — stdout, xato — stderr. Server loglarida esa ko'pincha hammasi stdout'ga yoziladi — daraja maydonning o'zida bor ("daraja":"error"), ikkita oqimni sinxronlash shart emas. Pino ham sukut bo'yicha shunday qiladi.

vazifalar-api da bitta istisno bor: config xatosi. Logger config'dan (LOG_LEVEL) yasaladi, config o'qilmagan bo'lsa logger hali yo'q. Shuning uchun server.ts faqat shu joyda console.error ishlatadi va exit 78 bilan chiqadi (env validatsiyasi).

7. Nima log qilinadi, nima — hech qachon

7.1 Ro'yxat

Yozamiz Hech qachon
so'rov: metod, yo'l, status, ms parol, PIN, bir martalik kod
biznes voqea: yaratildi, o'chirildi token, API kalit, Authorization
xato: nomi, xabari, stack, cause Cookie, Set-Cookie sarlavhalari
tashqi xizmat: status, vaqt to'liq karta raqami
ishga tushish va to'xtash pasport, telefon, manzil
sekin so'rovlar (warn) so'rov tanasini butunligicha

Nega so'rov tanasini butunligicha yozmaymiz? Unda ertaga parol maydoni paydo bo'lishi mumkin — va u jimgina logga tushadi. Kerakli maydonni aniq tanlang.

Shaxsiy ma'lumot bilan ishlash O'zbekistonda qonun bilan tartibga solinadi ("Shaxsga doir ma'lumotlar to'g'risida"gi qonun, O'RQ-547 — 21-qismda ko'rgansiz). Log ham "ma'lumotlar bazasi" — uni ko'p odam o'qiydi, u uzoq saqlanadi, nusxalanadi.

7.2 Hujumchi nigohi: log injection

Hujumchi foydalanuvchi maydoniga qator ko'chirish (\n) qo'yib, logga soxta qator qo'shmoqchi bo'ladi. Matnli logda bu ishlaydi:

js
const ism = "Ali\n2026-10-24 INFO admin tizimga kirdi";

console.log(`2026-10-24 WARN kirish xato: ${ism}`);
const qator = { daraja: "warn", msg: "kirish xato", ism };
console.log(JSON.stringify(qator));

Konsolda:

text
2026-10-24 WARN kirish xato: Ali
2026-10-24 INFO admin tizimga kirdi
{"daraja":"warn","msg":"kirish xato","ism":"Ali\n2026-10-24 INFO admin tizimga kirdi"}

Birinchi usulda logda ikkinchi, soxta qator paydo bo'ldi: "admin tizimga kirdi". Tergov qilayotgan odam unga ishonishi mumkin. JSON'da esa \n qochirildi (\n ikki belgi bo'lib qoldi) — qator bitta, soxta yozuv yo'q.

Kanon ikkinchi himoyani ham qo'ygan. Mijoz yuboradigan X-Request-Id ishonchsiz — u logga yoziladi. Shuning uchun src/umumiy/sorov-id.ts uni faqat qisqa, xavfsiz belgilar bo'lsa qabul qiladi:

ts
// Mijozdan kelgan qiymat — ishonchsiz:
// faqat qisqa, xavfsiz belgilar
// (log'ga yoziladi — qator ko'chirish yo'q)
const YAROQLI = /^[\w.-]{8,128}$/;

Ikkinchi xavf — sir oqishi. Authorization: Bearer … sarlavhasini logga yozish — tokenni log o'qiy oladigan har kimga berish demak. Pino buni redact bilan yopadi (keyingi dars); kanonda test bor: Bearer SOXTA-TOKEN loglarda 0 marta.

8. API qadami: Pino'ni o'rnatish (holat 44, 1-qism)

Holat 44 to'rt darsga bo'lingan: bugungi asoslar, Pino chuqur, Winston va rotatsiya (tajriba) va Correlation ID. Hali commit qilmang — commit to'rtinchi darsda.

O'z loggerimizni ishlab chiqarishga olib chiqmaymiz: kanon tayyor, tez va sinalgan Pino ni tanladi (nega aynan u — keyingi darsda, o'lchov bilan). O'rnatish:

bash
npm i --save-exact pino@10.4.0

Bugun src/umumiy/log.ts ning birinchi qismi — darsdagi g'oyalar Pino sozlamasi bo'lib (kanon, holat 44):

ts
import { pino } from "pino";
import type { DestinationStream, Logger, LoggerOptions } from "pino";

export type Log = Logger;

export function logYarat(
  daraja: string,
  manzil?: DestinationStream,
): Log {
  const sozlama: LoggerOptions = {
    level: daraja,
    // vaqt ISO ko'rinishda (epoch ms emas) — odam ham o'qisin
    timestamp: pino.stdTimeFunctions.isoTime,
    base: { ilova: "vazifalar-api" },
    formatters: { level: (label) => ({ daraja: label }) },
  };
  return manzil === undefined ? pino(sozlama) : pino(sozlama, manzil);
}

Har sozlama — darsning bir bo'limi: level — chegara (LOG_LEVEL), timestamp — ISO vaqt, base — har qatordagi doimiy maydon, formatters.level — "level":30 o'rniga "daraja":"info". manzil — testlar uchun: logni stdout o'rniga xotiraga yozish.

src/server.ts — bugungi kesim, to'liq (holat 44 dagi fayldan farqi bitta: bogliqliklar ichida log hali yo'q — ilova uni keyingi darsda qabul qiladi; uzun izohlar bo'lindi):

ts
// server.ts — kirish nuqtasi: config (fail-fast) → ombor →
// server → jarayon hodisalari
// Ishga tushirish: npm start
// (node --env-file-if-exists=.env src/server.ts)
import { configYukla, ConfigXatosi } from "./config.ts";
import { ishgaTushir, omborYarat } from "./ishga-tushir.ts";
import { jarayonniBoshqar } from "./jarayon.ts";
import type { Config } from "./config.ts";
import { logYarat } from "./umumiy/log.ts";

let config: Config;
try {
  config = configYukla(process.env);
} catch (x) {
  if (!(x instanceof ConfigXatosi)) throw x;
  console.error(x.message);
  // EX_CONFIG (sysexits.h) — supervisor qayta-qayta ishga
  // tushirmasin, sozlamani tuzatish kerak
  process.exit(78);
}

const log = logYarat(config.LOG_LEVEL);
const { server, manzil } = await ishgaTushir({
  port: config.PORT,
  host: config.HOST,
  bogliqliklar: {
    ombor: await omborYarat(config),
    corsOriginlar: config.CORS_ORIGINS,
    tz: config.APP_TZ,
    trustProxy: config.TRUST_PROXY,
    hsts: config.HSTS,
  },
});
log.info(
  { manzil, muhit: config.NODE_ENV, saqlash: config.SAQLASH },
  `vazifalar-api: ${manzil}`,
);
jarayonniBoshqar(server, {
  info: (x) => { log.info(x); },
  // fatal sinxron yoziladi (pino stdout'ga sinxron) —
  // exit'dan oldin yo'qolmaydi
  fatal: (x, xato) => { log.fatal({ err: xato }, x); },
});

Holat 40 dagi console.log(...) (ishga tushish) va jarayon.ts adapteridagi console.log/console.error endi logger orqali. console.error faqat config xatosida qoldi.

Natija — «Kanonda qanday ko'rinadi» bo'limidagi birinchi qator.

Diqqat: Oraliq kesim. Bugun faqat ikki fayl o'zgardi: src/umumiy/log.ts (yangi, yuqoridagi logYarat) va src/server.ts. Qolgan fayllar holat 40 dagidek. Biz shu kesimni holat 44 nusxasida qayta yig'dik (Node 24.21.0, npm ci). Natija: tip 0, lint 0, testlar 54/54. npm start birinchi qatorni JSON qilib chiqardi; LOG_LEVEL=hammasi bilan esa server ishga tushmadi — → at LOG_LEVEL xatosi va exit 78.

9. Ko'p uchraydigan xatolar

9.1 Hammasi info

404, 500, sekin so'rov — hammasi bir darajada. Natijada LOG_LEVEL=warn qilsangiz, muhim narsa ham yo'qoladi. «Oltita daraja» jadvali bo'yicha tanlang.

9.2 O'zgaruvchi xabar ichida

`vazifa ${id} yaratildi` — har qator noyob, guruhlab bo'lmaydi. log.info({ vazifaId: id }, "vazifa yaratildi").

9.3 Xatoni satrga aylantirish

String(err) yoki "Xato: " + err — stack va cause yo'qoladi. log.error({ err }, "…").

9.4 Log qilib, yana tashlash

ts
catch (x) {
  log.error({ err: x }, "saqlanmadi"); // ❌ bu yerda ham
  throw x; // ... global ishlovchi ham yozadi
}

Bitta xato logda ikki-uch marta chiqadi va "ikkita muammo bor" degan tasavvur beradi. Yoki ushlab hal qiling (va yozing), yoki tashlang — yozishni global error handler ga qoldiring.

9.5 Ishlab chiqarishda console.log qoldirish

console.log darajasiz, strukturasiz va LOG_LEVEL ga bo'ysunmaydi. Hamma joyda bitta logger. Kanonda console.* faqat logger hali yo'q joyda (config xatosi) qolgan.

10. Mashqlar

1-mashq (oson): darajani tanlang

Har voqea uchun darajani ayting:

  • (a) server 3000 portda ishga tushdi.
  • (b) foydalanuvchi bo'sh matn yubordi va 422 oldi.
  • (c) baza ulanishi uzildi, so'rov 500 bilan tugadi.
  • (d) eksport 4 soniya davom etdi (odatda 0.1 s).
  • (e) uncaughtException — jarayon to'xtaydi.
  • (f) kursor qiymati ochildi: [3, 17].
Yechim

(a) info — muhim voqea, bir marta. (b) warn — mijoz xatosi, server sog'. (c) error — sabab bizda. (d) warn — ishlayapti, lekin g'alati sekin. (e) fatal — jarayon ishlay olmaydi. (f) debug — dasturchi uchun tafsilot, ishlab chiqarishda yozilmaydi.

2-mashq (o'rta): chegara va bola logger

«Bola logger» bo'limidagi logYarat ga daraja chegarasini qo'shing: logYarat(daraja, asos) — info va warn metodlari bo'lsin, chegaradan past qator yozilmasin, bola() chegarani saqlab qolsin. Har qatorda daraja maydoni ham bo'lsin. logYarat("warn", { ilova: "api" }) dan bola yasab, info va warn chaqiring. Eslatma: obyektni yoyish (...) — spread.

Yechim
js
const DARAJALAR = { info: 30, warn: 40 };

function logYarat(daraja, asos = {}) {
  const yoz = (nom) => (maydonlar, xabar) => {
    if (DARAJALAR[nom] < DARAJALAR[daraja]) return;
    const qator = { daraja: nom, ...asos, ...maydonlar, msg: xabar };
    console.log(JSON.stringify(qator));
  };
  return {
    info: yoz("info"),
    warn: yoz("warn"),
    bola: (qoshimcha) => logYarat(daraja, { ...asos, ...qoshimcha }),
  };
}

const api = logYarat("warn", { ilova: "api" });
const bola = api.bola({ sorovId: "x1" });
bola.info({}, "ko'rinmaydi");
bola.warn({ ms: 1500 }, "sekin javob");

Konsolda:

text
{"daraja":"warn","ilova":"api","sorovId":"x1","ms":1500,"msg":"sekin javob"}

bola yangi logYarat chaqiradi — chegara (daraja) va eski maydonlar unga uzatiladi. Pino'ning child() i ham chegarani ota logger'dan oladi.

3-mashq (qiyin): sirlarni yashirish

yashir(obyekt, kalitlar) funksiyasini yozing: u obyektning nusxasini qaytarsin, unda nomi kalitlar to'plamida bo'lgan har maydon (istalgan chuqurlikda) "[yashirildi]" ga almashsin. Asl obyekt o'zgarmasin. Kalitlar katta-kichik harfga sezgir bo'lmasin (Authorization ham, authorization ham). Sinang: { req: { headers: { Authorization: "Bearer x", host: "api" } }, foydalanuvchi: { ism: "Malika", parol: "123" } }, kalitlar — authorization, parol, token. Eslatma: Object.entries va Object.fromEntries — obyekt metodlari.

Yechim
js
function yashir(qiymat, kalitlar) {
  if (Array.isArray(qiymat)) {
    return qiymat.map((x) => yashir(x, kalitlar));
  }
  if (qiymat === null || typeof qiymat !== "object") return qiymat;
  return Object.fromEntries(
    Object.entries(qiymat).map(([k, v]) => [
      k,
      kalitlar.has(k.toLowerCase())
        ? "[yashirildi]"
        : yashir(v, kalitlar),
    ]),
  );
}

const kalitlar = new Set(["authorization", "parol", "token"]);
const asl = {
  req: { headers: { Authorization: "Bearer x", host: "api" } },
  foydalanuvchi: { ism: "Malika", parol: "123" },
};
console.log(JSON.stringify(yashir(asl, kalitlar)));
console.log(asl.foydalanuvchi.parol); // 123

Konsolda:

text
{"req":{"headers":{"Authorization":"[yashirildi]","host":"api"}},"foydalanuvchi":{"ism":"Malika","parol":"[yashirildi]"}}
123

Rekursiya har obyekt va massivga kiradi, Object.fromEntries yangi obyekt yasaydi — asl joyida qoladi. Pino'ning redact i boshqacha ishlaydi: kalit nomi emas, aniq yo'l beriladi (req.headers.authorization, *.parol) — bu tezroq va kutilmagan maydonlarni yashirib yubormaydi. Keyingi darsda ko'ramiz.

4-mashq: API qadami

vazifalar-api ga pino@10.4.0 ni o'rnating va «API qadami» bo'limidagi logYarat ni src/umumiy/log.ts ga yozing. server.ts dagi ishga tushish console.log ini log.info(...) bilan almashtiring. npm start — birinchi qator JSON bo'lsin. Keyin LOG_LEVEL=warn npm start — ishga tushish qatori chiqmasligi kerak (u info). Commit qilmang.

Yechim

Kod — «API qadami» bo'limida. Tekshiruv: npm start chiqishida {"daraja":"info",…,"msg":"vazifalar-api: http://127.0.0.1:…"}. LOG_LEVEL=warn bilan qator yo'q — server ishlayapti, lekin logger info ni yozmaydi. LOG_LEVEL=hammasi bilan esa server umuman ishga tushmaydi: config Zod enum i uni rad etadi va exit 78 (fail-fast — env validatsiyasi). Bu yaxshi: noto'g'ri daraja jim e'tiborsiz qolmaydi. Windows PowerShell'da muhit o'zgaruvchisi boshqacha beriladi: $env:LOG_LEVEL="warn"; npm start.

11. Real ishda

  • Navbatchilik: katta jamoalarda "on-call" navbatchi error/fatal soni oshganda telefonga xabar oladi. Daraja noto'g'ri tanlansa — yo kechasi bekorga uyg'onadi, yo haqiqiy muammoni o'tkazib yuboradi.
  • Log yig'ish: Grafana Loki, Elasticsearch (ELK), Datadog, Google Cloud Logging, AWS CloudWatch — hammasi JSON qatorlarni maydon bo'yicha qidiradi (34-qism).
  • Audit: to'lov tizimlari (Click, Payme) bilan ishlaydigan backend'da "kim, qachon, nima qildi" yozuvlari talab qilinadi. Ularda ham sirlar yo'q bo'lishi kerak.
  • Intervyu savollari: "Log darajalarini sanang va qachon qaysi biri?", "Nega JSON log?", "12-factor'da log qayerga yoziladi?", "Logga nimani yozmaysiz?".

Xulosa

  • Log — mashina uchun yoziladi: daraja, vaqt (UTC), xabar va kontekst maydonlari.
  • Oltita daraja: fatal 60, error 50, warn 40, info 30, debug 20, trace 10; chegara — LOG_LEVEL muhitdan.
  • Har qator — bitta JSON obyekt; xabar o'zgarmas, qiymatlar maydonda; bola logger maydonlarni bog'laydi.
  • Ilova logni stdout'ga yozadi (12-factor XI); fayl, rotatsiya va yig'ish — muhitning ishi.
  • Parol, token, Authorization, cookie va shaxsiy ma'lumot logga tushmaydi; JSON qator ko'chirishni qochiradi — log injection yopiladi.

Keyingi dars: Pino chuqur — pino-http bilan har so'rovga bitta qator, redact, serializer'lar, worker thread'dagi transport va pino-pretty; Pino, console.log va Winston tezligi o'lchov bilan.

Manbalar

  • The Twelve-Factor App: XI. Logs (2026-10-24 da tekshirildi).
  • Pino hujjati: API — log levels, child (pino 10.x).
  • OWASP: Logging Cheat Sheet — nimani yozmaslik va log injection.
  • Kanon: vazifalar-api holat 44 (src/umumiy/log.ts, src/umumiy/sorov-id.ts), natijalar/tekshiruv-44.txt.
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Logging asoslari: log darajalari, JSON log va stdout backend'da — IlmHamroh