Mundarija (38)
- Bu darsda
- 1. Nega bu kerak?
- 2. Pino'ning sukut qatori
- 2.1 Birinchi qator
- 2.2 Kanon formati
- 2.3 Noma'lum daraja — jarayon yiqiladi
- 3. Bola logger: child
- 4. pino-http: har so'rovga bitta qator
- 4.1 Sukut sozlama — xavfli
- 4.2 Kanon sozlamasi: sorovLogi
- 4.3 Kanon topilmasi: req.url va originalUrl
- 5. redact: sirlarni yashirish
- 5.1 Yo'llar va yulduzcha
- 5.2 Kanon ro'yxati va censor
- 5.3 Narxi
- 6. Serializer'lar
- 6.1 Xato: err
- 6.2 O'z serializer'ingiz
- 7. Qayerga yozadi: destination va transport
- 7.1 pino.destination: sinxron va asinxron
- 7.2 Transport: alohida worker thread
- 7.3 pino-pretty — faqat dev
- 8. O'lchov: Pino haqiqatan tezmi?
- 9. API qadami: pino-http, redact, pino-pretty (holat 44, 2-qism)
- 10. Ko'p uchraydigan xatolar
- 10.1 pino-http sukut sozlamasi bilan prod'ga chiqish
- 10.2 Xatoni error emas, boshqa kalitda berish
- 10.3 Xabarni birinchi argument qilish
- 10.4 pino-pretty ni prod bog'liqlikka qo'yish
- 10.5 redact da yulduzcha chuqurligini noto'g'ri tushunish
- 11. Mashqlar
- 1-mashq (oson): natijani ayting
- 2-mashq (o'rta): customLogLevel funksiyasi
- 3-mashq (qiyin): asliYol va maxfiy query
- 4-mashq: API qadami
- 12. Real ishda
- Xulosa
- Manbalar
Pino logger chuqur: pino-http, redact, serializer va transport
Qisqacha: Pino — Node uchun tez, JSON yozadigan logger. U stdout'ga bitta qatorli JSON yozadi.
log.child({ sorovId })maydonlarni bog'laydi.redactko'rsatilgan yo'llardagi qiymatni (req.headers.authorization)[Redacted]ga almashtiradi. Serializer'lar katta obyektdan (so'rov, xato) keraklisini oladi.pino-httphar so'rov oxirida bitta qator yozadi — lekin sukut sozlamasi bilan uAuthorizationsarlavhasini ochiq yozib qo'ydi (biz ishga tushirdik). Transport'lar log ishini alohida worker thread'ga o'tkazadi;pino-pretty— faqat dev uchun.
Bu darsda
- Pino'ning sukut qatorini o'qiy olasiz va uni o'z formatingizga sozlaysiz.
child,redact, serializer'larni loyihada qo'llaysiz.pino-httpning xavfli sukut sozlamasini ko'rib, kanon sozlamasini tushuntirasiz.- Transport va
pino.destinationfarqini,pino-prettyqayerda ishlatilishini bilasiz. - Pino,
console.logva Winston tezligini halol o'lchov bilan solishtira olasiz.
Oldin bilishingiz kerak: Logging asoslari, worker_threads, Middleware va request lifecycle, Stack trace va error.cause.
1. Nega bu kerak?
Oldingi darsda kichik logger'ni o'zimiz yozdik va g'oyalarni ko'rdik: darajalar, JSON, bola logger, yashirish. Ishlab chiqarish uchun esa bundan ko'pi kerak. Xato obyektini to'g'ri yoyish, so'rov tugaganda vaqtni o'lchash, yuzlab so'rovda serverni sekinlashtirmaslik, sirlarni ishonchli yashirish.
Bularning hammasi tayyor kutubxonada bor. Node dunyosida eng ko'p ishlatiladiganlari — Pino va Winston. Fastify freymvorki Pino'ni ichiga qurgan, NestJS uchun nestjs-pino bor. Kanon vazifalar-api Pino'ni tanladi. Bu darsda u qanday ishlashini ichidan ko'ramiz, Winston — keyingi darsda.
Hayotiy o'xshatish: pochta bo'limining saralash mashinasi. Xatlar (log qatorlari) to'xtovsiz keladi. Mashina ularni bir xil konvertga soladi (JSON), maxfiy qismini qora bo'yoq bilan yopadi (redact), keraksiz ilovalarni olib tashlaydi (serializer) va alohida xonada jo'natadi (transport). Kassadagi navbat (asosiy thread) to'xtamaydi.
2. Pino'ning sukut qatori
2.1 Birinchi qator
import { pino } from "pino";
const log = pino();
log.info("salom");
log.info({ vazifaId: 7 }, "vazifa yaratildi");
log.debug("ko'rinmaydi");Biz ishga tushirdik (Node 24.21.0, pino 10.4.0, Windows 11; hostname qiymati — kompyuter nomi, uni … bilan almashtirdik):
{"level":30,"time":1792823686552,"pid":12260,"hostname":"…","msg":"salom"}
{"level":30,"time":1792823686553,"pid":12260,"hostname":"…","vazifaId":7,"msg":"vazifa yaratildi"}Maydonlarni o'qiymiz:
| Maydon | Ma'nosi |
|---|---|
level |
daraja soni: 30 — info |
time |
epoch millisekund (1970-yildan beri) |
pid, hostname |
qaysi jarayon va qaysi kompyuter |
msg |
xabar — oxirgi argument |
Birinchi argument obyekt bo'lsa — uning maydonlari qatorga qo'shiladi. debug qatori yo'q: sukut chegara — info.
Darajalar Pino'da son bilan saqlanadi (oldingi darsdagi jadval):
console.log(pino.levels.values);{ trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 }2.2 Kanon formati
Sukut format mashinaga qulay, lekin "time":1792823686552 ni odam o'qiy olmaydi. hostname esa konteynerda tasodifiy satr bo'ladi va shaxsiy kompyuter nomini oshkor qiladi. Kanon uchta sozlama bilan formatni o'zgartirdi:
const log = pino({
base: { ilova: "vazifalar-api" }, // pid, hostname o'rniga
timestamp: pino.stdTimeFunctions.isoTime,
formatters: { level: (label) => ({ daraja: label }) },
});
log.info("iso");{"daraja":"info","time":"2026-10-24T06:34:46.558Z","ilova":"vazifalar-api","msg":"iso"}base — har qatordagi doimiy maydonlar (sukut — pid va hostname). isoTime — UTC ISO satr. formatters.level — daraja nomi bilan, o'zbekcha maydonda.
2.3 Noma'lum daraja — jarayon yiqiladi
LOG_LEVEL=yoq bilan Pino yaratilganda nima bo'ladi? Biz ishga tushirdik:
C:\Users\…\node_modules\pino\lib\levels.js:194
throw Error(`default level:${defaultLevel} must be included in custom levels`)default level:yoq must be included in custom levels — "sukut daraja yoq darajalar ro'yxatida bo'lishi kerak". Pino tashlaydi, lekin xabari chalkash. Shuning uchun kanon LOG_LEVEL ni Pino'gacha, config'da Zod enum bilan tekshiradi — xato tushunarli va exit 78 (env validatsiyasi).
3. Bola logger: child
const log = pino({
base: { ilova: "vazifalar-api" },
timestamp: false,
});
const sorovLogi = log.child({ sorovId: "kor-0001" });
sorovLogi.info({ vazifaId: 1 }, "vazifa yaratildi");
const eksportLogi = sorovLogi.child({ bolim: "eksport" });
eksportLogi.warn("sekin");Biz ishga tushirdik:
{"level":30,"ilova":"vazifalar-api","sorovId":"kor-0001","vazifaId":1,"msg":"vazifa yaratildi"}
{"level":40,"ilova":"vazifalar-api","sorovId":"kor-0001","bolim":"eksport","msg":"sekin"}Bola logger'ning bolasi ham bo'ladi — maydonlar yig'iladi. Pino bola maydonlarini yaratish paytida bir marta JSON'ga aylantirib qo'yadi. Shuning uchun child arzon: har so'rovga bittadan yasash odatiy hol.
Tekshirib ko'ring:
sorovLogi.child({ sorovId: "boshqa" })yasasak, qatorda nechtasorovIdbo'ladi?
Javob
Ikkita. Biz ishga tushirdik (pino 10.4.0):
{"level":30,"sorovId":"kor-0001","sorovId":"boshqa","msg":"ikki"}Pino tezlik uchun maydonlarni tekshirmasdan ketma-ket yozadi. JSON.parse oxirgisini oladi, lekin boshqa o'quvchilar birinchisini olishi mumkin. Qoida: bola logger'da ota maydonini qayta yozmang — yangi nom bering (ichkiId).
4. pino-http: har so'rovga bitta qator
4.1 Sukut sozlama — xavfli
pino-http — Node http (va Express) uchun middleware: so'rov tugaganda (res ning finish hodisasi) bitta qator yozadi — metod, yo'l, status, davomiylik. Shuningdek har req ga req.log — shu so'rovning bola logger'ini qo'shadi.
Sukut sozlama bilan oddiy node:http serverda sinab ko'rdik (kod qisqartirilgan, port — listen(0)):
const http = pinoHttp({
logger: log,
genReqId: (req) => req.headers["x-request-id"] ?? "yangi",
});
const server = createServer((req, res) => {
http(req, res);
req.log.info("handler ichida");
res.end("ok");
});
// fetch: x-request-id: sinov-0001, authorization: Bearer SOXTABiz ishga tushirdik (pino-http 11.0.0; bitta qator — ikkinchisida xuddi shu req takrorlanadi):
{"level":30,"req":{"id":"sinov-0001","method":"GET","url":"/vazifalar","headers":{"host":"127.0.0.1:56184","connection":"keep-alive","x-request-id":"sinov-0001","authorization":"Bearer SOXTA","accept":"*/*","accept-language":"*","sec-fetch-mode":"cors","user-agent":"node","accept-encoding":"gzip, deflate"},"remoteAddress":"127.0.0.1","remotePort":56185},"msg":"handler ichida"}Uchta muammo ko'rinadi:
"authorization":"Bearer SOXTA"— token ochiq. Haqiqiy tizimda bu foydalanuvchining kaliti bo'lardi.- Handler ichidagi har
req.log.infoqatoriga butunreq(hamma sarlavhalar) qo'shildi — bitta so'rovda 5 qator bo'lsa, sarlavhalar 5 marta. - Yakuniy qator xabari — umumiy
"request completed", qidirish noqulay.
Sukut sozlama — ko'rgazma uchun, ishlab chiqarish uchun emas.
4.2 Kanon sozlamasi: sorovLogi
src/umumiy/log.ts (holat 44) — har qator izohi bilan:
export function sorovLogi(log: Log): RequestHandler[] {
const http = pinoHttp({
logger: log,
// ID — bizning X-Request-Id middleware'imizdan (holat 17)
genReqId: (req) =>
(req as unknown as { sorovId: string }).sorovId,
customAttributeKeys: { reqId: "sorovId", responseTime: "ms" },
// req.log ga faqat sorovId — har qatorga butun req yozilmasin
quietReqLogger: true,
// 4xx — warn, 5xx — error, qolgani info
customLogLevel: (_req, res, xato) =>
xato !== undefined || res.statusCode >= 500
? "error"
: res.statusCode >= 400 ? "warn" : "info",
// req.url router ichida qisqaradi ("/"); originalUrl — to'liq
customSuccessMessage: (req, res) =>
`${String(req.method)} ${asliYol(req)} ` +
String(res.statusCode),
serializers: {
// to'liq sarlavhalar o'rniga keraklilari
req: (req) => ({
method: req.method,
url: req.raw?.originalUrl ?? req.url,
ip: req.remoteAddress,
userAgent: req.headers["user-agent"],
}),
res: (res) => ({ status: res.statusCode }),
},
});
return [http /* , AsyncLocalStorage — Correlation ID darsida */];
}Kod qisqartirilgan: tiplar va customErrorMessage olib tashlangan. Faylning to'liq yakuniy matni — Correlation ID darsining «API qadami» bo'limida (holat 44 shu darsda tugaydi).
Natija — tekshiruv-44.txt dan haqiqiy qator:
{"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"}Endi: daraja status bo'yicha (warn), xabar o'qiladi, sarlavhalardan faqat userAgent, vaqt ms maydonida.
4.3 Kanon topilmasi: req.url va originalUrl
Birinchi variantda xabar req.url dan yasalgan edi. Natija: "POST / 201". Nega /?
express.Router darsini eslang: v1.use("/vazifalar", router) ichida Express req.url dan ulangan qismni kesib tashlaydi — router o'zini ildizda deb biladi. pino-http esa finish paytida, router ichida qisqargan req.url ni oldi. To'liq yo'l req.originalUrl da saqlanadi. Kanon shu sababli asliYol() yordamchisini yozdi (so'rov parametrlarini ham kesadi — ?q=... logga tushmasin).
Tekshirib ko'ring: Nega serializer'da
ipvauserAgentbor-u,headersbutunligicha yo'q?
Javob
Ikki sabab. Hajm: sarlavhalar har qatorni 3–4 barobar uzaytiradi, minglab so'rovda bu disk va pul. Maxfiylik: sarlavhalarda Authorization, Cookie bo'ladi — oq ro'yxat (faqat keraklisini olish) qora ro'yxatdan (redact) ishonchliroq: ertaga yangi maxfiy sarlavha qo'shilsa, u avtomatik tashqarida qoladi.
5. redact: sirlarni yashirish
5.1 Yo'llar va yulduzcha
Redact (redact) — "qora bo'yoq bilan yopish". Pino'ga JSON yo'llar ro'yxati beriladi, shu yo'llardagi qiymat yoziladigan paytda almashtiriladi:
const log = pino({
base: undefined,
timestamp: false,
redact: ["req.headers.authorization", "*.parol"],
});
log.info(
{
req: { headers: { authorization: "Bearer SOXTA", host: "x" } },
user: { ism: "Ali", parol: "123" },
},
"kirish",
);Biz ishga tushirdik:
{"level":30,"req":{"headers":{"authorization":"[Redacted]","host":"x"}},"user":{"ism":"Ali","parol":"[Redacted]"},"msg":"kirish"}*.parol — "istalgan bitta darajadagi obyektning parol maydoni". Yulduzcha bitta darajani bildiradi: a.b.parol uchun *.*.parol kerak. Kalit nomida tire bo'lsa — qavs: 'req.headers["x-api-key"]'.
5.2 Kanon ro'yxati va censor
export const YASHIRIN = [
"req.headers.authorization",
"req.headers.cookie",
'req.headers["x-api-key"]',
"res.headers['set-cookie']",
"*.parol",
"*.token",
];
// logYarat ichida:
redact: { paths: YASHIRIN, censor: "[yashirildi]" },censor — almashtiruvchi matn. remove: true bersangiz — maydon butunlay o'chadi:
{"level":30,"user":{"ism":"Ali"},"msg":"remove"}Kanonda remove emas, censor tanlangan: "maydon bor edi, lekin yashirildi" degan iz qolsin — tergovda foydali.
Serializer req dan sarlavhalarni allaqachon olib tashlagan bo'lsa, redact nega kerak? Bu — ikkinchi qatlam. Kimdir ertaga log.info({ req }, …) deb xom so'rovni yozib yuborsa ham, token yopiq qoladi. Kanon testi: Bearer SOXTA-TOKEN bilan so'rov, keyin barcha log qatorlarida SOXTA-TOKEN qidiriladi — 0 marta.
5.3 Narxi
redact bepul emas. Kanon o'lchovi (t42-log.txt, 100 000 qator faylga): oddiy Pino — 882 ms, ikki yo'lli redact bilan — 1 326 ms. Ya'ni yashirish yozishni ~1.5 barobar sekinlashtirdi. Yo'llar ro'yxatini qisqa tuting va ** kabi chuqur qidiruvdan qoching.
6. Serializer'lar
6.1 Xato: err
Serializer (serializer) — maydon logga yozilishidan oldin uni aylantiradigan funksiya. Pino'da err kalitiga sukut serializer biriktirilgan — u Error ni oddiy obyektga yoyadi:
const asl = new Error("ECONNRESET");
const x = new Error("ulanish uzildi", { cause: asl });
log.error({ err: x }, "xato");Biz ishga tushirdik (stack qatorlari qisqartirildi — …):
{"level":50,"err":{"type":"Error","message":"ulanish uzildi: ECONNRESET","stack":"Error: ulanish uzildi\n at file:///…/e1.mjs:21:11\n …\ncaused by: Error: ECONNRESET\n at …"},"msg":"xato"}Diqqat qiling: message ga cause ning xabari qo'shildi (ulanish uzildi: ECONNRESET), stack oxirida esa caused by: qismi bor. error.cause darsidagi zanjir logda to'liq saqlandi.
Nega bu muhim? Error ning message va stack xususiyatlari sanalmaydigan (non-enumerable). Oddiy JSON.stringify(xato) ularni ko'rmaydi va {} yozadi. Winston'da aynan shu tuzoqni keyingi darsda ko'ramiz.
6.2 O'z serializer'ingiz
const log = pino({
base: undefined,
timestamp: false,
serializers: {
vazifa: (v) => ({ id: v.id, uzunlik: v.matn.length }),
},
});
const v = { id: 3, matn: "Non olish #uy", teglar: ["uy"] };
log.info({ vazifa: v }, "saqlandi");{"level":30,"vazifa":{"id":3,"uzunlik":13},"msg":"saqlandi"}Vazifa matnini logga yozmadik — faqat uzunligini. Foydalanuvchi matnida shaxsiy narsa bo'lishi mumkin ("Malikaga 998 90 … ga qo'ng'iroq").
7. Qayerga yozadi: destination va transport
7.1 pino.destination: sinxron va asinxron
Sukut bo'yicha Pino stdout'ga sinxron yozadi: log.info qaytganda qator allaqachon OS'ga berilgan. Jarayon yiqilsa ham oxirgi qatorlar yo'qolmaydi — fatal uchun muhim (kanon jarayon.ts dagi izoh shu haqda).
Asinxron rejim (pino.destination({ sync: false, minLength: 4096 })) qatorlarni bufer'da yig'ib, katta bo'laklarda yozadi. Nazariy jihatdan tezroq — kanon o'lchovida esa boshqacha chiqdi (pastdagi grafik).
7.2 Transport: alohida worker thread
Transport (transport) — log qatorlarini qayta ishlaydigan va jo'natadigan modul. Pino uni alohida worker thread da ishga tushiradi: formatlash, faylga yozish, tarmoqqa jo'natish asosiy thread'ni (so'rovlarni bajaradigan) bloklamaydi.
const t = pino.transport({
targets: [
{
target: "pino/file",
options: { destination: "./ilova.log" },
level: "info",
},
{
target: "pino-pretty",
options: { colorize: false },
level: "warn",
},
],
});
const log = pino({ base: undefined, timestamp: false }, t);
log.info({ vazifaId: 1 }, "vazifa yaratildi");
log.warn({ ms: 1200 }, "sekin so'rov");Biz ishga tushirdik — terminalda (faqat warn va yuqori, chiroyli):
WARN: sekin so'rov
ms: 1200ilova.log faylida (info va yuqori, JSON):
{"level":30,"vazifaId":1,"msg":"vazifa yaratildi"}
{"level":40,"ms":1200,"msg":"sekin so'rov"}Bitta logger, ikkita manzil, har biriga o'z chegarasi. Shuningdek process.exit(0) dan oldin yozilgan qator ham transport orqali faylga yetib bordi (sinadik) — Pino chiqishda worker'ni kutadi.
Kanonda transport yo'q: 12-factor bo'yicha ilova faqat stdout'ga yozadi, qolgani muhitda. Transport — muhit stdout'ni yig'a olmaydigan holatlar uchun (masalan, serverda to'g'ridan logni tashqi xizmatga jo'natish).
flowchart LR
A[log.info] --> B{manzil}
B -->|sukut| S[stdout, sinxron]
B -->|transport| W[worker thread]
W --> F[pino/file]
W --> P[pino-pretty]
W --> X[tashqi xizmat]Rasmga qarang: transport bilan log.info faqat qatorni worker'ga uzatadi; og'ir ish o'sha yerda.
7.3 pino-pretty — faqat dev
pino-pretty JSON'ni odam o'qiydigan ko'rinishga aylantiradi. Kanon uni devDependencies ga qo'ydi va faqat dev skriptida quvur orqali ishlatadi:
{
"scripts": {
"dev": "node --watch --env-file-if-exists=.env src/server.ts | pino-pretty"
}
}Kanon tajribasidagi ko'rinish (t42-log.txt):
[11:02:05.059] INFO (9428): vazifa yaratildi
sorovId: "s-0"
vazifaId: 0
ms: 3Ishlab chiqarishda nega yo'q? U har qatorni qayta parse qilib formatlaydi — CPU sarflaydi. Natija JSON emas — yig'uvchi tizim maydonlarni ajrata olmaydi. Va u prod bog'liqlik bo'lib, hajmni oshiradi (19 paket).
8. O'lchov: Pino haqiqatan tezmi?
Pino o'zini "eng tez Node logger" deb tanishtiradi. Kanon buni tekshirdi: 100 000 qator, bitta jarayon, Windows NTFS (t42-log.txt; band mashina, bitta o'lchov):
- console.log → quvurbola jarayon stdout'i553 ms
- pino sinxron fayl882 ms
- pino asinxron fayl1 056 ms
- winston File1 246 ms
- pino + redact1 326 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-24: Node 24.21.0, Windows 11, i5-12500H (t42-log.txt), bitta o'lchov
Halol o'qiymiz:
console.logeng tez chiqdi — lekin u boshqa joyga yozdi (bola jarayon stdout quvuri), Pino esa faylga. Bu o'lchovdan "console tezroq" degan xulosa chiqmaydi — ular teng sharoitda emas.- Teng sharoitda (ikkalasi faylga): Pino 882 ms, Winston 1 246 ms — Pino ~1.4 barobar tez.
- Asinxron rejim bu sinovda sinxrondan sekinroq chiqdi (1 056 ms). Sababini kanon aniqlamagan; bitta o'lchov.
redacteng qimmat — ehtiyot bo'ling.
Serverda log narxi qanday? Kanon o'lchovlarida mavjud bo'lmagan vazifaga so'rov holat 40 da (log yo'q) 5 116 req/s, holat 44 da (har 404 — bitta warn qatori) 5 176 req/s chiqdi (tekshiruv-40.txt, tekshiruv-44.txt; autocannon, bitta mashina, bitta o'lchov). Log bilan biroz tezroq — demak farq o'lchov shovqini ichida. Bu yukda bitta log qatorining narxi sezilmadi; 100 000 qatorli sinov esa uni alohida ko'rsatadi (yuqoridagi grafik).
9. API qadami: pino-http, redact, pino-pretty (holat 44, 2-qism)
Holat 44 ning ikkinchi qismi. Hali commit qilmang — commit Correlation ID darsida.
npm i --save-exact pino-http@11.0.0
npm i -D --save-exact pino-pretty@13.2.0O'zgarishlar:
| Fayl | O'zgarish |
|---|---|
src/umumiy/log.ts |
YASHIRIN va redact; sorovLogi() |
src/ilova.ts |
log bog'liqligi (sukut logYarat("silent")); app.use(sorovId()) dan keyin app.use(sorovLogi(log)) |
src/server.ts |
bogliqliklar ga log |
package.json |
dev — | pino-pretty |
test/yordamchi.ts |
xotiraLogi() — testda loglarni o'qish |
Xato ishlovchi, xizmat, eksport va idempotentlik bugun o'zgarmaydi — ular hali console.error bilan yozadi. Ular logger'ga Correlation ID darsida o'tadi: o'sha qatorlarda sorovId bo'lishi kerak, buning uchun AsyncLocalStorage kerak.
Bugungi kesimda src/umumiy/log.ts — to'liq (yakuniy fayldan AsyncLocalStorage qismi olib tashlangan; u Correlation ID darsida qo'shiladi):
// log.ts — strukturali (JSON) log: Pino. stdout'ga yoziladi
// (12-factor) — faylga yozish, rotatsiya, yig'ish — muhitning
// ishi (Docker, Loki — 34-qism).
import { pino } from "pino";
import type { DestinationStream, Logger, LoggerOptions } from "pino";
import { pinoHttp } from "pino-http";
import type { RequestHandler } from "express";
export type Log = Logger;
/** Hech qachon logga tushmasligi kerak bo'lgan joylar —
* qiymat "[yashirildi]" ga almashadi */
export const YASHIRIN = [
"req.headers.authorization",
"req.headers.cookie",
'req.headers["x-api-key"]',
"res.headers['set-cookie']",
"*.parol",
"*.token",
];
export function logYarat(
daraja: string,
manzil?: DestinationStream,
): Log {
const sozlama: LoggerOptions = {
level: daraja,
// vaqt ISO ko'rinishda (epoch ms emas) — odam ham o'qisin;
// Toshkentga aylantirish — ko'rsatuvchi vositada
timestamp: pino.stdTimeFunctions.isoTime,
redact: { paths: YASHIRIN, censor: "[yashirildi]" },
base: { ilova: "vazifalar-api" },
formatters: { level: (label) => ({ daraja: label }) },
};
return manzil === undefined ? pino(sozlama) : pino(sozlama, manzil);
}
/**
* pino-http: har so'rov oxirida bitta qator
* (metod, yo'l, status, vaqt),
* sorovId — bizning X-Request-Id.
* sorovId() middleware'idan KEYIN qo'yiladi
*/
function asliYol(req: { url?: string | undefined }): string {
const r = req as { originalUrl?: string; url?: string };
return (r.originalUrl ?? r.url ?? "").split("?")[0] ?? "";
}
export function sorovLogi(log: Log): RequestHandler[] {
const http = pinoHttp({
logger: log,
genReqId: (req) =>
(req as unknown as { sorovId: string }).sorovId,
customAttributeKeys: { reqId: "sorovId", responseTime: "ms" },
// req.log child logger'iga faqat sorovId
// bog'lanadi — har qatorga butun req yozilmasin
quietReqLogger: true,
// 4xx — warn, 5xx — error (xato ishlovchi o'zi ham yozadi),
// qolgani info
customLogLevel: (_req, res, xato) =>
xato !== undefined || res.statusCode >= 500
? "error"
: res.statusCode >= 400
? "warn"
: "info",
// req.url — router ichida qisqaradi ("/"),
// originalUrl — to'liq yo'l
customSuccessMessage: (req, res) =>
`${String(req.method)} ${asliYol(req)} ` +
String(res.statusCode),
customErrorMessage: (req, res) =>
`${String(req.method)} ${asliYol(req)} ` +
String(res.statusCode),
serializers: {
// to'liq sarlavhalar o'rniga keraklilari (hajm va maxfiylik)
req: (req: {
method: string;
url: string;
raw?: { originalUrl?: string };
headers: Record<string, string | undefined>;
remoteAddress?: string;
}) => ({
method: req.method,
url: req.raw?.originalUrl ?? req.url,
ip: req.remoteAddress,
userAgent: req.headers["user-agent"],
}),
res: (res: { statusCode: number }) => ({
status: res.statusCode,
}),
},
});
return [http];
}src/ilova.ts — holat 40 dan farqi (qolgan qatorlar o'zgarmaydi):
--- a/ilova.ts
+++ b/ilova.ts
import type { XatoLogi } from "./umumiy/xato-ishlovchi.ts";
+import { logYarat, sorovLogi } from "./umumiy/log.ts";
+import type { Log } from "./umumiy/log.ts";
@@ Bogliqliklar @@
xatoLogi?: XatoLogi;
+ /** Pino logger (sukut — "silent": testlarda jim) */
+ log?: Log;
@@ ilovaYarat parametrlari @@
xatoLogi,
+ log = logYarat("silent"),
tz = "Asia/Tashkent",
@@ middleware tartibi @@
app.use(sorovId());
+ app.use(sorovLogi(log));src/server.ts da bogliqliklar obyektiga bitta qator qo'shiladi — log, (hsts: config.HSTS, dan keyin).
Tartib muhim: sorovLogi sorovId() dan keyin turadi — genReqId tayyor req.sorovId ni oladi. Undan oldin qo'ysangiz, sorovId maydoni undefined bo'ladi.
Testlar uchun yordamchi — logni stdout o'rniga xotiradagi massivga yozadi (logYarat ning ikkinchi parametri — «Logging asoslari» darsidagi manzil):
export function xotiraLogi(daraja = "info") {
const qatorlar: Record<string, unknown>[] = [];
const log = logYarat(daraja, {
write: (q: string) => {
qatorlar.push(JSON.parse(q) as Record<string, unknown>);
},
});
return { log, qatorlar };
}Pino uchun { write(satr) } obyekti — yetarli manzil. Testda keyin qatorlar.find((q) => q.msg === "…") bilan tekshiramiz. ilovaYarat da esa sukut log = logYarat("silent") — testlar jim ishlaydi.
Diqqat: Oraliq kesim. Biz bugungi kesimni holat 44 nusxasida yig'ib tekshirdik (Node 24.21.0,
npm ci): tip 0, lint 0, testlar 54/54. Bitta tuzoqni ham ko'rdik. Xato ishlovchini bugunoqlog.error(...)ga o'tkazsak,xato.test.tsdagi 500 testi qizardi (53/54). Sabab — test haliconsole.errorchaqirilishini kutadi. Shuning uchun xato ishlovchi va uning testi keyingi qadamda, birga o'zgaradi.
10. Ko'p uchraydigan xatolar
10.1 pino-http sukut sozlamasi bilan prod'ga chiqish
Authorization va hamma sarlavhalar logda («Sukut sozlama — xavfli» bo'limi). redact + req serializer + quietReqLogger.
10.2 Xatoni error emas, boshqa kalitda berish
log.error({ xato: x }) — xato kalitiga serializer yo'q. Biz sinadik: "xato":{} — xabar ham, stack ham yo'q. err kaliti (yoki serializers ga o'zingiz qo'shing).
10.3 Xabarni birinchi argument qilish
log.info("vazifa", { vazifaId: 1 }) — Pino'da obyekt birinchi turadi. Biz sinadik: natija {"level":30,"msg":"vazifa"} — vazifaId jimgina yo'qoldi (obyekt "format argumenti" deb olinadi). log.info({ vazifaId: 1 }, "vazifa yaratildi").
10.4 pino-pretty ni prod bog'liqlikka qo'yish
devDependencies va faqat dev skriptida quvur.
10.5 redact da yulduzcha chuqurligini noto'g'ri tushunish
*.parol faqat bitta daraja ichida ishlaydi: { a: { b: { parol } } } yopilmaydi. Aniq yo'l yoki *.*.parol; testda tekshiring.
11. Mashqlar
1-mashq (oson): natijani ayting
Logger redact: ["user.parol", "*.token"] bilan yasalgan, base: undefined, timestamp: false. Qator: log.info({ user: { ism: "Sardor", parol: "1", token: "t" }, kalit: { token: "k" } }, "x"). Nima yoziladi?
Yechim
{"level":30,"user":{"ism":"Sardor","parol":"[Redacted]","token":"[Redacted]"},"kalit":{"token":"[Redacted]"},"msg":"x"}user.parol — aniq yo'l. *.token — har birinchi darajadagi obyektning token i: user.token ham, kalit.token ham. Natijani biz ishga tushirib oldik (pino 10.4.0).
2-mashq (o'rta): customLogLevel funksiyasi
Kanondagi daraja tanlovini alohida toza funksiya qiling: darajaTanla(status, xato) — xato bo'lsa yoki status ≥ 500 — "error", 400–499 — "warn", qolgani — "info". Qo'shimcha: 499 (mijoz so'rovni tashlab ketdi) — "info" bo'lsin (bu bizning xato emas va ogohlantirish ham emas). [200, 304, 404, 422, 499, 500] va xatoli 200 uchun chiqaring.
Yechim
function darajaTanla(status, xato) {
if (xato !== undefined || status >= 500) return "error";
if (status === 499) return "info";
return status >= 400 ? "warn" : "info";
}
const statuslar = [200, 304, 404, 422, 499, 500];
console.log(statuslar.map((s) => `${s}:${darajaTanla(s)}`).join(" "));
console.log(darajaTanla(200, new Error("finish'dan keyin")));Konsolda:
200:info 304:info 404:warn 422:warn 499:info 500:error
errorToza funksiya — Express'siz test qilinadi. pinoHttp({ customLogLevel: (_req, res, x) => darajaTanla(res.statusCode, x) }) bilan ulanadi. 499 — standart kod emas (nginx kiritgan), lekin loglarda ko'p uchraydi.
3-mashq (qiyin): asliYol va maxfiy query
Kanondagi asliYol(req) so'rov parametrlarini kesadi. Ba'zan esa ?limit=20&sort=-yaratildi foydali. Funksiya yozing: xavfsizYol(originalUrl, ruxsat) — yo'l + faqat ruxsat to'plamidagi parametrlar (tartibi saqlansin), qolganlari [yashirildi]. Sinang: "/v1/vazifalar?limit=20&token=abc&q=Malika", ruxsat — limit, sort. Eslatma: URL va URLSearchParams — URL darsi.
Yechim
function xavfsizYol(asl, ruxsat) {
const url = new URL(asl, "http://x");
for (const [k] of url.searchParams) {
if (!ruxsat.has(k)) url.searchParams.set(k, "[yashirildi]");
}
const qism = url.searchParams.toString();
return url.pathname + (qism ? `?${decodeURIComponent(qism)}` : "");
}
const ruxsat = new Set(["limit", "sort"]);
const yol = "/v1/vazifalar?limit=20&token=abc&q=Malika";
console.log(xavfsizYol(yol, ruxsat));
console.log(xavfsizYol("/v1/vazifalar", ruxsat));Konsolda:
/v1/vazifalar?limit=20&token=[yashirildi]&q=[yashirildi]
/v1/vazifalarnew URL(nisbiy, asos) — nisbiy yo'lni parse qilish uchun soxta asos. set mavjud kalitning qiymatini joyida almashtiradi — tartib saqlanadi. Qidiruv matni (q=Malika) ham shaxsiy bo'lishi mumkin — shuning uchun oq ro'yxat.
4-mashq: API qadami
pino-http va pino-pretty ni o'rnating, log.ts ga YASHIRIN, redact va sorovLogi ni qo'shing, ilova.ts da sorovId() dan keyin ulang. npm run dev bilan bitta POST va bitta yo'q vazifaga GET yuboring: birinchisi INFO, ikkinchisi WARN ko'rinishida chiqsin. Keyin Authorization: Bearer SOXTA sarlavhasi bilan so'rov yuborib, chiqishda SOXTA yo'qligini tekshiring. Commit qilmang.
Yechim
Kod — «Kanon sozlamasi» va «API qadami» bo'limlarida. npm start (pretty'siz) bilan JSON ko'rinishida tekshirish qulayroq: npm start > server.log, so'rovlardan keyin server.log da SOXTA ni qidiring — 0 natija. Kanon jonli tekshiruvida xuddi shunday: SOXTA-TOKEN logda: 0 marta.
12. Real ishda
- Fastify Pino'ni ichiga qurgan:
fastify({ logger: true })— xuddi shu JSON qatorlar. NestJS'danestjs-pino(30-qism). - Bulut loglari: Google Cloud, AWS va Grafana Loki JSON qatordan
level,msgni avtomatik tanib oladi; ba'zilariseveritynomini kutadi —formatters.levelaynan shuning uchun bor. - Xavfsizlik auditi: "loglarda token bormi?" — tez-tez beriladigan savol;
redactva uning testi — tayyor javob. - Intervyu savollari: "Pino nega tez?", "Transport nima uchun worker thread'da?", "Logda sirlarni qanday yashirasiz?", "
pino-prettyni prod'da ishlatasizmi?".
Xulosa
- Pino — JSON yozadigan tez logger; sukut qatori:
level,time,pid,hostname,msg—base,timestamp,formattersbilan sozlanadi. childmaydonlarni bog'laydi va arzon;pino-httphar so'rovgareq.logva yakuniy qator beradi.pino-httpsukut sozlamasiAuthorizationni ochiq yozdi —redact,reqserializer vaquietReqLoggershart.errserializermessage,stackvacausezanjirini saqlaydi; o'z serializer'ingiz — oq ro'yxat.- Transport — alohida worker thread; kanon esa faqat stdout (12-factor);
pino-pretty— faqat dev. - O'lchov: teng sharoitda Pino Winston'dan ~1.4 barobar tez;
redact~1.5 barobar qimmatlashtiradi.
Keyingi dars: Winston va log rotatsiya — Winston'ning format va transport tizimi, xato obyektidagi tuzoq, log fayllari diskni to'ldirmasligi uchun rotatsiya va nega kanon faylga yozmaydi.
Manbalar
- Pino hujjati: API, Redaction, Transports (pino 10.x, 2026-10-24 da ko'rildi).
- pino-http: README (11.x) —
quietReqLogger,customLogLevel,customAttributeKeys. - Kanon:
vazifalar-apiholat 44 (src/umumiy/log.ts,test/yordamchi.ts),natijalar/tekshiruv-44.txt,natijalar/tajriba/t42-log.txt.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!