Tuzilgan log: prod'da nima bo'layotganini haqiqatan ko'rish
Ishlab chiqarishdagi eng noqulay suhbat shunday boshlanadi: "foydalanuvchi to'lov o'tmadi deb yozyapti". Siz logga kirasiz va u yerda mana bu turadi:
Error: Request failed
at processTicksAndRejections (node:internal/process/task_queues:95:5)Qaysi foydalanuvchi? Qaysi so'rov? Qachon? Nima so'radi? Hech narsa yo'q. Log bor, lekin undan foyda yo'q.
Muammo log yozmaganingizda emas. Muammo — logni odam o'qishi uchun yozganingizda, aslida uni keyinchalik qidirish kerak bo'lganda.
Matn qatori va tuzilgan yozuv
Odatiy log shunday ko'rinadi:
console.log(`Foydalanuvchi ${userId} buyurtma ${orderId} yaratdi`)O'qishga qulay. Lekin ertaga sizga "shu foydalanuvchining bugungi barcha amallari" kerak bo'lganda, siz matn ichidan grep bilan qidirasiz — va formatni bir joyda o'zgartirgan zahoti qidiruv buziladi.
Tuzilgan log boshqacha yondashadi: har bir yozuv — bu obyekt.
logger.info({ userId, orderId, amount }, 'buyurtma yaratildi')Chiqishi JSON bo'ladi:
{"level":"info","time":1789000000,"userId":"u_123","orderId":"o_456","amount":42000,"msg":"buyurtma yaratildi"}Farqi shundaki, endi siz matndan emas, maydondan qidirasiz. "userId u_123 bo'lgan barcha yozuvlar" — bu bir qatorlik so'rov, formatga bog'liq emas.
Node.js'da men pino ishlataman: u tez va standart holda JSON yozadi.
import pino from 'pino'
export const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
})Eng katta g'alaba: so'rovni bog'lash
Tuzilgan log o'z-o'zidan yaxshi, lekin haqiqiy foyda bog'lashdan keladi.
Bitta HTTP so'rov ichida o'nlab log yozuvi paydo bo'ladi: kirish, autentifikatsiya, uchta baza so'rovi, tashqi API chaqiruvi, javob. Agar ular bir-biri bilan bog'lanmagan bo'lsa, siz ularni yuz boshqa so'rovning yozuvlari orasidan qo'lda ajratishga urinasiz.
Yechim — har bir so'rovga bitta identifikator berish va uni barcha yozuvlarga qo'shish:
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] ?? crypto.randomUUID()
req.log = logger.child({ requestId: req.id })
res.setHeader('x-request-id', req.id)
next()
})Uch qator kod. Natijasi katta: endi bitta identifikator bo'yicha qidirsangiz, butun so'rovning hikoyasi tartib bilan chiqadi.
Men identifikatorni javob headerida ham qaytaraman. Buning sababi amaliy: foydalanuvchi shikoyat qilganda undan shu qatorni so'rash mumkin va siz to'g'ridan-to'g'ri o'sha so'rovga tushasiz. Qidiruv o'rniga manzil.
Bir necha xizmat bo'lsa, identifikator ular orasida ham uzatiladi — o'sha x-request-id headeri bilan. Shunda log butun zanjir bo'ylab bog'lanadi.
Darajalarni to'g'ri ishlatish
Darajalar bo'yicha eng ko'p uchraydigan xato — hamma narsani info qilib yozish. Bunda daraja umuman ma'no tashlamaydi.
Men shunday chegara qo'yaman:
error— kimdir aralashuvi kerak. Ish bajarilmadi, foydalanuvchi zarar ko'rdi. Bu daraja alert bilan bog'lanadi.warn— kutilmagan holat, lekin tizim o'zi uddaladi. Masalan, tashqi API birinchi urinishda javob bermadi, ikkinchisida berdi.info— biznes hodisasi. Buyurtma yaratildi, foydalanuvchi ro'yxatdan o'tdi, hisobot generatsiya qilindi.debug— texnik tafsilot. Prod'da o'chirilgan, muammo tekshirilayotganda yoqiladi.
Asosiy sinov error uchun: agar bu yozuv chiqqanda hech kim hech narsa qilmasa, u error emas. Bu oddiy qoida error darajasini shovqindan tozalab turadi — va faqat shundagina alert'ga ishonish mumkin bo'ladi.
Maydonlarni bir xil nomlang
Bu zerikarli, lekin uzoq muddatda eng ko'p vaqt tejaydigan qoida.
Agar bir joyda userId, boshqasida user_id, uchinchisida uid yozsangiz, sizda uchta qidiruv va uchta xato imkoniyati bo'ladi. Loyihada bitta ro'yxat tuzing va unga rioya qiling.
Mening odatiy to'plamim: requestId, userId, organizationId, durationMs, statusCode, route. Qolganini vazifaga qarab qo'shaman.
Alohida tavsiya — davomiylikni har doim durationMs deb, sonda yozing. Matn ("1.2s") yozsangiz, keyin "500 ms dan sekin so'rovlar" degan so'rov yozolmaysiz.
Nimani hech qachon loglamaslik kerak
Bu qismni eng oxirida emas, eng boshida o'ylash kerak, chunki log odatda uzoq saqlanadi va ko'p odam ko'radi.
Hech qachon yozmang: parollar, tokenlar, sessiya kalitlari, karta ma'lumotlari, to'liq Authorization headeri, shaxsiy hujjat raqamlari.
Eng ko'p uchraydigan sizish yo'li — butun obyektni loglash:
logger.info({ user }, 'foydalanuvchi kirdi') // butun obyekt, parol xeshi ham ichidaTo'g'ri shakl — kerakli maydonlarni aniq tanlash:
logger.info({ userId: user.id, role: user.role }, 'foydalanuvchi kirdi')Shaxsiy ma'lumot bilan ishlaydigan tizimlarda bu qoida ayniqsa qattiq bo'lishi kerak. Maktab tizimida o'quvchilar va ota-onalar ma'lumoti bor, log esa odatda butun stekdagi eng kam himoyalangan joy.
Qo'shimcha himoya sifatida pino da maydonlarni avtomatik yashirish mumkin:
pino({
redact: ['req.headers.authorization', 'password', '*.token'],
})Loglarni qayerda saqlash
Boshlanishda hech qayerda — bu normal. Log stdout ga chiqadi, systemd yoki Docker uni yig'adi, siz journalctl yoki docker logs bilan o'qiysiz.
Bitta shart bor: faylga o'zingiz yozmang va rotatsiyani o'zingiz qilmang. Bu allaqachon yechilgan masala, va noto'g'ri yechim diskni to'ldirib serverni to'xtatadi.
Alohida yig'uvchi tizim (Loki, Elastic va shu kabilar) qachon kerak bo'ladi? Ikkita belgi bor: bir nechta server yoki konteyner paydo bo'lganda, va bir necha kunlik tarixni qidirish kerak bo'lganda. Shu ikkisi yo'q ekan, journalctl | grep mukammal ishlaydi.
Boshlash tartibi
Agar hozir loyihangizda console.log bo'lsa, hammasini bir kunda almashtirishga urinmang. Tartib shunday:
pinoqo'shing va bittaloggereksport qiling.- So'rov middleware'ida
requestIdyarating va uni javob headerida qaytaring. - Xatolar qayta ishlanadigan joyda
errordarajasini to'g'rilang — shovqinni chiqarib tashlang. - Maydon nomlari ro'yxatini yozib qo'ying.
- Yashiriladigan maydonlarni sozlang.
Beshta qadam, bir necha soat ish. Keyingi safar "foydalanuvchi shikoyat qildi" degan xabar kelganda siz logni ochib, bitta identifikator bilan butun hikoyani ko'rasiz — va bu farqni birinchi kundayoq sezasiz.