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