8.26-bob: Audit log va faoliyat tarixi
8-QISM — NestJS (chuqur) · 26-mavzu · Amaliy real mavzu
1. Kirish va motivatsiya
Endi yana bir real, ayniqsa jiddiy tizimlarda zarur mavzu — audit log (audit jurnali, faoliyat tarixi). Bu — "kim, nima, qachon, qayerdan qildi" degan savolga javob beradigan o'zgarmas yozuvlar: "Admin Ali 2026-06-22 14:30 da 5-mijozni o'chirdi", "Vali buyurtma narxini 50000 dan 30000 ga o'zgartirdi". Bu — moliyaviy tizim (bank, to'lov — 8.19), SaaS (multi-tenant — 8.25), tibbiyot, davlat tizimlarida majburiy (compliance — qonun talabi); va har jiddiy ilovada foydali (xavfsizlik tergovi, nizolar, debug, hisobdorlik).
Audit log oddiy log'dan 5.12-bob farq qiladi: oddiy log — texnik (debug, xato — vaqtincha); audit log — biznes hodisalari (kim nima qildi — doimiy, o'zgarmas, qonuniy dalil). Misol: kimdir mijoz ma'lumotini o'g'irladi deb shubha bo'lsa — audit log kim ko'rganini ko'rsatadi; mijoz "men buyurtma bermadim" desa — audit log dalil; admin xato qilsa — kim, qachon. Audit log'ning eng muhim xususiyati — o'zgarmaslik (immutable — yozilgach o'zgartirib/o'chirib bo'lmaydi — aks holda dalil emas).
Bu bob: audit log nima (oddiy log'dan farq), nima loglash kerak, audit yozuvi tuzilishi (kim/nima/qachon/eski-yangi qiymat), avtomatik audit (interceptor/subscriber — qo'lda emas), o'zgarish tarixi (eskiyangi — diff), o'zgarmaslik (immutable), ko'rish/qidirish (admin panel), va xavfsizlik/compliance. Bu bob 5.12 (log), 8.6 (interceptor), 8.25 (multi-tenant audit), 14 (xavfsizlik) bilan bog'liq. Audit log — ishonch, hisobdorlik, qonuniylik asosi.
O'xshatish: audit log — bankning kuzatuv kamerasi va jurnali. Oddiy log 5.12-bob — qo'riqchining kundalik eslatmasi (bugun nima bo'ldi — vaqtincha). Audit log — kuzatuv yozuvi: kim qachon kirdi, kim seyfga tegdi, qancha pul olindi — o'zgartirib bo'lmaydigan (kamera yozuvi tahrirlanmaydi — aks holda dalil emas). Jinoyat bo'lsa — yozuvni ko'rib, kim qilganini bilasiz; nizo bo'lsa — dalil. Bankda bu qonun talabi (majburiy). Audit log — raqamli kuzatuv tizimi: har muhim harakat abadiy yoziladi.
Nega muhim?
- Compliance — qonun talabi (bank, to'lov, tibbiyot, SaaS).
- Xavfsizlik tergovi — kim nima qildi (buzilish, o'g'irlik).
- Nizo/dalil — "men qilmadim" audit log ko'rsatadi.
- Hisobdorlik — jamoa, admin harakatlari shaffof.
2. Nazariya — chuqur tushuntirish
2.1. Audit log vs oddiy log
ODDIY LOG 5.12-bob: AUDIT LOG:
Texnik (debug, xato, so'rov) Biznes hodisalari (kim nima qildi)
Vaqtincha (rotatsiya, o'chadi) Doimiy (o'zgarmas, saqlanadi)
Dasturchi uchun (debug) Audit/huquq/biznes uchun (dalil)
Format erkin Tuzilgan (kim/nima/qachon — 2.3)
console/fayl/ELK DB (qidiriladigan, o'zgarmas)
Oddiy log — "nima ishladi"; audit log — "kim nima qildi" (dalil)Audit log vs oddiy log: oddiy log 5.12-bob — texnik (debug, xato — vaqtincha, dasturchi uchun); audit log — biznes hodisalari (kim nima qildi — doimiy, o'zgarmas, dalil uchun). Audit log — tuzilgan (kim/nima/qachon — 2.3), DB'da (qidiriladigan), o'zgarmas 2.6-bob. Ikkalasi alohida maqsad (audit log oddiy log o'rnini bosmaydi). Audit — huquqiy/biznes; log — texnik.
2.2. Nima loglash kerak
AUDIT LOG GA YOZILADI (muhim biznes harakatlari):
O'zgartirish (yaratish/yangilash/o'chirish — ma'lumot)
Auth hodisalari (login, logout, parol o'zgarishi, muvaffaqiyatsiz kirish)
Ruxsat o'zgarishi (rol berish, admin qilish — 8.7)
Moliyaviy (to'lov, refund, narx o'zgarishi — 8.19)
Maxfiy ma'lumot ko'rish (mijoz ma'lumoti, hisobot)
Eksport/import (ma'lumot yuklash — 8.21)
Har o'qish (GET — ko'p, ahamiyatsiz — faqat maxfiy)
Sirlar (parol qiymati, token — log'ga emas! 14)Nima loglash: muhim biznes harakatlari — o'zgartirish (CRUD), auth (login/parol), ruxsat (rol — 8.7), moliyaviy (to'lov — 8.19), maxfiy ma'lumot ko'rish, eksport. Har GET'ni loglama (ko'p, ahamiyatsiz — faqat maxfiy ko'rishni). Sirlarni loglama (parol qiymati, token — audit log'ga ham emas — 14). To'g'ri tanlov: muhim, kerakli (audit log foydali bo'lishi uchun — shovqin emas).
2.3. Audit yozuvi tuzilishi
AUDIT LOG yozuvi (kim/nima/qachon/qayerdan):
┌──────────────┬──────────────────────────────────────┐
│ id │ yozuv ID │
│ userId │ KIM (qaysi foydalanuvchi) │
│ action │ NIMA ("user.delete", "order.update") │
│ resource │ qaysi obyekt ("User", "Order") │
│ resourceId │ qaysi yozuv (5-mijoz) │
│ oldValue │ ESKI qiymat (o'zgarishdan oldin) │
│ newValue │ YANGI qiymat (keyin) │
│ ip │ QAYERDAN (IP manzil) │
│ userAgent │ qurilma/brauzer │
│ tenantId │ qaysi tenant (SaaS — 8.25) │
│ createdAt │ QACHON (vaqt — o'zgarmas) │
└──────────────┴──────────────────────────────────────┘Audit yozuvi: to'liq kontekst — kim (userId), nima (action —
user.delete), qaysi obyekt (resource + resourceId), eskiyangi qiymat (o'zgarish — 2.5), qayerdan (IP, userAgent), tenant (SaaS — 8.25), qachon (createdAt). Bu ma'lumotlar — tergov/dalil uchun yetarli bo'lishi kerak (kim, qachon, nima, qayerdan — to'liq rasm). Tuzilgan format (qidiriladigan — 2.7).
2.4. Avtomatik audit (interceptor — qo'lda emas)
// Qo'lda audit yozish — unutiladi. Avtomatik (interceptor — 8.6)
@Injectable()
export class AuditInterceptor implements NestInterceptor {
constructor(private auditService: AuditService, private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const audit = this.reflector.get("audit", context.getHandler()); // @Audit metadata
if (!audit) return next.handle(); // belgilanmagan o'tkazib yubor
const req = context.switchToHttp().getRequest();
return next.handle().pipe(
tap((natija) => { // muvaffaqiyatdan keyin (8.6)
this.auditService.yoz({
userId: req.user?.id, action: audit.action,
resourceId: req.params.id, ip: req.ip,
newValue: natija, tenantId: req.tenantId, // (8.25)
});
}),
);
}
}
// @Audit({ action: "user.delete" }) @Delete(":id") ochir() {}Avtomatik audit (interceptor — 8.6): qo'lda audit yozish unutiladi (har joyga). Yechim: interceptor +
@Audit()dekorator (metadata) — belgilangan endpoint avtomatik loglanadi.tap(muvaffaqiyatdan keyin — 8.6). Yoki TypeORM subscriber (DB o'zgarishini avtomatik — 8.13, 8.25: 2.5). Avtomatik — ishonchli (inson unutmaydi). Bu — audit'ning to'g'ri amaliyoti (qo'lga ishonmaslik).
2.5. O'zgarish tarixi (eski yangi — diff)
// Yangilashda — eski va yangi qiymat (nima o'zgardi)
async yangila(id: string, dto: UpdateOrderDto, userId: string) {
const eski = await this.repo.findOneBy({ id }); // o'zgarishdan OLDIN
const yangi = await this.repo.save({ ...eski, ...dto });
await this.auditService.yoz({
userId, action: "order.update", resource: "Order", resourceId: id,
oldValue: this.diff(eski, dto), // faqat o'zgargan maydonlar
newValue: this.diff(yangi, dto),
});
return yangi;
}
// Faqat o'zgargan maydonlarni ajratish (diff)
private diff(obj: any, changes: any) {
return Object.keys(changes).reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {});
}O'zgarish tarixi (eskiyangi — diff): yangilashda eski qiymat (oldin) va yangi qiymat (keyin) saqlanadi — "narx 50000 30000". Faqat o'zgargan maydonlar (diff — butun obyekt emas — toza). Bu — tergov uchun muhim ("kim narxni o'zgartirdi, qanchadan qanchaga"). Versiyalash (har o'zgarish — yangi versiya) ham mumkin. Diff — audit'ning eng qimmatli qismi.
2.6. O'zgarmaslik (immutable — dalil bo'lishi uchun)
AUDIT LOG O'ZGARMAS bo'lishi kerak (dalil bo'lishi uchun):
- Faqat INSERT (yozish) — UPDATE/DELETE yo'q
- Hatto admin ham o'zgartira/o'chira olmaydi
- DB darajasida himoya (faqat insert ruxsati, RLS)
- Yoki: append-only saqlash (alohida DB, WORM)
- Hash zanjiri (blockchain ruhida — har yozuv oldingisi hash'i bilan)
O'zgartiriladigan audit log = DALIL EMAS (manipulyatsiya mumkin)O'zgarmaslik (immutable — audit'ning eng muhim xususiyati): audit log yozilgach o'zgartirib/o'chirib bo'lmasligi kerak (aks holda dalil emas — manipulyatsiya qilinishi mumkin). Faqat INSERT (UPDATE/DELETE yo'q — hatto admin ham). DB darajasida (faqat insert ruxsati, RLS — 8.25). Yuqori talab: hash zanjiri (har yozuv oldingisining hash'ini saqlaydi — buzilsa bilinadi — blockchain ruhi). O'zgaruvchan audit — befoyda.
2.7. Ko'rish va qidirish (admin panel)
// Audit log'ni ko'rish/qidirish (admin — tergov)
async qidir(filter: AuditFilterDto) {
const qb = this.repo.createQueryBuilder("a");
if (filter.userId) qb.andWhere("a.userId = :u", { u: filter.userId }); // kim
if (filter.action) qb.andWhere("a.action = :a", { a: filter.action }); // nima
if (filter.resource) qb.andWhere("a.resource = :r", { r: filter.resource });
if (filter.from) qb.andWhere("a.createdAt >= :from", { from: filter.from }); // qachon
if (filter.to) qb.andWhere("a.createdAt <= :to", { to: filter.to });
return qb.orderBy("a.createdAt", "DESC").limit(filter.limit || 50).getMany();
}
// Admin: "Ali oxirgi hafta nima qildi?", "5-mijozni kim o'zgartirdi?"Ko'rish/qidirish: audit log foydali bo'lishi uchun qidiriladigan bo'lishi kerak (admin panel — tergov). Filtr: kim (userId), nima (action), qaysi obyekt (resource), qachon (sana oralig'i — 2.3). Indeks (userId, action, createdAt, resourceId — tez qidiruv — 6.10). Katta hajm eski yozuvlarni arxivga (cold storage). Admin "kim, qachon, nima" savollariga javob topadi. Bu — audit'ning amaliy qiymati.
2.8. Saqlash va hajm (katta ma'lumot)
AUDIT LOG hajmi tez o'sadi (har harakat — yozuv):
- Indeks (tez qidiruv — userId/action/createdAt — 6.10)
- Partitioning (sana bo'yicha — eski oylar alohida — 6.10)
- Arxivlash (eski yozuvlar cold storage / S3 — arzon)
- Saqlash muddati (compliance: bank 5 yil, GDPR cheklov)
- Alohida DB/jadval (asosiy DB'ni sekinlatmasin)
Audit log = ko'p yoziladi (insert-heavy) — optimizatsiya kerakSaqlash/hajm: audit log tez o'sadi (har harakat — yozuv — insert-heavy). Optimizatsiya: indeks (tez qidiruv — 6.10), partitioning (sana bo'yicha — eski oylar alohida — 6.10), arxivlash (eski S3 — arzon), saqlash muddati (compliance — bank 5 yil; GDPR — ortiqcha saqlamaslik). Alohida jadval/DB (asosiy DB'ni sekinlatmasin — yozuv ko'p). Katta hajm — boshqaruv talab qiladi.
2.9. Multi-tenant va xavfsizlik audit (8.25, 14)
tenantId har yozuvda (SaaS — kim qaysi tenant'da — 8.25)
Audit log o'zi himoyalangan (faqat admin ko'radi — 8.7)
Sirlarni yozma (parol/token qiymati — 14, 2.2)
O'zgarmas (immutable — 2.6)
Auth hodisalari (muvaffaqiyatsiz kirish — buzilish belgisi)
IP/userAgent (qayerdan — tergov — 2.3)
Audit log ko'rilishi ham loglanadi (kim audit'ni ko'rdi)Multi-tenant + xavfsizlik audit:
tenantId(SaaS — kim qaysi tenant'da — 8.25); audit log o'zi himoyalangan (faqat admin — 8.7); sirlarni yozmaslik (14); o'zgarmas 2.6-bob; auth hodisalari (muvaffaqiyatsiz kirish — buzilish belgisi — 14); IP/userAgent (qayerdan). Hatto "kim audit log'ni ko'rdi" ham loglanadi (audit'ning auditi — eng yuqori xavfsizlik). Audit — xavfsizlikning ko'zi.
2.10. Aktyorni olish — request context (AsyncLocalStorage / nestjs-cls)
MUAMMO: subscriber DB darajasida ishlaydi 2.4-bob — u yerda
"req.user" YO'Q (HTTP so'rov konteksti emas). Kim o'zgartirdi?
YECHIM: request context (so'rov davomida saqlanadigan xotira):
- AsyncLocalStorage (Node yadrosi) — async oqim bo'ylab kontekst
- nestjs-cls (CLS — Continuation Local Storage — NestJS o'ramasi)
- Middleware/guard so'rov boshida userId/tenantId/ip'ni "store"ga qo'yadi
- Subscriber/service ISTALGAN chuqurlikda cls.get("userId") oladi
Aktyorni parametr sifatida har joyga uzatish shart emas (toza)Aktyorni olish (request context): audit'ning eng nozik muammosi — subscriber 2.4-bob DB darajasida ishlaydi, u yerda
req.useryo'q (HTTP kontekstidan uzoq). Aktyorni (kim) har metodga parametr qilib uzatish — iflos (barcha imzolarni buzadi). To'g'ri yechim: request context — so'rov davomida yashaydigan xotira. Node'ningAsyncLocalStorage(async oqim bo'ylab kontekst saqlaydi) yoki uning NestJS o'ramasi nestjs-cls (CLS). Middleware so'rov boshidauserId/tenantId/ip'ni "store"ga qo'yadi; subscriber yoki service ixtiyoriy chuqurlikdacls.get("userId")orqali oladi. Bu — subscriber-audit'ni real ishlatishning kaliti (Misol 4'dagiRequestContext— aynan shu).
2.11. Async audit (asosiy oqimni bloklamaslik — event / queue)
MUAMMO: audit yozuvi (DB insert) asosiy so'rovni sekinlatadi
(foydalanuvchi audit yozilishini kutmasligi kerak).
YECHIM 1 — @nestjs/event-emitter (jarayon ichida async):
eventEmitter.emit("audit", data) listener alohida yozadi
(asosiy oqim davom etadi; audit "fon"da)
YECHIM 2 — queue (BullMQ — 8.18) — ishonchli, alohida jarayon:
auditQueue.add("write", data) worker DB'ga yozadi
(Redis'da navbat; worker qulasa — qayta urinish; yo'qolmaydi)
Muvozanat: event — oddiy, tez (lekin jarayon qulasa yo'qoladi);
queue — ishonchli (Redis'da saqlanadi) — muhim audit uchunAsync audit (bloklamaslik): audit yozuvi ham DB insert — asosiy so'rovni sekinlatishi mumkin (foydalanuvchi audit yozilishini kutmasligi kerak). Ikki yechim. 1)
@nestjs/event-emitter(jarayon ichida):eventEmitter.emit("audit", data)— listener alohida yozadi, asosiy oqim davom etadi (fon vazifasi). Oddiy va tez, lekin jarayon qulasa yozuv yo'qoladi. 2) Queue (BullMQ — 8.18):auditQueue.add(...)— Redis'dagi navbatga qo'yiladi, alohida worker DB'ga yozadi. Ishonchli (worker qulasa — qayta urinish, yozuv yo'qolmaydi). Muvozanat: oddiy audit uchun event yetarli; moliyaviy/compliance audit (yo'qolmasligi shart) uchun queue. Interceptor'datapichidaemit— asosiy javob kechikmaydi.
2.12. Tamper-evident — hash zanjiri (buzilishni aniqlash)
IMMUTABLE 2.6-bob yetarli emas bo'lishi mumkin — DB'ga to'g'ridan
kirgan kishi yozuvni o'zgartirishi/o'chirishi mumkin. Aniqlash?
HASH ZANJIRI (blockchain ruhi — tamper-evident):
- Har yozuv: hash = SHA256(oldingi_hash + shu_yozuv_maydonlari)
- Yozuvlar zanjir bo'lib bog'lanadi (bloknot: har varaq oldingisiga)
- Bittasini o'zgartirish uning va KEYINGI barcha hash buziladi
- Davriy tekshiruv: zanjirni qayta hisoblab, mos kelishini tekshirish
O'zgartirishni to'sib bo'lmasa ham, ANIQLASH mumkin (dalil buzildi)Tamper-evident (hash zanjiri): immutable 2.6-bob app darajasida himoya, lekin DB'ga to'g'ridan kirgan admin/hujumchi yozuvni o'zgartirishi mumkin. Buni aniqlash uchun — hash zanjiri (blockchain ruhi). Har yozuvda
hash = SHA256(oldingi yozuv hash'i + shu yozuv maydonlari)— yozuvlar zanjir bo'lib bog'lanadi (bloknot: har varaq oldingisiga tikilgan). Bitta yozuvni o'zgartirsa — uning va undan keyingi barcha yozuvlar hash'i mos kelmay qoladi (buzilish "tarqaladi"). Davriy tekshiruv zanjirni qayta hisoblab, buzilganini aniqlaydi. Bu — moliyaviy/yuridik audit uchun eng yuqori kafolat (o'zgartirishni to'sib bo'lmasa ham, dalilning buzilganini isbotlaydi). Misol 11'da amali.
2.13. Saqlash joyi tanlovi (DB / Elasticsearch / fayl)
AUDIT LOG QAYERDA saqlanadi (har birining bahosi):
- Asosiy DB, alohida jadval — oddiy, tranzaksion; katta hajmda og'ir
- Alohida audit DB — asosiy DB'ni bloklmaydi; boshqaruv qo'shimcha
- Elasticsearch / OpenSearch — kuchli qidiruv/agregatsiya; tranzaksiya yo'q
- Fayl (append-only, WORM) — arzon, o'zgarmas; qidirish qiyin
- Bulut (S3 + Glacier) — arxiv uchun eng arzon; tez qidiruv yo'q
Tanlov: hajm, qidiruv talabi, compliance, harajat bo'yichaSaqlash joyi tanlovi: audit qayerda yashaydi — muvozanat masalasi. Asosiy DB, alohida jadval — oddiy, tranzaksion (asosiy amal bilan bitta tranzaksiyada — atomik), lekin hajm o'ssa asosiy DB'ni og'irlashtiradi. Alohida audit DB — asosiy bazani bloklmaydi 2.8-bob. Elasticsearch/OpenSearch — kuchli matnli qidiruv va agregatsiya (dashboard, tahlil), lekin tranzaksiya kafolati yo'q (async yoziladi). Fayl (append-only/WORM) — arzon, tabiiy o'zgarmas, lekin qidirish qiyin. Bulut (S3 + Glacier) — arxiv 2.8-bob uchun eng arzon. Ko'p tizim aralash ishlatadi: joriy yozuvlar DB'da (tez qidiruv), eski yozuvlar S3'da (arzon arxiv).
2.14. Retention siyosati va GDPR (qancha saqlash)
QANCHA SAQLASH — ikki qarama-qarshi talab:
- Compliance: MINIMUM muddat saqlash (bank 5-7 yil, soliq talabi)
- GDPR: ortiqcha shaxsiy ma'lumot saqlamaslik (data minimization)
YECHIM:
- Retention muddati (masalan 2 yil "issiq", keyin arxiv/o'chirish)
- Shaxsiy ma'lumotni MINIMAL (userId — havola; ism/email emas)
- "Unutilish huquqi": foydalanuvchi o'chsa — audit'da userId
anonimlashtiriladi/pseudonim (yozuv qoladi, shaxs bog'lanmaydi)
Audit = dalil (saqlash kerak) VS GDPR (shaxsiy — o'chirish) muvozanatiRetention va GDPR: "qancha saqlash?" — ikki qarama-qarshi talab kesishadi. Compliance minimum muddat talab qiladi (bank/soliq — 5-7 yil). GDPR esa ortiqcha shaxsiy ma'lumot saqlashni taqiqlaydi (data minimization). Yechim: aniq retention siyosati (masalan 2 yil "issiq" DB, keyin arxiv, compliance muddati tugagach o'chirish — 2.8). Audit yozuvida shaxsiy ma'lumotni minimal saqlash —
userIdhavolasi yetarli (ism/email'ni takrorlamaslik). "Unutilish huquqi": foydalanuvchi o'chirilsa, audit yozuvini o'chirib bo'lmaydi (dalil), lekinuserIdanonimlashtiriladi (pseudonim) — yozuv "kim nima qildi" faktini saqlaydi, ammo shaxsga bog'lanmaydi. Bu — dalil saqlash va shaxsiy huquq muvozanati.
2.15. Soft-delete bilan bog'liqlik
SOFT-DELETE (deletedAt — yozuv o'chmaydi, belgilanadi):
- "o'chirish" aslida UPDATE (deletedAt = now) — subscriber ko'radi
- Audit: action = "user.delete" (hodisa), lekin ma'lumot qoladi
- Restore (tiklash) ham audit'ga yoziladi
- Audit + soft-delete = to'liq tarix (o'chirilgan ham, tiklangan ham)
Audit LOG o'zi soft-delete EMAS — hech qachon o'chmaydi (2.6)Soft-delete bilan bog'liqlik: ko'p tizim
deletedAtbilan soft-delete ishlatadi (yozuv fizik o'chmaydi, belgilanadi). Bu audit bilan yaxshi bog'lanadi: soft-delete aslida UPDATE (deletedAt = now), subscriber 2.4-bob buni ko'radi vaaction: "user.delete"hodisasini yozadi — ma'lumotning o'zi ham qoladi (to'liq tergov mumkin). Tiklash (restore) ham audit'ga tushadi. Muhim farq: audit log o'zi hech qachon soft-delete emas — u umuman o'chmaydi (immutable — 2.6). Soft-delete — biznes ma'lumot uchun; audit — abadiy dalil.
2.16. Best practices (audit log)
Audit log ≠ oddiy log (biznes hodisa, doimiy — 2.1)
Muhim harakatlar (CRUD, auth, moliyaviy, ruxsat — 2.2)
To'liq kontekst (kim/nima/qachon/qayerdan/eski-yangi — 2.3)
Avtomatik (interceptor/subscriber — qo'lga ishonma — 2.4)
Diff (eskiyangi — o'zgargan maydonlar — 2.5)
O'zgarmas (immutable — faqat insert — 2.6)
Qidiriladigan (indeks, admin panel — 2.7)
Hajm boshqaruvi (partitioning, arxiv, muddat — 2.8)
Sirlarni yozma; tenant; o'zi himoyalangan (14, 8.25 — 2.9)
Aktyorni request context'dan (cls/AsyncLocalStorage — 2.10)
Async (event/queue — asosiy oqim bloklanmasin — 2.11)
Tamper-evident (hash zanjiri — buzilishni aniqlash — 2.12)
Saqlash joyi ongli (DB/ES/arxiv — hajm/qidiruv — 2.13)
Retention + GDPR (muddat, anonimlashtirish — 2.14)3. Sintaksis — tez ma'lumotnoma
// Audit yozuvi 2.3-bob: { userId, action, resource, resourceId, oldValue, newValue, ip, tenantId, createdAt }
// Avtomatik 2.4-bob: @Audit({ action }) + AuditInterceptor (tap)
// Diff 2.5-bob: eski vs yangi (o'zgargan maydonlar)
// O'zgarmas 2.6-bob: faqat INSERT (UPDATE/DELETE yo'q)
// Qidirish 2.7-bob: filter (userId, action, resource, sana oralig'i)4. Batafsil kod namunalari
Misol 1 — Audit entity (o'zgarmas — 2.3, 2.6)
@Entity("audit_logs")
@Index(["userId", "createdAt"]) // tez qidiruv (2.7, 6.10)
@Index(["resource", "resourceId"])
export class AuditLog {
@PrimaryGeneratedColumn("uuid") id: string;
@Column({ nullable: true }) userId: string; // KIM
@Column() action: string; // NIMA ("order.update")
@Column() resource: string; // qaysi obyekt
@Column({ nullable: true }) resourceId: string; // qaysi yozuv
@Column("jsonb", { nullable: true }) oldValue: any; // ESKI (2.5)
@Column("jsonb", { nullable: true }) newValue: any; // YANGI
@Column({ nullable: true }) ip: string; // QAYERDAN
@Column({ nullable: true }) userAgent: string;
@Column({ nullable: true }) tenantId: string; // SaaS (8.25)
@CreateDateColumn() createdAt: Date; // QACHON
// updatedAt YO'Q (o'zgarmas — 2.6)
}Misol 2 — Audit service (faqat insert — 2.6)
@Injectable()
export class AuditService {
constructor(@InjectRepository(AuditLog) private repo: Repository<AuditLog>) {}
// FAQAT yozish (o'qish va yozish — UPDATE/DELETE metod YO'Q — immutable 2.6)
async yoz(data: Partial<AuditLog>): Promise<void> {
await this.repo.insert({ // insert (save emas — o'zgartirmaslik)
...data,
oldValue: this.sirlarTozala(data.oldValue), // sirlarni tozalash (2.9, 14)
newValue: this.sirlarTozala(data.newValue),
});
}
private sirlarTozala(obj: any) {
if (!obj) return obj;
const { parol, token, refreshToken, ...toza } = obj; // sirlarni olib tashlash (14)
return toza;
}
async qidir(filter: AuditFilterDto) { /* Misol 5 */ }
}Misol 3 — Avtomatik audit interceptor (2.4)
// @Audit dekorator (metadata)
export const Audit = (action: string, resource: string) =>
SetMetadata("audit", { action, resource });
@Injectable()
export class AuditInterceptor implements NestInterceptor {
constructor(private auditService: AuditService, private reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const meta = this.reflector.get<{ action: string; resource: string }>("audit", context.getHandler());
if (!meta) return next.handle();
const req = context.switchToHttp().getRequest();
return next.handle().pipe(
tap((natija) => { // muvaffaqiyatdan keyin (8.6)
this.auditService.yoz({
userId: req.user?.id,
action: meta.action, resource: meta.resource,
resourceId: req.params?.id || natija?.id,
newValue: natija,
ip: req.ip, userAgent: req.headers["user-agent"],
tenantId: req.tenantId, // (8.25)
});
}),
);
}
}
// Ishlatish
@Audit("user.delete", "User")
@Delete(":id")
@UseGuards(JwtAuthGuard, RolesGuard) @Roles(Rol.ADMIN) // (8.7)
ochir(@Param("id") id: string) { return this.service.ochir(id); }Misol 4 — TypeORM subscriber (DB avtomatik audit — 2.4)
// DB o'zgarishlarini avtomatik audit (subscriber — 8.13, 8.25)
@EventSubscriber()
export class AuditSubscriber implements EntitySubscriberInterface {
constructor(dataSource: DataSource, private context: RequestContext) {
dataSource.subscribers.push(this);
}
afterUpdate(event: UpdateEvent<any>) {
if (this.auditlanadimi(event.metadata.tableName)) {
this.audit({
action: `${event.metadata.tableName}.update`,
resourceId: event.entity?.id,
oldValue: event.databaseEntity, // ESKI (2.5)
newValue: event.entity, // YANGI
userId: this.context.userId,
});
}
}
afterInsert(event: InsertEvent<any>) { /* yaratish */ }
afterRemove(event: RemoveEvent<any>) { /* o'chirish */ }
private auditlanadimi(table: string) {
return ["orders", "users", "payments"].includes(table); // muhim jadvallar (2.2)
}
}Misol 5 — Audit qidirish (admin panel — 2.7)
@Controller("admin/audit")
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Rol.ADMIN) // faqat admin (8.7, 2.9)
export class AuditController {
constructor(private auditService: AuditService) {}
@Get()
async qidir(@Query() filter: AuditFilterDto, @Req() req) {
// Audit ko'rilishi ham loglanadi (2.9)
await this.auditService.yoz({ userId: req.user.id, action: "audit.view", resource: "AuditLog" });
return this.auditService.qidir(filter);
}
@Get("resource/:resource/:id") // bir obyekt tarixi
tarix(@Param("resource") resource: string, @Param("id") id: string) {
return this.auditService.obyektTarixi(resource, id); // 5-mijoz barcha o'zgarishlari
}
}
class AuditFilterDto {
@IsOptional() userId?: string;
@IsOptional() action?: string;
@IsOptional() resource?: string;
@IsOptional() @IsDateString() from?: string;
@IsOptional() @IsDateString() to?: string;
@Type(() => Number) @IsOptional() limit?: number = 50;
}Misol 6 — Auth hodisalari audit (2.2, 14)
// Auth — muhim audit (login, parol, muvaffaqiyatsiz kirish — buzilish belgisi)
@Injectable()
export class AuthService {
async login(email: string, parol: string, ip: string, userAgent: string) {
const user = await this.validateUser(email, parol); // (8.9)
if (!user) {
await this.auditService.yoz({ // MUVAFFAQIYATSIZ kirish (14 — buzilish?)
action: "auth.login.failed", resource: "Auth",
newValue: { email }, ip, userAgent, // parol YOQ (14)
});
throw new UnauthorizedException("Email yoki parol noto'g'ri");
}
await this.auditService.yoz({
userId: user.id, action: "auth.login.success", resource: "Auth", ip, userAgent,
});
return this.tokenlarBer(user);
}
async parolOzgartir(userId: string, ip: string) {
// parol qiymati YOZILMAYDI (14) — faqat hodisa
await this.auditService.yoz({ userId, action: "auth.password.changed", resource: "Auth", ip });
}
}Misol 7 — Obyekt tarixi (versiyalash — 2.5)
// Bir obyektning barcha o'zgarishlari (tarix — kim, qachon, nima)
async obyektTarixi(resource: string, resourceId: string) {
const yozuvlar = await this.repo.find({
where: { resource, resourceId },
order: { createdAt: "ASC" }, // vaqt tartibida (eskiyangi)
});
return yozuvlar.map((y) => ({
sana: y.createdAt,
kim: y.userId,
amal: y.action,
ozgarish: this.ozgarishlar(y.oldValue, y.newValue), // nima o'zgardi (diff — 2.5)
}));
}
private ozgarishlar(eski: any, yangi: any) {
if (!eski || !yangi) return null;
const farqlar: any = {};
for (const k of Object.keys(yangi)) {
if (eski[k] !== yangi[k]) farqlar[k] = { eski: eski[k], yangi: yangi[k] }; // "narx: 50000 30000"
}
return farqlar;
}Misol 8 — O'zgarmaslik himoyasi (immutable — 2.6)
-- DB darajasida — audit_logs faqat INSERT (UPDATE/DELETE bloklangan)
REVOKE UPDATE, DELETE ON audit_logs FROM app_user; -- app faqat insert/select
GRANT INSERT, SELECT ON audit_logs TO app_user;
-- Yoki trigger (UPDATE/DELETE'ni bloklash)
CREATE OR REPLACE FUNCTION audit_immutable() RETURNS trigger AS $$
BEGIN RAISE EXCEPTION 'Audit log o''zgartirilmaydi'; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_update BEFORE UPDATE OR DELETE ON audit_logs
FOR EACH ROW EXECUTE FUNCTION audit_immutable();Misol 9 — Hajm boshqaruvi (partitioning + arxiv — 2.8)
// Eski audit yozuvlarni arxivlash (cron — 8.18)
@Injectable()
export class AuditArchiveService {
@Cron(CronExpression.EVERY_WEEK)
async arxivla() {
// 1 yildan eski yozuvlar S3 (arzon saqlash — compliance)
const eski = await this.repo.find({
where: { createdAt: LessThan(new Date(Date.now() - 365 * 24 * 3600 * 1000)) },
});
if (eski.length) {
await this.s3.yukla(Buffer.from(JSON.stringify(eski)), `audit-archive/${Date.now()}.json`);
// Compliance muddatigacha saqlash (bank 5 yil) — keyin o'chirish (2.8)
}
}
}
// Partitioning (PostgreSQL — sana bo'yicha) — migration'da (6.10, 6.14)Misol 10 — To'liq audit modul
src/audit/
├── audit.entity.ts (o'zgarmas — Misol 1)
├── audit.service.ts (faqat insert — Misol 2)
├── audit.interceptor.ts (avtomatik — Misol 3)
├── audit.subscriber.ts (DB avtomatik — Misol 4)
├── audit.controller.ts (admin qidirish — Misol 5)
├── audit.decorator.ts (@Audit)
└── audit-archive.service.ts (hajm — Misol 9)
Oqim:
- @Audit endpoint AuditInterceptor yozuv (avtomatik)
- DB o'zgarish AuditSubscriber yozuv (avtomatik)
- Auth AuthService audit (login/parol — Misol 6)
- Admin qidirish/tarix (Misol 5, 7)
- Cron arxivlash (Misol 9)
O'zgarmas (DB himoya — Misol 8); sirlar tozalangan (14)Misol 11 — Request context bilan aktyorni olish (nestjs-cls — 2.10)
// Middleware — so'rov boshida userId/tenantId/ip'ni CLS store'ga qo'yadi
@Injectable()
export class AuditContextMiddleware implements NestMiddleware {
constructor(private cls: ClsService) {}
use(req: any, _res: any, next: () => void) {
this.cls.set("userId", req.user?.id); // KIM (2.10)
this.cls.set("tenantId", req.tenantId); // qaysi tenant (8.25)
this.cls.set("ip", req.ip); // QAYERDAN
this.cls.set("userAgent", req.headers["user-agent"]);
next();
}
}
// Subscriber (DB darajasida — req.user YO'Q) endi CLS'dan oladi
@EventSubscriber()
export class AuditSubscriber implements EntitySubscriberInterface {
constructor(dataSource: DataSource, private cls: ClsService) {
dataSource.subscribers.push(this);
}
afterUpdate(event: UpdateEvent<any>) {
if (!this.auditlanadimi(event.metadata.tableName)) return;
this.auditService.yoz({
userId: this.cls.get("userId"), // chuqurlikda kontekst (2.10)
tenantId: this.cls.get("tenantId"),
ip: this.cls.get("ip"),
action: `${event.metadata.tableName}.update`,
resource: event.metadata.tableName,
resourceId: event.entity?.id,
oldValue: event.databaseEntity, newValue: event.entity,
});
}
private auditlanadimi(t: string) { return ["orders", "users"].includes(t); }
}Misol 12 — Async audit (event-emitter — asosiy oqimni bloklamaslik — 2.11)
// Interceptor: audit'ni EMIT qiladi (kutmaydi — asosiy javob tez)
return next.handle().pipe(
tap((natija) => {
this.eventEmitter.emit("audit.event", { // fon vazifasi (2.11)
userId: req.user?.id, action: meta.action, resource: meta.resource,
resourceId: req.params?.id ?? natija?.id, newValue: natija,
ip: req.ip, userAgent: req.headers["user-agent"], tenantId: req.tenantId,
});
}),
);
// Listener — alohida yozadi (asosiy so'rov allaqachon javob qaytargan)
@Injectable()
export class AuditListener {
constructor(private auditService: AuditService) {}
@OnEvent("audit.event", { async: true }) // async — bloklamaydi (2.11)
async yoz(data: Partial<AuditLog>) {
await this.auditService.yoz(data); // xato bo'lsa — asosiy oqimga ta'sir yo'q
}
}
// Muhim/compliance audit uchun — BullMQ queue (ishonchli — yo'qolmaydi):
// this.auditQueue.add("write", data) worker DB'ga yozadi (Redis, retry — 8.18)Misol 13 — Tamper-evident hash zanjiri (buzilishni aniqlash — 2.12)
// Har yozuvga oldingi yozuv hash'iga bog'langan hash qo'shiladi
async yoz(data: Partial<AuditLog>): Promise<void> {
const oxirgi = await this.repo.findOne({ // zanjirdagi oxirgi yozuv
where: {}, order: { createdAt: "DESC" },
});
const prevHash = oxirgi?.hash ?? "GENESIS"; // birinchi yozuv — boshlang'ich
const toza = { ...data, oldValue: this.sirlarTozala(data.oldValue),
newValue: this.sirlarTozala(data.newValue) };
const hash = createHash("sha256") // SHA256(oldingi + shu yozuv)
.update(prevHash + JSON.stringify(toza))
.digest("hex");
await this.repo.insert({ ...toza, prevHash, hash }); // zanjir bog'lanadi (2.12)
}
// Davriy tekshiruv — zanjir buzilganini aniqlash
async zanjirTekshir(): Promise<{ butun: boolean; buzilgan?: string }> {
const barcha = await this.repo.find({ order: { createdAt: "ASC" } });
let prev = "GENESIS";
for (const y of barcha) {
const { id, hash, prevHash, createdAt, ...maydonlar } = y;
const kutilgan = createHash("sha256").update(prev + JSON.stringify(maydonlar)).digest("hex");
if (y.hash !== kutilgan || y.prevHash !== prev) {
return { butun: false, buzilgan: y.id }; // shu yozuv o'zgartirilgan!
}
prev = y.hash;
}
return { butun: true }; // zanjir buzilmagan (dalil toza)
}Misol 14 — Chuqur diff va sir yashirish (deep diff + redaction — 2.5, 2.9)
// Chuqur (ichma-ich) diff — faqat haqiqatan o'zgargan maydonlar
function chuqurDiff(eski: any, yangi: any): Record<string, { eski: any; yangi: any }> {
const farq: any = {};
const kalitlar = new Set([...Object.keys(eski ?? {}), ...Object.keys(yangi ?? {})]);
for (const k of kalitlar) {
const e = eski?.[k], y = yangi?.[k];
if (JSON.stringify(e) !== JSON.stringify(y)) { // qiymat farq qilsa (obyekt ham)
farq[k] = { eski: e, yangi: y }; // "narx: 50000 30000"
}
}
return farq;
}
// Maxfiy maydonlarni yashirish (rekursiv — ichki obyektlar ham — 2.9, 14)
const SIRLAR = ["parol", "password", "token", "refreshToken", "secret", "cardNumber"];
function sirYashir(obj: any): any {
if (Array.isArray(obj)) return obj.map(sirYashir);
if (obj && typeof obj === "object") {
return Object.fromEntries(Object.entries(obj).map(([k, v]) =>
SIRLAR.includes(k) ? [k, "***REDACTED***"] : [k, sirYashir(v)], // yashirilgan (14)
));
}
return obj;
}
// Diff'da ham, saqlashda ham sir chiqmasligi kerak (hatto ichki obyektda)5. To'g'ri va noto'g'ri holatlar
1) Audit qo'lda (har joyda)
qo'lda yozish (unutiladi — 2.4)
avtomatik (interceptor/subscriber)2) Audit o'zgartiriladigan
UPDATE/DELETE mumkin (dalil emas — 2.6)
immutable (faqat insert)3) Sirlarni audit'ga yozish
parol/token qiymati (14, 2.9)
tozalash (faqat hodisa)4) Diff yo'q (faqat "o'zgartirildi")
nima o'zgargani noma'lum (2.5)
eskiyangi (diff)5) Audit ham oddiy log (vaqtincha)
rotatsiya/o'chish (dalil yo'qoladi — 2.1)
doimiy DB (compliance muddatigacha)6) Audit asosiy so'rovni kutadi
sinxron insert (foydalanuvchi sekinlashuvni sezadi — 2.11)
async (event/queue — fon vazifasi)7) Subscriber'da aktyor "system" bo'lib qoladi
req.user yo'q (DB darajasi) userId null (kim noma'lum — 2.10)
request context (cls/AsyncLocalStorage) — userId topiladi8) GDPR: foydalanuvchi o'chsa audit ham o'chiriladi
audit yozuvini o'chirish (dalil yo'qoladi — 2.6)
userId anonimlashtiriladi (yozuv qoladi, shaxs emas — 2.14)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Audit unutiladi (qo'lda)
Sababi: har endpoint qo'lda 2.4-bob. Yechimi: interceptor/subscriber (avtomatik).
Xato 2 — Audit DB sekinlashtiradi
Sababi: asosiy DB, ko'p insert 2.8-bob. Yechimi: alohida jadval/DB; async (navbat).
Xato 3 — Qidiruv sekin
Sababi: indeks yo'q 2.7-bob. Yechimi: indeks (userId/action/createdAt).
Xato 4 — Sirlar audit'da
Sababi: butun obyekt yozilgan 2.9-bob. Yechimi: tozalash (Misol 2).
Xato 5 — Hajm o'sib ketdi
Sababi: arxiv/partition yo'q 2.8-bob. Yechimi: partitioning, arxiv (Misol 9).
Xato 6 — Audit o'zgartirildi (dalil emas)
Sababi: immutable emas 2.6-bob. Yechimi: DB himoya (Misol 8).
Xato 7 — Subscriber'da "kim" yo'q
Sababi: DB darajasida req.user mavjud emas 2.10-bob. Yechimi: request context (nestjs-cls / AsyncLocalStorage — Misol 11).
Xato 8 — Sir ichki obyektda qoldi
Sababi: faqat yuza maydonlar tozalangan, ichki obyektdagi token chiqib ketdi 2.9-bob. Yechimi: rekursiv sir yashirish (Misol 14).
Xato 9 — DB'ga to'g'ridan kirilib audit o'zgartirildi
Sababi: app immutable, lekin DB admin'i o'zgartira oladi 2.12-bob. Yechimi: hash zanjiri — buzilish aniqlanadi (Misol 13).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Log 5.12-bob: oddiy log'dan farq.
- Interceptor/Subscriber (8.6, 8.13): avtomatik audit.
- Auth 8.9-bob: login/parol audit.
- RBAC 8.7-bob: ruxsat o'zgarishi; audit ko'rish.
- Multi-tenant 8.25-bob: tenantId.
- Payment 8.19-bob: moliyaviy audit.
- Indeks/partition 6.10-bob: hajm.
- Xavfsizlik (14): tergov, sirlar.
8. Eng yaxshi amaliyotlar (best practices)
- Audit ≠ oddiy log (biznes, doimiy — 2.1).
- Muhim harakatlar (CRUD, auth, moliyaviy, ruxsat — 2.2).
- To'liq kontekst (kim/nima/qachon/qayerdan/diff — 2.3).
- Avtomatik (interceptor/subscriber — 2.4).
- Diff (eskiyangi — 2.5).
- O'zgarmas (immutable — faqat insert — 2.6).
- Qidiriladigan (indeks, admin panel — 2.7).
- Hajm boshqaruvi (partition, arxiv, muddat — 2.8).
- Sirlarni yozma; tenant; o'zi himoyalangan (14, 8.25 — 2.9).
- Async (asosiy oqimni bloklamasin — navbat — 2.8).
9. Amaliy loyiha: "Audit Log Tizimi"
Audit log'ni amalda mustahkamlash.
Maqsad
To'liq audit tizimi: avtomatik audit, diff, immutable, admin qidirish, arxiv.
Talablar (requirements)
- Audit entity: to'liq kontekst, indeks, immutable (Misol 1, 2.3, 2.6).
- Audit service: faqat insert + sir tozalash (Misol 2, 2.9).
- Interceptor: @Audit avtomatik (Misol 3, 2.4).
- Subscriber: DB avtomatik (Misol 4, 2.4).
- Diff: eskiyangi (Misol 5, 7, 2.5).
- Auth audit: login/parol/muvaffaqiyatsiz (Misol 6, 2.2).
- Admin qidirish: filtr + obyekt tarixi (Misol 5, 7, 2.7).
- Immutable: DB himoya (Misol 8, 2.6).
- Arxiv: hajm (Misol 9, 2.8).
- Xavfsizlik: sirlar, tenant, faqat admin 2.9-bob.
Maslahatlar (hint)
- Avtomatik (2.4, 1-xato).
- Immutable (2.6, 2-holat, 6-xato).
- Sir tozalash (2.9, 3-holat).
- Diff (2.5, 4-holat).
- Indeks (2.7, 3-xato).
- Arxiv (2.8, 5-xato).
"Tayyor" mezonlari (acceptance criteria)
- Audit entity (immutable).
- Service (insert + sir).
- Interceptor.
- Subscriber.
- Diff.
- Auth audit.
- Admin qidirish.
- Immutable (DB).
- Arxiv.
- Xavfsizlik.
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda audit log va faoliyat tarixini o'rgandik:
- Audit vs oddiy log 2.1-bob; nima loglash 2.2-bob; yozuv tuzilishi (kim/nima/qachon — 2.3).
- Avtomatik (interceptor/subscriber — 2.4); diff (eskiyangi — 2.5); o'zgarmaslik (immutable — dalil — 2.6).
- Qidirish (admin panel — 2.7); hajm (partition, arxiv — 2.8); xavfsizlik (sirlar, tenant, compliance — 2.9).
Keyingi bob — 8.27: API versioning. Audit'ni bildik; endi yana bir real mavzu — API versioning (v1/v2 — eski mijozni buzmasdan API yangilash) — ni o'rganamiz. Mobil ilova/integratsiya bo'lsa, API o'zgarishi eski versiyalarni buzmasligi kerak.
Foydalanilgan rasmiy/ishonchli manbalar
- OWASP — Logging va Monitoring bo'yicha qo'llanma (audit jurnali, o'zgarmaslik, sirlarni loglamaslik).
- NestJS rasmiy hujjati — Interceptors (audit interceptor), Middleware, Execution Context.
- TypeORM rasmiy hujjati — Entity Subscribers va Listeners (DB darajasidagi avtomatik audit).
- Node.js rasmiy hujjati —
AsyncLocalStorage(async oqim bo'ylab so'rov konteksti). - PostgreSQL rasmiy hujjati — Table Partitioning, Triggers, GRANT/REVOKE (immutable himoya).
- NIST SP 800-92 — Guide to Computer Security Log Management (audit trail, saqlash).
- GDPR (Umumiy ma'lumot himoyasi reglamenti) — data minimization, "unutilish huquqi", retention.
- Moliyaviy compliance standartlari (masalan PCI DSS, SOX) — audit trail talablari va saqlash muddati.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!