Papooch / nestjs-cls

A continuation-local storage (async context) module compatible with NestJS's dependency injection.
https://papooch.github.io/nestjs-cls/
MIT License
393 stars 23 forks source link

Example on how to use ClsMiddleware with dependency injection #50

Closed yskeat closed 1 year ago

yskeat commented 1 year ago

I'm new to Nestjs and I'm trying to use middleware to store both correlationId and contextLogger into cls to ease my services to log with context. Here is my middleware code

@Injectable()
export class TestClsMiddleware implements NestMiddleware {
  constructor(
    private readonly clsService: ClsService,
    @Inject(ILogger) private readonly loggerService: ILogger,
  ) {}
  use(req: any, res: any, next: (err?: any) => any) {
    const cls = this.clsService;
    let {
      'x-correlation-id': correlationId,
    } = req.headers;

    cls.set('correlationId', correlationId);
    const contextLogger = this.loggerService.getContextLogger({correlationId, causationId, processId});
    cls.set('logger', contextLogger);
  }
}

@Module({
  imports: [ClsModule.forRoot({
    global     : true,
    middleware : {
      mount : true,
      setup : (cls: ClsService, req: Request) => {
      }
    }
  }],
  controllers : [AppController],  
})
export class AppModule implements NestModule {
  async configure(consumer: MiddlewareConsumer) {
    const middlewares: any[] = [helmet()];

    middlewares.push(SessionMiddleware);
    consumer
      .apply(TestClsMiddleware )
      .forRoutes('*');
  }
}

From above code, I get error Cannot set the key \"correlationId\". No CLS context available, please make sure that a ClsMiddleware/Guard/Interceptor has set up the context, or wrap any calls that depend on CLS with \"ClsService#run\" when I it hit the line cls.set('correlationId', correlationId); Please enlight me what is missing from my code. Or is there any better way to archive it?

Papooch commented 1 year ago

Hm, this should work in the general case.

Can you provide a minimal reproducible example (a repo with runnable code that I can test)? What's going on with the middlewares array, I don't see it used anywhere, is there some context that you omitted? It is possible that some other middleware causes the context to be lost.

Also, yould you try setting mount to false and applying the ClsMiddleware inside the AppModule yourself (preferably before the first middleware that needs the context and after any middleware that might cause context loss).

yskeat commented 1 year ago

Thanks @Papooch , I able to make work by adding ClsMiddleware before I access it in my TestClsMiddleware. Below is my working example

@Injectable()
export class TestClsMiddleware implements NestMiddleware {
  constructor(
    private readonly clsService: ClsService,
    @Inject(ILogger) private readonly loggerService: ILogger,
  ) {}
  use(req: any, res: any, next: (err?: any) => any) {
    const cls = this.clsService;
    let {
      'x-correlation-id': correlationId,
    } = req.headers;

    const processId = nanoid();
    cls.set('correlationId', correlationId);
    const contextLogger = this.loggerService.getContextLogger({correlationId, processId});
    cls.set('logger', contextLogger);
  }
}

@Module({
  imports: [ClsModule.forRoot({
    global : true,
  }],
  controllers : [AppController],  
})
export class AppModule implements NestModule {
  async configure(consumer: MiddlewareConsumer) {
    const middlewares: any[] = [helmet()];

    middlewares.push(someOtherMiddlewares);
    middlewares.push(ClsMiddleware); // <-- added this line to create cls context
    middlewares.push(TestClsMiddleware );
    consumer
      .apply(middlewares)
      .forRoutes('*');
  }
}