Nest: Custom db logger service with TypeOrmModule.forRootAsync()

Created on 20 Nov 2018  路  16Comments  路  Source: nestjs/nest


[ ] Regression 
[ ] Bug report
[ ] Feature request
[X] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

I have a custom DB module to initialize TypeOrm (= Nestjs's TypeOrmModule). I want to pass an TypeormLoggerService (which relies on an app-wide LoggerService). This is the code:

db.module.ts:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeormLoggerService } from './typeorm-logger.service';

@Module({
  controllers: [],
  providers: [],
  imports: [
    TypeOrmModule.forRootAsync({
      inject: [TypeormLoggerService],
      useFactory: async (typeormLoggerService: TypeormLoggerService): Promise<any> => ({
        type: 'sqlite',
        database: `var/nest.db`,
        entities: [`src/**/**.entity{.ts,.js}`],
        logger: typeormLoggerService,
        logging: true
      }),
    })
  ]
})
export class DbModule {}

Node crashes with the following error:

1: node::Abort() [/usr/local/bin/node]
 2: node::Chdir(v8::FunctionCallbackInfo<v8::Value> const&) [/usr/local/bin/node]
 3: v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) [/usr/local/bin/node]
 4: v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) [/usr/local/bin/node]
 5: v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) [/usr/local/bin/node]
 6: 0x1776425042fd

Environment


nest/typeorm version: 5.2.2
nest/common version: 5.3.5
nest/core version: 5.3.4
needs clarification

Most helpful comment

TypeOrmModule.forRootAsync({
   inject: [TypeormLoggerService],
   useFactory: ...,
})

You are trying to inject TypeormLoggerService which is unavailable in the TypeOrmModule. In order to provide it, you would have to import a module that exports TypeormLoggerService, for example:

TypeOrmModule.forRootAsync({
   imports: [TypeOrmLoggerModule], <------ THIS
   inject: [TypeormLoggerService],
   useFactory: ...,
})

All 16 comments

Are you sure that this is a full node output? Also, where is your TypeormLoggerService registered?

We need your full logs.

Are you sure that this is a full node output? Also, where is your TypeormLoggerService registered?

regarding Logs)
Yes, this is the full output. No more messages, besides that:

[nodemon] app crashed - waiting for file changes before starting...
[nodemon] restarting due to changes...
[nodemon] starting `ts-node -r tsconfig-paths/register src/main.ts`

(after reload crashes again and so on)

regarding registration)
TypeormLoggerService is part of the same module (DbModule). I've added it now also to "providers" of the DbModule and to "inject" of TypeOrmModule.forRootAsync(), but nothing changed.

This is the TypeormLoggerService (if needed):

import { Injectable } from '@nestjs/common';
import { Logger, QueryRunner } from 'typeorm';
import { LogService } from 'common/misc/log.service';

@Injectable()
export class TypeormLoggerService implements Logger {
  constructor(
    private logService: LogService
  ) {}

  public logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
    this.logService.trace(query, parameters, queryRunner);
  }

  public logQueryError(error: string, query: string, parameters?: any[], queryRunner?: QueryRunner) {
    this.logService.error(query, parameters, queryRunner);
  }

  public logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) {
    this.logService.warn(`Slow query took ${time} ms: %o %o %o`, query, parameters, queryRunner);
  }

  public logSchemaBuild(message: string, queryRunner?: QueryRunner) {
    this.logService.info(message, queryRunner);
  }

  public logMigration(message: string, queryRunner?: QueryRunner) {
    this.logService.info(message, queryRunner);
  }

  public log(level: 'log' | 'info' | 'warn', message: any, queryRunner?: QueryRunner) {
    switch (level) {
      case 'log': this.logService.info(message, queryRunner); break;
      case 'info': this.logService.info(message, queryRunner); break;
      case 'warn': this.logService.warn(message, queryRunner); break;
    }
  }
}

We need a reproduction repository

Repository is here:
https://github.com/cvh23/nestjs-typeorm-logger/tree/master/src/common

Now with the minimal example I get a little better log output. Looks like typeormLogServicecant be resolved in src/common/db/db.module.ts. What could be the reason?

[Nest] 80989   - 2018-11-20 18:22:08   [NestFactory] Starting Nest application...
[Nest] 80989   - 2018-11-20 18:22:08   [InstanceLoader] AppModule dependencies initialized +23ms
[Nest] 80989   - 2018-11-20 18:22:08   [InstanceLoader] CommonModule dependencies initialized +0ms
[Nest] 80989   - 2018-11-20 18:22:08   [InstanceLoader] MiscModule dependencies initialized +1ms
[Nest] 80989   - 2018-11-20 18:22:08   [InstanceLoader] TypeOrmModule dependencies initialized +0ms
[Nest] 80989   - 2018-11-20 18:22:08   [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument at index [0] is available in the current context. +20ms
Error: Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument at index [0] is available in the current context.
    at Injector.lookupComponentInExports (/home/cvh23/nestjs-typeorm-logger/node_modules/@nestjs/core/injector/injector.js:146:19)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
    at Function.Module.runMain (module.js:695:11)
    at Object.<anonymous> (/home/cvh23/nestjs-typeorm-logger/node_modules/ts-node/src/_bin.ts:177:12)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
 1: node::Abort() [/usr/local/bin/node]
 2: node::Chdir(v8::FunctionCallbackInfo<v8::Value> const&) [/usr/local/bin/node]
 3: v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) [/usr/local/bin/node]
 4: v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) [/usr/local/bin/node]
 5: v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) [/usr/local/bin/node]
 6: 0x28e724f042fd
