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_).
|
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
|
||||||
|
|
|
@ -23,3 +23,4 @@ networks:
|
||||||
v3-network:
|
v3-network:
|
||||||
name: v3-network
|
name: v3-network
|
||||||
driver: bridge
|
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"
|
"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",
|
||||||
|
|
|
@ -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: [],
|
||||||
})
|
})
|
||||||
|
|
|
@ -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