WisarWisar
Dasturlash kitobi/8-QISM — NestJS27 daqiqa

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 logbiznes 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

text
  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

text
  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

text
  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)

typescript
// 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)

typescript
// 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)

text
   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)

typescript
// 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)

text
  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 kerak

Saqlash/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)

text
   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)

text
  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.user yo'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'ning AsyncLocalStorage (async oqim bo'ylab kontekst saqlaydi) yoki uning NestJS o'ramasi nestjs-cls (CLS). Middleware so'rov boshida userId/tenantId/ip'ni "store"ga qo'yadi; subscriber yoki service ixtiyoriy chuqurlikda cls.get("userId") orqali oladi. Bu — subscriber-audit'ni real ishlatishning kaliti (Misol 4'dagi RequestContext — aynan shu).

2.11. Async audit (asosiy oqimni bloklamaslik — event / queue)

text
  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 uchun

Async 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'da tap ichida emit — asosiy javob kechikmaydi.

2.12. Tamper-evident — hash zanjiri (buzilishni aniqlash)

text
  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)

text
  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'yicha

Saqlash 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)

text
  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) muvozanati

Retention 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 — userId havolasi yetarli (ism/email'ni takrorlamaslik). "Unutilish huquqi": foydalanuvchi o'chirilsa, audit yozuvini o'chirib bo'lmaydi (dalil), lekin userId anonimlashtiriladi (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

text
  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 deletedAt bilan 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 va action: "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)

text
   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

typescript
// 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)

typescript
@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)

typescript
@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)

typescript
// @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)

typescript
// 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)

typescript
@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)

typescript
// 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)

typescript
// 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)

sql
-- 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)

typescript
// 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

text
  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)

typescript
// 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)

typescript
// 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)

typescript
// 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)

typescript
// 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)

text
 qo'lda yozish (unutiladi — 2.4)
 avtomatik (interceptor/subscriber)

2) Audit o'zgartiriladigan

text
 UPDATE/DELETE mumkin (dalil emas — 2.6)
 immutable (faqat insert)

3) Sirlarni audit'ga yozish

text
 parol/token qiymati (14, 2.9)
 tozalash (faqat hodisa)

4) Diff yo'q (faqat "o'zgartirildi")

text
 nima o'zgargani noma'lum (2.5)
 eskiyangi (diff)

5) Audit ham oddiy log (vaqtincha)

text
 rotatsiya/o'chish (dalil yo'qoladi — 2.1)
 doimiy DB (compliance muddatigacha)

6) Audit asosiy so'rovni kutadi

text
 sinxron insert (foydalanuvchi sekinlashuvni sezadi — 2.11)
 async (event/queue — fon vazifasi)

7) Subscriber'da aktyor "system" bo'lib qoladi

text
 req.user yo'q (DB darajasi)  userId null (kim noma'lum — 2.10)
 request context (cls/AsyncLocalStorage) — userId topiladi

8) GDPR: foydalanuvchi o'chsa audit ham o'chiriladi

text
 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)

  1. Audit entity: to'liq kontekst, indeks, immutable (Misol 1, 2.3, 2.6).
  2. Audit service: faqat insert + sir tozalash (Misol 2, 2.9).
  3. Interceptor: @Audit avtomatik (Misol 3, 2.4).
  4. Subscriber: DB avtomatik (Misol 4, 2.4).
  5. Diff: eskiyangi (Misol 5, 7, 2.5).
  6. Auth audit: login/parol/muvaffaqiyatsiz (Misol 6, 2.2).
  7. Admin qidirish: filtr + obyekt tarixi (Misol 5, 7, 2.7).
  8. Immutable: DB himoya (Misol 8, 2.6).
  9. Arxiv: hajm (Misol 9, 2.8).
  10. 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!
8.26-bob: Audit log va faoliyat tarixi — Wisar