Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. REQUEST scope'ning narxi
- 2.1 Tajriba
- 2.2 Farqni o'qiymiz
- 3. nestjs-cls asoslari
- 3.1 Modulni ulash
- 3.2 ClsService metodlari
- 3.3 So'rovdan tashqarida
- 3.4 DI'siz kirish: ClsServiceManager
- 4. Kontekst qayerda ochilsin?
- 4.1 Tartib muammosi
- 4.2 Kanon yechimi
- 5. Express'da qanday edi, Nest'da qanday
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): metodni toping
- 2-mashq (o'rta): ClsService modeli
- 3-mashq (qiyin): scope ko'piklanishini hisoblash
- 8. Loyiha qadami: nestjs-cls va commit
- 8.1 Paket
- 8.2 log.ts — o'z AsyncLocalStorage imiz ketadi
- 8.3 app.module.ts — yakuniy ko'rinish
- 8.4 ilova.ts — yakuniy ko'rinish
- 8.5 Test timeout'lari
- 8.6 Natija: xizmat logi so'rov ID si bilan
- 8.7 Tekshiruv va commit
- 9. Real ishda
- Xulosa
- Manbalar
NestJS so'rov konteksti: nestjs-cls, ClsService va AsyncLocalStorage
Qisqacha:
nestjs-cls—AsyncLocalStorageustidagi Nest moduli. So'rov boshida middleware kontekst ochadi: so'rov ID, joriy foydalanuvchi, so'rov logger'i. Keyin istalgan singleton xizmatClsServiceorqalicls.get("kalit")vacls.getId()bilan shu so'rovning qiymatini oladi —reqni uzatmasdan vaREQUESTscope'siz. Parallel so'rovlar aralashmaydi. So'rovdan tashqarida (cron, navbat) kontekstnicls.run(...)ochadi.
Bu darsda
REQUESTscope nega qimmat ekanini va CLS uni qanday almashtirishini haqiqiy hisob bilan ko'rasiz.ClsModule.forRootsozlamalarini (global,middleware,generateId,idGenerator,setup) ishlatasiz.ClsServiceningget,set,getId,isActive,runmetodlarini bilasiz.ClsMiddlewareni nega Nest marshrutlaridan oldin,app.usebilan ulash kerakligini tushuntirasiz.vazifalar-apidagi o'zAsyncLocalStorageimizninestjs-clsga almashtirib, config/log/kontekst qadamini commit qilasiz.
Oldin bilishingiz kerak: AsyncLocalStorage, Correlation ID va so'rov kuzatuvi, Provider scope'lari, NestJS Logger va nestjs-pino.
O'tgan darsda Nest'ning tizim loglari bizning pino orqali chiqadigan bo'ldi. Endi oxirgi qism: xizmat ichidagi log qaysi so'rovga tegishli ekanini qanday bilamiz.
1. Nega bu kerak?
25-qismda bir muammoni hal qilgandik. VazifalarService.yarat ichida "vazifa yaratildi" deb log yozamiz. Lekin bir vaqtda o'nta so'rov kelsa — qaysi qator qaysi so'rovniki? Javob — correlation ID: har log qatorida so'rovning sorovId si (Correlation ID).
Uni qanday yetkazamiz? req ni har funksiyaga parametr qilib uzatish — kodni iflos qiladi. 24-qismdagi AsyncLocalStorage bu muammoni hal qiladi: so'rov boshida kontekst.run(req.log, next), keyin istalgan chuqurlikda kontekst.getStore() (AsyncLocalStorage). vazifalar-api da bu 10 qatorlik o'z kodimiz edi.
Nest'da yana bir yo'l bor: REQUEST scope. Lekin u qimmat. Keyinroq, 28-qismdagi auth Nest'ga ko'chganda, kontekstga yana bir narsa qo'shiladi — so'rov egasi (Passport NestJS'da). Bunday muhim narsa uchun Nest ekotizimidagi tayyor, sinalgan vosita kerak.
O'xshatish: kasalxonadagi bilaguzuk. Bemorga kirishda bilaguzuk taqiladi: ism, raqam. Keyin har shifokor, har laborant tarixni so'ramasdan bilaguzukka qaraydi. Bemorni bo'limdan bo'limga olib borishda qog'oz uzatish shart emas. CLS — so'rovning bilaguzugi.
2. REQUEST scope'ning narxi
2.1 Tajriba
Provider scope'lari darsida Scope.REQUEST ni ko'rgandik: provider har so'rovda yangidan yaratiladi va @Inject(REQUEST) bilan so'rovni oladi. Ikki yondashuvni demo loyihada solishtirdik (biz ishga tushirdik: NestJS 12.2.0, nestjs-cls 7.0.1).
REQUEST scope bilan:
@Injectable({ scope: Scope.REQUEST })
class RSalom {
constructor(@Inject(REQUEST) private readonly req: Request) {
rYaratildi++;
}
salom(): string {
return `salom, ${String(this.req.headers["x-ism"])}`;
}
}CLS bilan — oddiy singleton:
@Injectable()
class SalomXizmati {
constructor(private readonly cls: ClsService) {
yaratildi++;
}
async salom(): Promise<string> {
await new Promise((r) => setTimeout(r, 20));
const ism = String(this.cls.get("ism"));
return `${this.cls.getId()} → salom, ${ism}`;
}
}Uchta parallel so'rov (x-request-id va x-ism sarlavhalari bilan), keyin nusxalar soni:
id-1 → salom, Ali
id-2 → salom, Malika
id-3 → salom, Jasur
SalomXizmati nusxalari: 1REQUEST scope'li variantda ketma-ket uch so'rov:
REQUEST scope: RSalom nusxalari 3 so'rovda: 32.2 Farqni o'qiymiz
- CLS: bitta nusxa, uch so'rov — har biri o'z qiymatini oldi.
setTimeout(20)ataylab qo'yilgan: uchala so'rov bir vaqtda "uxlab", keyin davom etdi. Shunda hamid-1Ali'ga,id-3Jasur'ga tegishli qoldi. Aralashmadi. - REQUEST scope: har so'rov — yangi nusxa. Uch so'rovda uchta. Ming so'rovda — ming nusxa va ming marta constructor.
Eng qimmati — ko'piklanish (scope bubbling): REQUEST scope'li provider'ni inject qilgan har provider ham REQUEST scope'li bo'lib qoladi. RSalom ni controller oldi — controller ham har so'rovda yangidan yaratiladi. Agar log xizmati REQUEST scope'li bo'lsa, unga tayanadigan hamma narsa — xizmatlar, omborlar, controller'lar — har so'rovda qayta quriladi. Bu Provider scope'lari darsidagi qoida.
CLS bu muammoni yo'qotadi: xizmatlar singleton qoladi, qiymat esa AsyncLocalStorage orqali so'rovga bog'lanadi.
Tekshirib ko'ring:
VazifalarService→VazifaOmbori→LogXizmati.LogXizmatiniScope.REQUESTqildingiz. 100 so'rovdaVazifalarServicenechta nusxada yaratiladi?
Javob
100 ta (va controller ham 100 ta). Scope pastdan yuqoriga ko'piklanadi: LogXizmati REQUEST bo'lsa, unga tayangan VazifaOmbori ham, unga tayangan VazifalarService ham REQUEST bo'ladi. CLS bilan esa hammasi — bittadan.
3. nestjs-cls asoslari
3.1 Modulni ulash
ClsModule.forRoot({
global: true,
middleware: {
mount: true,
generateId: true,
idGenerator: (req: Request) =>
String(req.headers["x-request-id"] ?? "yangi-id"),
setup: (cls, req: Request) => {
cls.set("ism", req.headers["x-ism"] ?? "mehmon");
},
},
}),| Sozlama | Nima qiladi |
|---|---|
global |
ClsService hamma modulda (import'siz) |
middleware.mount |
kontekstni ochadigan middleware'ni Nest o'zi ulasinmi |
generateId |
har kontekstga ID berish (cls.getId()) |
idGenerator |
ID qanday olinadi (sukut — tasodifiy) |
setup |
kontekst ochilganda birinchi qiymatlarni qo'yish |
mount: true — ClsMiddleware Nest middleware'i sifatida hamma marshrutga ulanadi. Middleware'dan tashqari guard: { mount: true } va interceptor: { mount: true } variantlari ham bor — WebSocket, mikroservis kabi HTTP'dan boshqa transportlar uchun. Ular hujjatda; biz HTTP'da middleware bilan sinadik.
3.2 ClsService metodlari
| Metod | Nima |
|---|---|
cls.set(kalit, qiymat) |
joriy kontekstga yozish |
cls.get(kalit) |
joriy kontekstdan o'qish |
cls.getId() |
kontekst ID si (generateId) |
cls.isActive() |
hozir kontekst ichidamizmi |
cls.run(fn) |
yangi kontekst ochib, fn ni ichida bajarish |
3.3 So'rovdan tashqarida
Xuddi shu demoda, ilova ichida, lekin so'rovdan tashqarida:
const cls = app.get(ClsService);
console.log("so'rovdan tashqarida isActive:", cls.isActive());
await cls.run(async () => {
cls.set("ism", "cron");
const ism = String(cls.get("ism"));
console.log("cls.run ichida:", cls.isActive(), ism);
});so'rovdan tashqarida isActive: false
cls.run ichida: true cronSo'rovdan tashqarida kontekst yo'q — isActive() yolg'on. Bu holat real: rejalashtirilgan vazifa, navbat ishchisi, ishga tushishdagi kod. U yerda cls.get(...) kutilmagan natija beradi. Yechim — cls.run: o'zingiz kontekst ochasiz. 24-qismdagi kontekst.run(qiymat, fn) ning aynan o'zi.
Diqqat:
cls.getni chaqirishdan oldinisActive()ni tekshiring yoki sukut qiymat bering. KanondagijoriyLogshunday qiladi: kontekst bo'lmasa — asosiy logger.
3.4 DI'siz kirish: ClsServiceManager
Ba'zan ClsService ni constructor'da olib bo'lmaydi — masalan, oddiy funksiya ichida. ClsServiceManager.getClsService() — statik kirish:
const cls = ClsServiceManager.getClsService();Kanon joriyLog funksiyasida shuni ishlatadi: u klass emas, 25-qismdan qolgan funksiya. Bu DI qoidasidan chekinish, lekin ongli: joriyLog o'nlab joyda chaqiriladi va uning imzosi o'zgarmasligi kerak edi.
4. Kontekst qayerda ochilsin?
4.1 Tartib muammosi
O'tgan darsda muhim saboqni oldik: Nest modul middleware'i statik fayl va yaroqsiz JSON so'rovlarigacha yetib bormaydi. CLS ga ham shu qoida tegadi. mount: true bilan kontekst Nest marshrutlariga yaqin ochiladi — app.use middleware'lari undan oldin ishlaydi va kontekstsiz qoladi.
vazifalar-api da app.use middleware'lari ko'p: sorovId, pino-http, helmet, cors, statik fayllar, express.json, tanaXatosi. Masalan, yaroqsiz JSON xatosi — tanaXatosi — Problem Details javobida sorovId ni yozadi va logga chiqaradi. Bunga kontekst kerak.
flowchart LR
A["sorovId()"] --> B["pino-http: req.log"]
B --> C["ClsMiddleware: log → CLS"]
C --> D["helmet, cors, statik"]
D --> E["express.json, tanaXatosi"]
E --> F["Nest: guard, pipe, controller, xizmat"]Tartibga qarang: ClsMiddleware pino-http dan keyin turadi — unga tayyor req.log kerak. Qolgan hamma narsa undan keyin, kontekst ichida.
4.2 Kanon yechimi
Kanon mount: false qo'yadi va middleware'ni o'zi, app.use bilan, kerakli joyga ulaydi:
app.use(sorovLogi(l));
app.use(
new ClsMiddleware({
generateId: true,
idGenerator: (req: express.Request) => req.sorovId,
setup: (cls, req: express.Request) => {
cls.set(CLS_LOG, req.log);
},
}).use,
);idGenerator— tasodifiy ID emas, 25-qismdagisorovId(uX-Request-Idsarlavhasidan yoki yangi UUID). Bitta so'rovning ID si hamma joyda bir xil.setup— kontekstga so'rov logger'ini qo'yadi. Bupino-httpyaratgan bola logger: undasorovIdallaqachon bog'langan..use— klass nusxasining metodi; Express middleware'i funksiya kutadi.
Maslahat: 28-qismdagi auth Nest'ga ko'chganda shu kontekstga so'rov egasi ham qo'shiladi: auth middleware'i egasining ID'sini
cls.set(CLS_EGASI, …)bilan yozadi, xizmatlar unicls.getbilan oladi (Passport NestJS'da). Ko'p ijarachili (multi-tenant) tizimlarda —tenantId: so'rovning sarlavhasi yoki subdomeni bo'yichasetupda qo'yiladi va ombor har so'rovni shu ijarachiga cheklaydi. Kalit qancha bo'lmasin, naqsh bitta.
5. Express'da qanday edi, Nest'da qanday
| 25–27 (o'z ALS) | Nest (nestjs-cls) |
|
|---|---|---|
| Kontekst ochish | kontekst.run(req.log, next) |
ClsMiddleware |
| O'qish | kontekst.getStore() |
cls.get("log") |
| Qiymatlar | bitta (logger) | istalgancha kalit |
| So'rov ID | o'zimiz | generateId + getId() |
| Tranzaksiya | — | @nestjs-cls/transactional |
Oxirgi qator — eng katta yutuq. nestjs-cls ning plagini tranzaksiyani kontekst orqali uzatadi: bir nechta xizmat chaqiruvi bitta tranzaksiyada, tranzaksiya obyektini parametr qilmasdan. Buni Tranzaksiyalar NestJS'da darsida o'lchab ko'ramiz.
Nima to'ladik: o'zimizning 10 qatorimiz o'rniga bitta prod bog'liqlik. Xulq o'zgarmadi — log.test.ts dagi correlation testlari (xizmat logi, parallel so'rovlar, fon eksport) bir harf o'zgartirilmasdan o'tdi (kanon tekshiruvi).
6. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
cls.get — undefined cron'da |
so'rovdan tashqarida kontekst yo'q | cls.run(...) yoki isActive() tekshiruvi |
400 javobida sorovId yo'q |
mount: true — kontekst kech ochilgan |
mount: false + app.use(new ClsMiddleware(...).use) |
req.log CLS da undefined |
ClsMiddleware pino-http dan oldin |
tartib: pino-http → ClsMiddleware |
| Controller har so'rovda yaratiladi | zanjirda Scope.REQUEST provider |
CLS bilan singleton'ga qaytaring |
Nest can't resolve dependencies … ClsService |
global yo'q, ClsModule import qilinmagan |
global: true |
7. Mashqlar
Mashqlar kurs/mashqlar/30/30-nestjs-cls/ da.
1-mashq (oson): metodni toping
To'ldiring:
- Kontekst ID si:
cls.() - Cron ichida kontekst ochish:
cls.(async () => { ... }) - Kanonda
ClsMiddlewareqaysi middleware'dan keyin ulanadi? - Uch so'rovda CLS'li singleton xizmat nechta nusxada?
Yechim
- getId —
generateId: truebo'lsa. - run — 24-qismdagi
AsyncLocalStorage.runkabi. - pino-http —
setupga tayyorreq.logkerak. - 1 — tajribadagi
SalomXizmati nusxalari: 1.
2-mashq (o'rta): ClsService modeli
Node'da (node:async_hooks) kichik Cls klassini yozing: run(fn) — yangi bo'sh Map bilan kontekst ochadi va ID beradi (#keyingi hisoblagichdan: k-1, k-2, …); set, get, getId, isActive. Sinov: uchta parallel "so'rov" — har biri set("ism", …), tasodifiy bo'lmagan kechikish (setTimeout), keyin get. Kontekstdan tashqarida isActive() ni chiqaring. Ishora: AsyncLocalStorage (AsyncLocalStorage), getStore() kontekstdan tashqarida undefined.
Yechim
import { AsyncLocalStorage } from "node:async_hooks";
class Cls {
#als = new AsyncLocalStorage();
#keyingi = 0;
run(fn) {
const store = new Map([["id", `k-${++this.#keyingi}`]]);
return this.#als.run(store, fn);
}
isActive() { return this.#als.getStore() !== undefined; }
getId() { return this.#als.getStore()?.get("id"); }
get(kalit) { return this.#als.getStore()?.get(kalit); }
set(kalit, qiymat) {
const store = this.#als.getStore();
if (store === undefined) throw new Error("kontekst yo'q");
store.set(kalit, qiymat);
}
}
const cls = new Cls();
const kut = (ms) => new Promise((r) => setTimeout(r, ms));
async function salom(ism, ms) {
cls.set("ism", ism);
await kut(ms);
return `${cls.getId()} → salom, ${cls.get("ism")}`;
}
const natija = await Promise.all([
cls.run(() => salom("Ali", 30)),
cls.run(() => salom("Malika", 10)),
cls.run(() => salom("Jasur", 20)),
]);
console.log(natija.join("\n"));
console.log("tashqarida:", cls.isActive(), cls.get("ism"));Konsolda:
k-1 → salom, Ali
k-2 → salom, Malika
k-3 → salom, Jasur
tashqarida: false undefinedKechikishlar ataylab har xil: Malika'ning "so'rovi" birinchi tugadi, Ali'niki oxirgi. Shunda ham har biri o'z ismini oldi — await dan keyin AsyncLocalStorage kontekstni tiklaydi. nestjs-cls ichida ham AsyncLocalStorage; u ustiga DI, middleware va plaginlarni qo'shadi.
3-mashq (qiyin): scope ko'piklanishini hisoblash
Provider'lar grafigi berilgan: { nom: [bog'liqliklar] } va REQUEST scope'li provider'lar to'plami. requestScope(graf, asl) funksiyasini yozing: qaysi provider'lar natijada REQUEST scope'li bo'lishini qaytarsin (bog'liqligi REQUEST bo'lsa — o'zi ham). Keyin nusxalar(graf, asl, sorovlar) — har provider nechta nusxada yaratilishini: REQUEST bo'lsa sorovlar ta, aks holda 1. Sinov: Controller → Xizmat → Ombor → Log va Xizmat → Soat; avval Log REQUEST, keyin hech biri, keyin Soat. Ishora: o'zgarish bo'lmaguncha takrorlanadigan sikl (while) va Set.
Yechim
const graf = {
Controller: ["Xizmat"],
Xizmat: ["Ombor", "Soat"],
Ombor: ["Log"],
Log: [],
Soat: [],
};
function requestScope(graf, asl) {
const req = new Set(asl);
let ozgardi = true;
while (ozgardi) {
ozgardi = false;
for (const [nom, deps] of Object.entries(graf)) {
if (!req.has(nom) && deps.some((d) => req.has(d))) {
req.add(nom);
ozgardi = true;
}
}
}
return req;
}
function nusxalar(graf, asl, sorovlar) {
const req = requestScope(graf, asl);
return Object.keys(graf)
.map((nom) => `${nom}: ${req.has(nom) ? sorovlar : 1}`)
.join(", ");
}
console.log(nusxalar(graf, ["Log"], 100));
console.log(nusxalar(graf, [], 100));
console.log(nusxalar(graf, ["Soat"], 100));Konsolda:
Controller: 100, Xizmat: 100, Ombor: 100, Log: 100, Soat: 1
Controller: 1, Xizmat: 1, Ombor: 1, Log: 1, Soat: 1
Controller: 100, Xizmat: 100, Ombor: 1, Log: 1, Soat: 100Birinchi qator — «REQUEST scope'ning narxi» bo'limidagi savolning javobi: bitta Log butun zanjirni 100 nusxaga aylantirdi, faqat unga tayanmagan Soat bitta qoldi. Uchinchi qator qiziqroq: Soat REQUEST bo'lsa, Ombor va Log zararsiz — ular Soat ga tayanmaydi. Ko'piklanish faqat yuqoriga, iste'molchilar tomon ketadi. Sikl o'zgarish bo'lmaguncha aylanadi: har aylanishda bir qavat yuqoriga ko'tariladi.
8. Loyiha qadami: nestjs-cls va commit
Config, log va so'rov konteksti qadamining oxirgi qismi. Bu darsda — kontekst, keyin butun qadam commit qilinadi.
8.1 Paket
npm install --save-exact nestjs-cls@7.0.18.2 log.ts — o'z AsyncLocalStorage imiz ketadi
-import { AsyncLocalStorage } from "node:async_hooks";
+import { ClsServiceManager } from "nestjs-cls";-const kontekst = new AsyncLocalStorage<Log>();
-
-export function joriyLog(asosiy: Log): Log {
- return kontekst.getStore() ?? asosiy;
-}
+/** CLS kalitlari (nestjs-cls): joriy so'rov logger'i */
+export const CLS_LOG = "log";
+
+/** Joriy so'rovning logger'i (sorovId bilan); so'rovdan tashqarida
+ * (bootstrap, cron) — asosiy logger */
+export function joriyLog(asosiy: Log): Log {
+ const cls = ClsServiceManager.getClsService();
+ const joriy = cls.isActive()
+ ? cls.get<Log | undefined>(CLS_LOG)
+ : undefined;
+ return joriy ?? asosiy;
+}sorovLogi endi bitta middleware qaytaradi — kontekstni u ochmaydi:
-export function sorovLogi(log: Log): RequestHandler[] {
- const http = pinoHttp({
+export function sorovLogi(log: Log): RequestHandler {
+ return pinoHttp({
logger: log,Funksiya oxiridagi massiv o'chadi:
- return [
- http,
- (req, _res, next) => {
- kontekst.run(req.log, next);
- },
- ];
}joriyLog imzosi o'zgarmadi — uni chaqiradigan o'nlab joy (xizmatlar, filtr, eksport) tegilmaydi. Yana ikki qatorda (customSuccessMessage, customErrorMessage) String(req.method) → req.method — kanondagi kichik tozalash, ma'nosi bir xil.
8.3 app.module.ts — yakuniy ko'rinish
ClsModule.forRoot({ global: true, middleware: { mount: false } }) qo'shiladi. Uch darsdagi o'zgarishlardan keyin fayl to'liq (kanon, telefon uchun formatlangan, izohlar qisqartirildi):
// app.module.ts — ildiz modul: ilova shu moduldan boshlab yig'iladi.
// forRoot(...) — dinamik modul: ombor tayyor berilsa (testlar) — shu,
// aks holda config bo'yicha OmborModule.forRootAsync() yaratadi.
// Config — BogliqliklarModule ichida; nestjs-pino (Nest logi — JSON,
// bizning pino orqali); nestjs-cls (so'rov konteksti)
import { Module } from "@nestjs/common";
import type {
DynamicModule, MiddlewareConsumer, NestModule,
} from "@nestjs/common";
import { APP_FILTER, APP_GUARD, APP_PIPE } from "@nestjs/core";
import { LoggerModule } from "nestjs-pino";
import { ClsModule } from "nestjs-cls";
import { BogliqliklarModule } from "./bogliqliklar.module.ts";
import type { TashqiBogliqliklar } from "./bogliqliklar.module.ts";
import { OmborModule } from "./ombor.module.ts";
import { VazifalarModule } from "./vazifalar/vazifalar.module.ts";
import { EksportModule } from "./eksport/eksport.module.ts";
import { SxemaPipe } from "./umumiy/sxema.pipe.ts";
import { JsonTanaGuard } from "./umumiy/json-tana.guard.ts";
import { ProblemDetailsFilter } from "./umumiy/problem.filter.ts";
import { eskirgan, ESKI_YOLLAR } from "./umumiy/eskirgan.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
import { LOG } from "./tokenlar.ts";
import type { Log } from "./umumiy/log.ts";
export type IlovaBogliqliklari = TashqiBogliqliklar & {
ombor?: VazifaOmbori | undefined;
};
@Module({})
export class AppModule implements NestModule {
static forRoot({ ombor, ...b }: IlovaBogliqliklari): DynamicModule {
return {
module: AppModule,
imports: [
BogliqliklarModule.forRoot(b),
// Nest tizim loglari (bootstrap, ExceptionHandler) — bizning
// pino orqali JSON. useExisting: pino-http'ni o'zimiz app.use
// bilan ulaganmiz (ilova.ts) — Nest marshrutlaridan OLDIN
LoggerModule.forRootAsync({
inject: [LOG],
useFactory: (log: Log) => ({
pinoHttp: { logger: log },
useExisting: true,
}),
}),
// so'rov konteksti (AsyncLocalStorage) — middleware'ni
// ilova.ts da app.use bilan erta ulaymiz (mount: false)
ClsModule.forRoot({
global: true,
middleware: { mount: false },
}),
ombor === undefined
? OmborModule.forRootAsync()
: OmborModule.forRoot(ombor),
VazifalarModule,
EksportModule,
],
// global enhancer'lar DI bilan: Standard Schema tekshiruvi,
// 415 guard'i, Problem Details filtri
providers: [
{ provide: APP_PIPE, useClass: SxemaPipe },
{ provide: APP_GUARD, useClass: JsonTanaGuard },
{ provide: APP_FILTER, useClass: ProblemDetailsFilter },
],
};
}
// eski versiyasiz yo'llar: Deprecation + Sunset sarlavhalari bilan
configure(consumer: MiddlewareConsumer): void {
consumer.apply(eskirgan(ESKI_YOLLAR)).forRoutes("*splat");
}
}8.4 ilova.ts — yakuniy ko'rinish
ClsMiddleware sorovLogi dan keyin qo'shiladi, import — CLS_LOG. Fayl to'liq:
// ilova.ts — Nest ilovasi (25-qism ilova.ts ning o'rni): Express
// middleware'lari → Nest marshrutlari → Problem Details filtri.
// listen() YO'Q — server ishga-tushir.ts da (testlar — supertest).
// Sozlamalar DI'dagi Config'dan (ConfigModule) — parametrlar faqat
// testlarda ustidan yozish uchun
import { NestFactory } from "@nestjs/core";
import { VERSION_NEUTRAL, VersioningType } from "@nestjs/common";
import type {
NestExpressApplication,
} from "@nestjs/platform-express";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { join } from "node:path";
import { Logger } from "nestjs-pino";
import { ClsMiddleware } from "nestjs-cls";
import { AppModule } from "./app.module.ts";
import { sorovId } from "./umumiy/sorov-id.ts";
import { XotiraIdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import { JSON_TURLARI } from "./umumiy/json-kerak.ts";
import { tanaXatosi } from "./umumiy/tana-xatosi.ts";
import { CLS_LOG, sorovLogi } from "./umumiy/log.ts";
import type { Log } from "./umumiy/log.ts";
import type { Xabarchi } from "./umumiy/webhook.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
import type { Config } from "./config.ts";
import { CONFIG, LOG } from "./tokenlar.ts";
export type Bogliqliklar = {
/** tayyor ombor (testlar); berilmasa — config bo'yicha
* OmborModule.forRootAsync() yaratadi va yopadi */
ombor?: VazifaOmbori;
/** tayyor config; berilmasa — process.env dan */
config?: Config;
/** quyidagilar berilsa — config'dagi qiymat o'rniga (testlar) */
corsOriginlar?: readonly string[];
idempotentlikOmbori?: IdempotentlikOmbori;
log?: Log;
xabarchi?: Xabarchi;
tz?: string;
trustProxy?: boolean | number | string;
hsts?: boolean;
};
export async function ilovaYarat({
ombor, config, corsOriginlar,
idempotentlikOmbori = new XotiraIdempotentlikOmbori(),
log, xabarchi, tz, trustProxy, hsts,
}: Bogliqliklar): Promise<NestExpressApplication> {
// bodyParser: false — Nest o'z JSON parser'ini qo'ymasin.
// abortOnError: false — fabrika yiqilsa (config xato, baza yo'q)
// Nest process.exit(1) QILMASIN, xato Promise orqali chiqsin.
// bufferLogs — Nest loglari pino Logger ulangunicha yig'iladi
const app = await NestFactory.create<NestExpressApplication>(
AppModule.forRoot({
ombor, config, log, xabarchi, tz, idempotentlikOmbori,
}),
{ bodyParser: false, bufferLogs: true, abortOnError: false },
);
app.useLogger(app.get(Logger));
const c = app.get<Config>(CONFIG);
const l = app.get<Log>(LOG);
app.set("trust proxy", trustProxy ?? c.TRUST_PROXY);
app.disable("x-powered-by"); // "Express" ekanini oshkor qilmaymiz
// app.use() — Express middleware'lari, Nest marshrutlaridan OLDIN
app.use(sorovId());
// so'rov logi (pino-http), keyin CLS konteksti: req.log "log"
// kalitiga — xizmat, filtr, fon ishi joriyLog() bilan oladi
app.use(sorovLogi(l));
app.use(
new ClsMiddleware({
generateId: true,
idGenerator: (req: express.Request) => req.sorovId,
setup: (cls, req: express.Request) => {
cls.set(CLS_LOG, req.log);
},
}).use,
);
app.use(helmet({ strictTransportSecurity: hsts ?? c.HSTS }));
app.use(
cors({
origin: [...(corsOriginlar ?? c.CORS_ORIGINS)],
allowedHeaders: [
"Content-Type", "Idempotency-Key", "X-Request-Id",
"If-Match", "If-None-Match",
],
exposedHeaders: [
"Location", "X-Request-Id", "Idempotency-Replayed", "ETag",
"Link", "Deprecation", "Sunset",
],
maxAge: 600,
}),
);
app.useStaticAssets(join(import.meta.dirname, "..", "public"));
app.use(express.json({ limit: "16kb", type: JSON_TURLARI }));
app.use(tanaXatosi);
// Versiya URI'da: /v1/... ; VERSION_NEUTRAL — versiyasiz eski yo'l
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: ["1", VERSION_NEUTRAL],
});
await app.init();
return app;
}8.5 Test timeout'lari
O'tgan darsda jarayon.test.ts band mashinada bir marta yiqilganini ko'rdik. Vitest'ning sukut timeout'i — 5 s; 25–27 dagi node:test da esa timeout yo'q edi, testlar bunga moslanmagan. vitest.config.ts yakuniy:
// vitest.config.ts — Nest 12 ESM loyihalarining sukut test vositasi.
// Vite 8 (oxc) dekorator metama'lumotini tsconfig'dan oladi
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// 25–27 dagi nomlar (test/*.test.ts) + Nest odati (*.spec.ts)
include: ["test/**/*.test.ts", "src/**/*.spec.ts"],
// config process.env dan — testlar jim bo'lsin (o'z logini
// bergan testlar — xotiraLogi — bundan mustasno)
env: { LOG_LEVEL: "silent" },
// Vitest sukuti 5 s (node:test — cheksiz): baza bilan ishlaydigan
// testlar yuklangan mashinada 5–7 s oldi — 20 s
testTimeout: 20_000,
hookTimeout: 30_000,
},
});test/jarayon.test.ts da { timeout: 20_000 } → { timeout: 60_000 }. Kanon o'lchovi: bola jarayon yolg'iz ~3 s da ko'tariladi, parallel testlar bilan yuklangan mashinada 20 s dan oshdi (IlmHamroh, 2026-10). Bizda ham yolg'iz 3 s edi.
8.6 Natija: xizmat logi so'rov ID si bilan
npm run build, keyin SAQLASH=xotira node dist/main.js va X-Request-Id: aziz-0001 sarlavhali POST /v1/vazifalar. Biz yurgizdik (biz ishga tushirdik; time maydoni olib tashlandi):
{"daraja":"info","ilova":"vazifalar-api","context":"InstanceLoader","msg":"ClsModule dependencies initialized"}
{"daraja":"info","ilova":"vazifalar-api","sorovId":"aziz-0001","vazifaId":1,"msg":"vazifa yaratildi"}
{"daraja":"info","ilova":"vazifalar-api","sorovId":"aziz-0001","req":{"method":"POST","url":"/v1/vazifalar","ip":"127.0.0.1","userAgent":"node"},"res":{"status":201},"ms":20,"msg":"POST /v1/vazifalar 201"}
{"daraja":"warn","ilova":"vazifalar-api","sorovId":"aziz-0002","req":{"method":"POST","url":"/v1/vazifalar","ip":"127.0.0.1","userAgent":"node"},"res":{"status":400},"ms":4,"msg":"POST /v1/vazifalar 400"}Ikkinchi qator — VazifalarService ichidagi joriyLog(this.log).info(...). Xizmat req ni bilmaydi, lekin qatorda sorovId: "aziz-0001" — CLS orqali keldi. Uchinchi qator — shu so'rovning pino-http qatori, ID bir xil. Ishga tushish logida yangi modullar ham ko'rindi: ClsModule, ClsCommonModule, ClsRootModule, ClsPluginsModule.
8.7 Tekshiruv va commit
npm run checkBiz holat nusxasida yurgizdik (Postgres'siz): tip 0, lint 0, Vitest — 75 passed | 6 skipped, build o'tdi. Kanon bazali to'liq muhitda (kanon o'lchovi, IlmHamroh, 2026-10):
--- npm run tip
exit 0 (5698 ms)
--- npm run lint
exit 0 (10588 ms)
--- npm run test
↓ test/konteyner.test.ts (1 test | 1 skipped)
Test Files 22 passed | 1 skipped (23)
Tests 117 passed | 1 skipped (118)
exit 0 (8988 ms)
--- npm run build
exit 0 (6620 ms)Testcontainers 10/10, jonli API 9 so'rov uch saqlash rejimida (Postgres, Prisma, Prisma + kesh) — javoblar o'zgarmadi. Prod bog'liqliklar: 15 → 18 (@nestjs/config, nestjs-pino, nestjs-cls).
Commit:
git add -A
M="refactor: config — ConfigModule (registerAs + Zod configYukla),"
M+=" Nest loglari pino orqali (nestjs-pino),"
M+=" so'rov konteksti — nestjs-cls"
git commit -m "$M"9. Real ishda
- Correlation ID — prodakshnda xato qidirishning birinchi vositasi: foydalanuvchi "xato chiqdi" deydi, javobdagi
sorovIdbo'yicha shu so'rovning hamma log qatorlari topiladi. nestjs-clsNest loyihalarida joriy foydalanuvchi,tenantId, til va tranzaksiya uchun keng ishlatiladi. REQUEST scope esa "bilmasdan ishlatilgan va sekinlashtirgan" narsa sifatida review'da ko'p uchraydi.- Navbat ishchilari va cron —
cls.runni unutish klassik xato: fon ishining loglari ID'siz qoladi. 25-qismdan beri bor fon eksporti (/v1/eksportlar)sorovIdni shu sababli o'zi olib yuradi. - Intervyu: "Nest'da joriy foydalanuvchini xizmatda qanday olasiz?", "REQUEST scope nega sekin?", "AsyncLocalStorage nima?".
Xulosa
REQUESTscope har so'rovda yangi nusxa yaratadi va iste'molchilar tomon ko'piklanadi; CLS bilan xizmatlar singleton qoladi.ClsModule.forRoot—global,middleware(mount,generateId,idGenerator,setup).ClsService:set,get,getId,isActive,run; DI'siz —ClsServiceManager.getClsService().- So'rovdan tashqarida kontekst yo'q —
cls.runbilan oching. - Kanon:
mount: false+app.use(new ClsMiddleware(...).use)—pino-httpdan keyin, qolgan hamma middleware'dan oldin. - Config, log va kontekst qadami tugadi: 25-qism xulqi o'zgarmadi, Nest standart vositalari ichiga o'tdi.
Keyingi dars: Prisma bilan NestJS — PrismaService naqshi, onModuleInit va Prisma xatolari filtri, hamda kanon nega omborni boshqacha ulaganini ko'ramiz.
Manbalar
nestjs-clshujjati (setup,ClsService, middleware/guard/interceptor,ClsServiceManager): https://papooch.github.io/nestjs-cls/- NestJS hujjati — Injection scopes (REQUEST scope, scope hierarchy): https://docs.nestjs.com/fundamentals/injection-scopes
- Node.js —
AsyncLocalStorage: https://nodejs.org/api/async_context.html#class-asynclocalstorage - Kanon (IlmHamroh, 2026-10-27): config/log/kontekst qadami —
ClsModule(mount: false),ClsMiddlewareapp.usebilan,joriyLog→ClsServiceManager, Vitest timeout'lari; kanon tekshiruvi: 117/118, Testcontainers 10/10, jonli 9/9 × 3. REQUEST scope vaClsServicetajribasi, loyiha logi va oraliq holat tekshiruvlari — biz ishga tushirdik (2026-11-02, NestJS 12.2.0,nestjs-cls7.0.1, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!