Merge branch 'microservices' into 'main'

Microservices

See merge request mobicoop/lab/v3/services/logger!1
This commit is contained in:
Gsk54 2022-12-26 14:09:46 +00:00
commit ba0c35a3cf
11 changed files with 1118 additions and 19 deletions

View File

@ -26,6 +26,33 @@ docker-compose up -d
The app runs automatically on the port defined in `SERVICE_PORT` of `.env` file (default : _5099_). 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 ## Test
```bash ```bash

View File

@ -23,3 +23,4 @@ networks:
v3-network: v3-network:
name: v3-network name: v3-network
driver: bridge driver: bridge
external: true

951
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,12 +21,18 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@golevelup/nestjs-rabbitmq": "^3.4.0",
"@nestjs/common": "^9.0.0", "@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0", "@nestjs/core": "^9.0.0",
"@nestjs/microservices": "^9.2.1",
"@nestjs/platform-express": "^9.0.0", "@nestjs/platform-express": "^9.0.0",
"nest-winston": "^1.8.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.2.0" "rxjs": "^7.2.0",
"winston": "^3.8.2",
"winston-daily-rotate-file": "^4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^9.0.0", "@nestjs/cli": "^9.0.0",

View File

@ -1,7 +1,9 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { LoggerModule } from './modules/logger.module';
@Module({ @Module({
imports: [], imports: [ConfigModule.forRoot({ isGlobal: true }), LoggerModule],
controllers: [], controllers: [],
providers: [], providers: [],
}) })

View File

@ -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 {}

View File

@ -0,0 +1,10 @@
export enum level {
emerg = 'emerg',
alert = 'alert',
crit = 'crit',
error = 'error',
warning = 'warning',
notice = 'notice',
info = 'info',
debug = 'debug',
}

View File

@ -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],
};
}

View File

@ -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;
})();
}

View File

@ -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(),
);
}
}

View File

@ -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));
}
}