[nodemon] app crashed - waiting for file changes before starting...

That is exactly what I was asking about, TypeormLoggerService is not present in this scope

But isnt the inject providing the service to the scope?

 TypeOrmModule.forRootAsync({
      inject: [TypeormLoggerService],
      useFactory: ...

I ran into a similar problem with nestjs/terminus which is built similar as nestjs/typeorm
What fixed it for me is to use @Inject in combination with useClass (terminus example).

import { Module } from '@nestjs/common';
import { TypeOrmModule, TypeOrmOptionsFactory  } from '@nestjs/typeorm';
import { TypeormLoggerService } from './typeorm-logger.service';

class TypeOrmOptions implements TypeOrmOptionsFactory {
  constructor(@Inject(TypeormLoggerService) private readonly typeormLoggerService: TypeormLoggerService){}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    return {
        type: 'sqlite',
        database: `var/nest.db`,
        entities: [`src/**/**.entity{.ts,.js}`],
        logger: this.typeormLoggerService,
        logging: true
      };
  }
}

@Module({
  controllers: [],
  providers: [],
  imports: [
    TypeOrmModule.forRootAsync({
      inject: [TypeormLoggerService],
      useClass: TypeOrmOptions,
    })
  ]
})
export class DbModule {}

Can you try this out? @cvh23

Is this the recommended way? @kamilmysliwiec

@BrunnerLivio @Inject() definitely shouldn't be required in this case.

But isnt the inject providing the service to the scope?

Actually, no. It works in the same way as a typical custom provider, therefore TypeormLoggerService has to be defined as providers somewhere, for example, in the imported module (that is why you have imports array in forRootAsync()). Perhaps, we should think about extraProviders property that would define and make provider available in the async scope.

@BrunnerLivio Same error, now in TypeOrmOption.

[Nest] 82715   - 2018-11-20 23:49:38   [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmOptions (?). Please make sure that the argument at index [0] is available in the current context. +15ms
Error: Nest can't resolve dependencies of the TypeOrmOptions (?). Please make sure that the argument at index [0] is available in the current context.
    at Injector.lookupComponentInExports (/home/cvh23/nestjs-typeorm-logger/node_modules/@nestjs/core/injector/injector.js:146:19)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:228:7)
    at Function.Module.runMain (module.js:695:11)
    at Object.<anonymous> (/home/cvh23/nestjs-typeorm-logger/node_modules/ts-node/src/_bin.ts:177:12)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
 1: node::Abort() [/usr/local/bin/node]

@kamilmysliwiec
TypeormLoggerService is in the same module and it is declared in providers of the module definition.

TypeormLoggerService is in the same module and it is declared in providers of the module definition.

forRootAsync() creates a separated scope - it is actually extending TypeOrmModule with extra options which means that registering TypeormLoggerService in DbModule wouldn't be enough

So there is no possibility to achieve that at the moment?

I got it working with imports, but I really do not know if this is intended behavior...

import { Module, Inject } from '@nestjs/common';
import { TypeOrmModule, TypeOrmOptionsFactory, TypeOrmModuleOptions  } from '@nestjs/typeorm';
import { TypeormLoggerService } from './typeorm-logger.service';

class TypeOrmOptions implements TypeOrmOptionsFactory {
  // If this @Inject gets removed, logger will be undefined (???)
  constructor(@Inject(TypeormLoggerService) private readonly logger: TypeormLoggerService){}

  createTypeOrmOptions(): TypeOrmModuleOptions {
    this.logger.log('log', 'asdf'); // prints: asdf
    return {
        type: 'sqlite',
        database: `var/nest.db`,
        entities: [`src/**/**.entity{.ts,.js}`],
        logger: this.logger,
        logging: true,
      };
  }
}

@Module({
  providers: [TypeormLoggerService],
  exports: [TypeormLoggerService],
  imports: [
    TypeOrmModule.forRootAsync({
      // Thats the silver bullet
      // But I recommend to create a separate module, instead of this strange
      // circular importing. Why is this even possible?
      imports: [DbModule],
      useClass: TypeOrmOptions,
    }),
  ],
})
export class DbModule { }

Funnily enough, if you remove @Inject() in the constructor of TypeOrmOptions, then logger is undefined. I think I am performing some dark magic..

TypeOrmModule.forRootAsync({
   inject: [TypeormLoggerService],
   useFactory: ...,
})

You are trying to inject TypeormLoggerService which is unavailable in the TypeOrmModule. In order to provide it, you would have to import a module that exports TypeormLoggerService, for example:

TypeOrmModule.forRootAsync({
   imports: [TypeOrmLoggerModule], <------ THIS
   inject: [TypeormLoggerService],
   useFactory: ...,
})

I had to export TypeormLoggerService from DbModule and import it in TypeOrmModule although TypeOrmModule is included in the Module of TypeormLoggerService (DbModule). That was a bit confusing. But now I understand and it's working. Thanks a lot!

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings