Merge branch 'microservices' into 'main'
Microservices See merge request mobicoop/lab/v3/services/logger!1
This commit is contained in:
		
						commit
						ba0c35a3cf
					
				
							
								
								
									
										27
									
								
								README.md
								
								
								
								
							
							
						
						
									
										27
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -26,6 +26,33 @@ docker-compose up -d
 | 
			
		|||
 | 
			
		||||
The app runs automatically on the port defined in `SERVICE_PORT` of `.env` file (default : _5099_).
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
The app subscribes to RabbitMQ queues in order to log messages. We use the routing key with the given format : <**service**>.<**action**>.<**level**>, with :
 | 
			
		||||
 | 
			
		||||
-   **service** : the name of the service that has emitted the message
 | 
			
		||||
-   **action** : the action that was at the origin of the message (one or more words separated by a dot)
 | 
			
		||||
-   **level** : the severity (_log level_) of the message, as described in [RFC5424 syslog](https://www.rfc-editor.org/rfc/rfc5424) :
 | 
			
		||||
    -   **emer** : Emergency: system is unusable
 | 
			
		||||
    -   **alert** : Alert: action must be taken immediately
 | 
			
		||||
    -   **crit** : Critical: critical conditions
 | 
			
		||||
    -   **error** : Error: error conditions
 | 
			
		||||
    -   **warning** : Warning: warning conditions
 | 
			
		||||
    -   **notice** : Notice: normal but significant condition
 | 
			
		||||
    -   **info** : Informational: informational messages
 | 
			
		||||
    -   **debug** : Debug: debug-level messages
 | 
			
		||||
 | 
			
		||||
Examples of valid routing keys :
 | 
			
		||||
 | 
			
		||||
-   user.create.info
 | 
			
		||||
-   user.create.crit
 | 
			
		||||
-   user.update.warning
 | 
			
		||||
-   auth.username.add.info
 | 
			
		||||
 | 
			
		||||
It is the responsibility of each service to set the routing key to the appropriate value.
 | 
			
		||||
 | 
			
		||||
To handle logs for a given service, a controller corresponding to the service must be set. In this controller, you must define a handler for each action and level. You must also define a unique RabbitMQ queue for this handler (this permits to use durable queues, allowing to keep logs in RabbitMQ even if the logger service is down).
 | 
			
		||||
 | 
			
		||||
## Test
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,3 +23,4 @@ networks:
 | 
			
		|||
  v3-network:
 | 
			
		||||
    name: v3-network
 | 
			
		||||
    driver: bridge
 | 
			
		||||
    external: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -21,12 +21,18 @@
 | 
			
		|||
    "test:e2e": "jest --config ./test/jest-e2e.json"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@golevelup/nestjs-rabbitmq": "^3.4.0",
 | 
			
		||||
    "@nestjs/common": "^9.0.0",
 | 
			
		||||
    "@nestjs/config": "^2.2.0",
 | 
			
		||||
    "@nestjs/core": "^9.0.0",
 | 
			
		||||
    "@nestjs/microservices": "^9.2.1",
 | 
			
		||||
    "@nestjs/platform-express": "^9.0.0",
 | 
			
		||||
    "nest-winston": "^1.8.0",
 | 
			
		||||
    "reflect-metadata": "^0.1.13",
 | 
			
		||||
    "rimraf": "^3.0.2",
 | 
			
		||||
    "rxjs": "^7.2.0"
 | 
			
		||||
    "rxjs": "^7.2.0",
 | 
			
		||||
    "winston": "^3.8.2",
 | 
			
		||||
    "winston-daily-rotate-file": "^4.7.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@nestjs/cli": "^9.0.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { ConfigModule } from '@nestjs/config';
 | 
			
		||||
import { LoggerModule } from './modules/logger.module';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [],
 | 
			
		||||
  imports: [ConfigModule.forRoot({ isGlobal: true }), LoggerModule],
 | 
			
		||||
  controllers: [],
 | 
			
		||||
  providers: [],
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { UserController } from './logger/adapters/primaries/user.controller';
 | 
			
		||||
import { WinstonModule } from 'nest-winston';
 | 
			
		||||
import * as winston from 'winston';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    RabbitMQModule.forRootAsync(RabbitMQModule, {
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      useFactory: async (configService: ConfigService) => ({
 | 
			
		||||
        exchanges: [
 | 
			
		||||
          {
 | 
			
		||||
            name: 'logging',
 | 
			
		||||
            type: 'topic',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        uri: configService.get<string>('RMQ_URI'),
 | 
			
		||||
        enableControllerDiscovery: true,
 | 
			
		||||
      }),
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
    }),
 | 
			
		||||
    WinstonModule.forRoot({
 | 
			
		||||
      levels: winston.config.syslog.levels,
 | 
			
		||||
      transports: [],
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [UserController],
 | 
			
		||||
  providers: [],
 | 
			
		||||
  exports: [],
 | 
			
		||||
})
 | 
			
		||||
export class LoggerModule {}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
export enum level {
 | 
			
		||||
  emerg = 'emerg',
 | 
			
		||||
  alert = 'alert',
 | 
			
		||||
  crit = 'crit',
 | 
			
		||||
  error = 'error',
 | 
			
		||||
  warning = 'warning',
 | 
			
		||||
  notice = 'notice',
 | 
			
		||||
  info = 'info',
 | 
			
		||||
  debug = 'debug',
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
import { TransportOptions } from './transport.options';
 | 
			
		||||
import * as winston from 'winston';
 | 
			
		||||
import { level } from './level.enum';
 | 
			
		||||
import 'winston-daily-rotate-file';
 | 
			
		||||
 | 
			
		||||
export default function loggerOptions(
 | 
			
		||||
  dirname: string,
 | 
			
		||||
  level: level,
 | 
			
		||||
  filename: string,
 | 
			
		||||
) {
 | 
			
		||||
  const transportOptions = new TransportOptions(dirname, level, filename);
 | 
			
		||||
  const transport = new winston.transports.DailyRotateFile({
 | 
			
		||||
    ...transportOptions,
 | 
			
		||||
  });
 | 
			
		||||
  return {
 | 
			
		||||
    levels: winston.config.syslog.levels,
 | 
			
		||||
    transports: [transport],
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
import * as winston from 'winston';
 | 
			
		||||
 | 
			
		||||
export default function filter(level) {
 | 
			
		||||
  return winston.format((info) => {
 | 
			
		||||
    if (typeof level === 'string') {
 | 
			
		||||
      if (info.level === level) return info;
 | 
			
		||||
    } else if (Array.isArray(level)) {
 | 
			
		||||
      if (level.includes(info.level)) return info;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  })();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
import filter from './loglevel.filter';
 | 
			
		||||
import * as winston from 'winston';
 | 
			
		||||
import { level as levelEnum } from './level.enum';
 | 
			
		||||
 | 
			
		||||
export class TransportOptions {
 | 
			
		||||
  extension = '.log';
 | 
			
		||||
  maxSize = '1m';
 | 
			
		||||
  maxFiles = '60';
 | 
			
		||||
  zippedArchive = true;
 | 
			
		||||
  filename = 'info';
 | 
			
		||||
  level = levelEnum.info;
 | 
			
		||||
  dirname = 'logs';
 | 
			
		||||
  format: winston.Logform.Format = winston.format.combine(
 | 
			
		||||
    filter(this.level),
 | 
			
		||||
    winston.format.timestamp(),
 | 
			
		||||
    winston.format.json(),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  constructor(dir: string, level: levelEnum, filename: string) {
 | 
			
		||||
    this.level = level;
 | 
			
		||||
    this.filename = filename;
 | 
			
		||||
    this.dirname += '/' + dir;
 | 
			
		||||
    this.format = winston.format.combine(
 | 
			
		||||
      filter(this.level),
 | 
			
		||||
      winston.format.timestamp(),
 | 
			
		||||
      winston.format.json(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { Controller, Inject } from '@nestjs/common';
 | 
			
		||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
 | 
			
		||||
import { Logger } from 'winston';
 | 
			
		||||
import { level } from './logger/level.enum';
 | 
			
		||||
import loggerOptions from './logger/logger';
 | 
			
		||||
 | 
			
		||||
@Controller()
 | 
			
		||||
export class UserController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    exchange: 'logging',
 | 
			
		||||
    routingKey: 'user.create.info',
 | 
			
		||||
    queue: 'logging-user-create-info',
 | 
			
		||||
  })
 | 
			
		||||
  public async userCreatedInfoHandler(message: string) {
 | 
			
		||||
    this.logger.configure(loggerOptions('user', level.info, 'info'));
 | 
			
		||||
    this.logger.info(JSON.parse(message));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    exchange: 'logging',
 | 
			
		||||
    routingKey: 'user.create.warning',
 | 
			
		||||
    queue: 'logging-user-create-warning',
 | 
			
		||||
  })
 | 
			
		||||
  public async userCreatedWarningHandler(message: string) {
 | 
			
		||||
    this.logger.configure(loggerOptions('user', level.warning, 'warning'));
 | 
			
		||||
    this.logger.warning(JSON.parse(message));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    exchange: 'logging',
 | 
			
		||||
    routingKey: 'user.create.crit',
 | 
			
		||||
    queue: 'logging-user-create-crit',
 | 
			
		||||
  })
 | 
			
		||||
  public async userCreatedCriticalHandler(message: string) {
 | 
			
		||||
    this.logger.configure(loggerOptions('user', level.crit, 'critical'));
 | 
			
		||||
    this.logger.crit(JSON.parse(message));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue