From cdda74475ef47d40b8e02019c346746dba0d3ded Mon Sep 17 00:00:00 2001 From: vigdorov Date: Sun, 19 Jul 2020 14:40:07 +0300 Subject: [PATCH] =?UTF-8?q?HM-57.=20=D0=A1=D0=BE=D0=B1=D0=B8=D1=80=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=D1=81=D1=8F=20=D0=B4=D0=B2=D0=B0=20=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=B0=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5:=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=BD=D1=8B=D0=B5=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B8=20=D0=BF=D0=B0=D1=80=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81-=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8E=20=D0=B0=D0=BF=D0=B8.=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=8B=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=80=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BE=D0=BF=20=D0=BF=D0=BE=D0=BB=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=82=D0=BE=D0=BC=20=D1=87=D0=B8=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=B0=D1=82=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BA=D0=B8.=20=D0=9F=D0=BE=D1=8F=D0=B2=D0=B8=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=8C=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=BE=D1=87=D0=B8=D1=89=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 14 +++++++ package.json | 2 + src/app.module.ts | 12 +++--- src/consts.ts | 4 ++ src/logs/logging.interceptor.ts | 63 +++++++++++++++++++++++++++++++ src/logs/logs.controller.ts | 51 ++++++++++++++++++++----- src/logs/logs.schema.ts | 66 +++++++++++++++++++++++++++++---- src/logs/logs.service.ts | 30 +++++++++++---- src/services/logger.service.ts | 38 ++++++++++++++----- src/store/store.controller.ts | 4 +- src/store/store.service.ts | 3 +- store.http | 19 ++++++---- 12 files changed, 259 insertions(+), 47 deletions(-) create mode 100644 src/logs/logging.interceptor.ts diff --git a/package-lock.json b/package-lock.json index b14684e..9c7554f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1962,6 +1962,15 @@ "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", "dev": true }, + "@types/moment": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", + "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=", + "dev": true, + "requires": { + "moment": "*" + } + }, "@types/mongodb": { "version": "3.5.25", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.25.tgz", @@ -9199,6 +9208,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "mongodb": { "version": "3.5.9", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz", diff --git a/package.json b/package.json index d20bb01..837b659 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@nestjs/mongoose": "^7.0.1", "@nestjs/platform-express": "^7.0.0", "@nestjs/swagger": "^4.5.12", + "moment": "^2.27.0", "mongoose": "^5.9.21", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", @@ -38,6 +39,7 @@ "@nestjs/testing": "^7.0.0", "@types/express": "^4.17.3", "@types/jest": "25.2.3", + "@types/moment": "^2.13.0", "@types/mongoose": "^5.7.29", "@types/node": "^13.9.1", "@types/supertest": "^2.0.8", diff --git a/src/app.module.ts b/src/app.module.ts index b9c4d56..c03a7f3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,12 +1,13 @@ -import {Module} from '@nestjs/common'; +import {Module, NestModule, MiddlewareConsumer} from '@nestjs/common'; import {MongooseModule} from '@nestjs/mongoose'; -import {MONGO_URL, DB_NAME, DB_TEST_NAME, DB_LOGGER} from './consts'; +import {MONGO_URL, DB_NAME, DB_TEST_NAME, DB_LOGGER, COLLECTION_STORE} from './consts'; import {StoreService} from './store/store.service'; import {Store, StoreSchema} from './store/store.schema'; import {StoreController} from './store/store.controller'; -import {Log, LogSchema} from './logs/logs.schema'; + import {LogsService} from './logs/logs.service'; import {LogsController} from './logs/logs.controller'; +import {ClientLog, ClientLogSchema, ServerLog, ServerLogSchema} from './logs/logs.schema'; @Module({ imports: [ @@ -26,7 +27,8 @@ import {LogsController} from './logs/logs.controller'; {name: Store.name, schema: StoreSchema}, ], DB_TEST_NAME), MongooseModule.forFeature([ - {name: Log.name, schema: LogSchema}, + {name: ClientLog.name, schema: ClientLogSchema}, + {name: ServerLog.name, schema: ServerLogSchema}, ], DB_LOGGER), ], controllers: [ @@ -38,4 +40,4 @@ import {LogsController} from './logs/logs.controller'; LogsService, ] }) -export class AppModule {} \ No newline at end of file +export class AppModule {} diff --git a/src/consts.ts b/src/consts.ts index df4e3ea..7451dcc 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -4,6 +4,10 @@ export const DB_LOGGER = 'logger'; export const MONGO_URL = 'mongodb://localhost:27017'; export const COLLECTION_STORE = 'store'; export const COLLECTION_LOGS = 'logs'; +export const LOG_TYPE = { + CLIENT: 'client-logs', + SERVER: 'server-logs', +} export const ALLOW_ORIGIN_ALL: [string, string] = ['Access-Control-Allow-Origin', '*']; export const ALLOW_CREDENTIALS: [string, string] = ['Access-Control-Allow-Credentials', 'true']; export const CONTENT_LENGTH: [string, string] = ['Content-Length', '0']; diff --git a/src/logs/logging.interceptor.ts b/src/logs/logging.interceptor.ts new file mode 100644 index 0000000..2f9c7c9 --- /dev/null +++ b/src/logs/logging.interceptor.ts @@ -0,0 +1,63 @@ +import * as mongoose from 'mongoose'; +import * as moment from 'moment'; +import * as lodash from 'lodash'; + +import {Injectable, NestInterceptor, ExecutionContext, CallHandler} from '@nestjs/common'; +import {Observable, of} from 'rxjs'; +import {tap, catchError} from 'rxjs/operators'; +import {LOG_TYPE} from 'src/consts'; + +const STATUSES = { + OK: 'OK', + BAD: 'BAD', +}; + +const errorSchema = new mongoose.Schema({ + type: String, + request: Object, + response: Object, + startTime: String, + endTime: String, +}); +const ErrorModel = mongoose.model(LOG_TYPE.CLIENT, errorSchema); + +const saveLog = ( + type: string, + request: any, + response: any, + startTime: moment.Moment, +) => { + const endTime = moment(); + const error = new ErrorModel({ + type, + request: request, + response: lodash.isEmpty(response) ? 'Server down, look server-logs' : response, + startTime: startTime.format(), + endTime: endTime.format(), + }); + error.save(); +}; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const start = moment(); + const args = context.getArgs()?.[0] ?? {}; + const {headers, url, method, body} = args; + const request = { + headers, url, method, body + }; + return next + .handle() + .pipe( + tap((response: any) => { + saveLog(STATUSES.OK, request, response, start); + }), + catchError((err: any) => { + console.log(typeof err) + saveLog(STATUSES.BAD, request, err, start); + throw(err); + }), + ); + } +} diff --git a/src/logs/logs.controller.ts b/src/logs/logs.controller.ts index 1fb4ebf..5f8659b 100644 --- a/src/logs/logs.controller.ts +++ b/src/logs/logs.controller.ts @@ -1,8 +1,8 @@ -import {Controller, Get, Header} from '@nestjs/common'; -import {ApiTags, ApiResponse} from '@nestjs/swagger'; +import {Controller, Get, Header, Req, NotFoundException, Delete, Options, HttpCode} from '@nestjs/common'; +import {ApiTags, ApiResponse, ApiParam} from '@nestjs/swagger'; import {LogsService} from './logs.service'; -import {ALLOW_ORIGIN_ALL, COLLECTION_LOGS} from 'src/consts'; -import {Log, LogRequest} from './logs.schema'; +import {ALLOW_ORIGIN_ALL, COLLECTION_LOGS, LOG_TYPE, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS} from 'src/consts'; +import {ClienLogRequest, ClientLog, ServerLog} from './logs.schema'; @Controller(COLLECTION_LOGS) @ApiTags(COLLECTION_LOGS) @@ -11,15 +11,48 @@ export class LogsController { private readonly logsService: LogsService ) {} - @Get() + @Get('/client') @Header(...ALLOW_ORIGIN_ALL) @ApiResponse({ status: 200, description: 'Возвращает список всех логов', - type: [LogRequest], + type: [ClienLogRequest] }) - async findAll(): Promise { - const logsList = await this.logsService.findAll(); - return logsList; + async findAllClientLogs(): Promise { + return await this.logsService.findAllClientLogs(); + } + + @Delete('/client') + @Header(...ALLOW_ORIGIN_ALL) + async clearAllClientLogs(): Promise<[]> { + return await this.logsService.clearLogsByType(LOG_TYPE.CLIENT); + } + + @Get('/server') + @Header(...ALLOW_ORIGIN_ALL) + @ApiResponse({ + status: 200, + description: 'Возвращает список всех логов', + type: [ClienLogRequest] + }) + async findAllServerLogs(): Promise { + return await this.logsService.findAllServerLogs(); + } + + @Delete('/server') + @Header(...ALLOW_ORIGIN_ALL) + async clearAllServerLogs(): Promise<[]> { + return await this.logsService.clearLogsByType(LOG_TYPE.SERVER); + } + + @Options() + @Header(...ALLOW_ORIGIN_ALL) + @Header(...ALLOW_METHOD) + @Header(...ALLOW_CREDENTIALS) + @Header(...CONTENT_LENGTH) + @Header(...ALLOW_HEADERS) + @HttpCode(204) + async options(): Promise { + return ''; } } diff --git a/src/logs/logs.schema.ts b/src/logs/logs.schema.ts index a67f4af..921fbe2 100644 --- a/src/logs/logs.schema.ts +++ b/src/logs/logs.schema.ts @@ -2,18 +2,70 @@ import { Document } from 'mongoose'; import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose'; import {ApiProperty} from '@nestjs/swagger'; -export class LogRequest { +export class ClienLogRequest { + @ApiProperty() + type: string; + + @ApiProperty() + request: string; + + @ApiProperty() + response: string; + + @ApiProperty() + startTime: string; + + @ApiProperty() + endTime: string; +} + +export class ServerLogRequest { + @ApiProperty() + type: string; + @ApiProperty() message: string; + + @ApiProperty() + trace: string; + + @ApiProperty() + date: string; } @Schema() -export class Log extends Document { - @Prop({ - required: true, - type: String, - }) +export class ServerLog extends Document { + @Prop({required: true, type: String}) + type: string; + + @Prop({required: true, type: String}) message: string; + + @Prop({required: true, type: String}) + trace: string; + + @Prop({required: true, type: String}) + date: string; } -export const LogSchema = SchemaFactory.createForClass(Log); +export const ServerLogSchema = SchemaFactory.createForClass(ServerLog); + +@Schema() +export class ClientLog extends Document { + @Prop({required: true, type: String}) + type: string; + + @Prop({required: true, type: String}) + request: string; + + @Prop({required: true, type: String}) + response: string; + + @Prop({required: true, type: String}) + startTime: string; + + @Prop({required: true, type: String}) + endTime: string; +} + +export const ClientLogSchema = SchemaFactory.createForClass(ClientLog); diff --git a/src/logs/logs.service.ts b/src/logs/logs.service.ts index d4ee651..3c08071 100644 --- a/src/logs/logs.service.ts +++ b/src/logs/logs.service.ts @@ -1,8 +1,8 @@ import {Injectable} from '@nestjs/common'; import {InjectConnection} from '@nestjs/mongoose'; -import {DB_LOGGER, COLLECTION_LOGS} from 'src/consts'; -import {Connection, Model} from 'mongoose'; -import {LogSchema, Log} from './logs.schema'; +import {DB_LOGGER, LOG_TYPE} from 'src/consts'; +import {Connection} from 'mongoose'; +import {ClientLog, ServerLog, ClientLogSchema, ServerLogSchema} from './logs.schema'; @Injectable() export class LogsService { @@ -10,11 +10,27 @@ export class LogsService { @InjectConnection(DB_LOGGER) private dbConnection: Connection, ) {} - get logModel(): Model { - return this.dbConnection.model(COLLECTION_LOGS, LogSchema); + async findAllClientLogs(): Promise { + return this.dbConnection + .model(LOG_TYPE.CLIENT, ClientLogSchema) + .find() + .exec(); } - async findAll(): Promise { - return this.logModel.find().exec(); + async findAllServerLogs(): Promise { + return this.dbConnection + .model(LOG_TYPE.SERVER, ServerLogSchema) + .find() + .exec(); + } + + async clearLogsByType(type: string): Promise<[]> { + const logList = type === LOG_TYPE.CLIENT + ? await this.findAllClientLogs() + : await this.findAllServerLogs(); + if (logList.length) { + await this.dbConnection.collection(type).drop(); + } + return []; } } diff --git a/src/services/logger.service.ts b/src/services/logger.service.ts index 3a64ab9..1ef03ce 100644 --- a/src/services/logger.service.ts +++ b/src/services/logger.service.ts @@ -1,37 +1,55 @@ import * as mongoose from 'mongoose'; +import * as moment from 'moment'; import {Logger as DefaultLogger} from '@nestjs/common'; -import {MONGO_URL} from 'src/consts'; +import {MONGO_URL, LOG_TYPE} from 'src/consts'; + mongoose.connect(`${MONGO_URL}/logger`, {useNewUrlParser: true}); +const ERRORS = { + LOG: 'LOG', + ERROR: 'ERROR', + WARN: 'WARN', + DEBUG: 'DEBUG', + VERBOSE: 'VERBODE', +}; + const errorSchema = new mongoose.Schema({ + type: String, message: String, + trace: String, + date: String, }); -const ErrorModel = mongoose.model('logs', errorSchema); +const ErrorModel = mongoose.model(LOG_TYPE.SERVER, errorSchema); + +const saveError = (type: string, message: string, trace = '') => { + const date = moment().format(); + const error = new ErrorModel({ + type, message, trace, date + }); + error.save(); +}; export class Logger extends DefaultLogger { log(message: string): void { super.log(message); + // saveError(ERRORS.LOG, message); } error(message: string, trace: string): void { super.error(message, trace); - const error = new ErrorModel({ - message: `ERROR: message = ${message}, trace = ${trace}` - }); - error.save(); + saveError(ERRORS.ERROR, message, trace); } warn(message: string): void { super.warn(message); - const error = new ErrorModel({ - message: `WARN: message = ${message}` - }); - error.save(); + saveError(ERRORS.WARN, message); } debug(message: string): void { super.debug(message); + // saveError(ERRORS.DEBUG, message); } verbose(message: string): void { super.verbose(message); + // saveError(ERRORS.VERBOSE, message); } } \ No newline at end of file diff --git a/src/store/store.controller.ts b/src/store/store.controller.ts index 066627b..0dafd80 100644 --- a/src/store/store.controller.ts +++ b/src/store/store.controller.ts @@ -1,9 +1,10 @@ -import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put} from '@nestjs/common'; +import {Controller, Get, Req, Post, Options, Header, Delete, HttpCode, Put, UseInterceptors} from '@nestjs/common'; import {StoreService} from './store.service'; import {Store, StoreRequest} from './store.schema'; import {ApiResponse, ApiTags, ApiParam, ApiBody} from '@nestjs/swagger'; import {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, COLLECTION_STORE} from 'src/consts'; import {Request} from 'express'; +import {LoggingInterceptor} from 'src/logs/logging.interceptor'; const prepareStoreToStoreRequest = ({ key, value, description, service_name, author @@ -16,6 +17,7 @@ const makeApiHeader = (request: Request): string => { return typeof apiHeader === 'string' ? apiHeader : ''; }; +@UseInterceptors(LoggingInterceptor) @Controller(COLLECTION_STORE) @ApiTags(COLLECTION_STORE) export class StoreController { diff --git a/src/store/store.service.ts b/src/store/store.service.ts index 2568790..64cdcfb 100644 --- a/src/store/store.service.ts +++ b/src/store/store.service.ts @@ -71,7 +71,8 @@ export class StoreService { } async findOne(api: string, key: string): Promise { - return this.storeModel(api).findOne({key}) + const searchStore = await this.storeModel(api).findOne({key}); + return searchStore; } async removeOne(api: string, key: string): Promise { diff --git a/store.http b/store.http index 311f313..deafc70 100644 --- a/store.http +++ b/store.http @@ -1,13 +1,12 @@ +### GET http://localhost:4001/store HTTP/1.1 Api-Name: store-service-test ### - -GET http://localhost:4001/store/testApi-4 HTTP/1.1 +GET http://localhost:4001/store/testApi-4345345345 HTTP/1.1 Api-Name: store-service-test ### - POST http://localhost:4001/store HTTP/1.1 content-type: application/json Api-Name: store-service-test @@ -23,13 +22,12 @@ Api-Name: store-service-test } ### Update Request - PUT http://localhost:4001/store HTTP/1.1 content-type: application/json Api-Name: store-service-test { - "key": "testApi-4", + "key": "testApi-4234222", "value": { }, @@ -39,10 +37,17 @@ Api-Name: store-service-test } ### - DELETE http://localhost:4001/store/testApi-433 HTTP/1.1 Api-Name: store-service-test ### +GET http://localhost:4001/logs/client HTTP/1.1 -GET http://localhost:4001/logs HTTP/1.1 +### +GET http://localhost:4001/logs/server HTTP/1.1 + +### +DELETE http://localhost:4001/logs/client HTTP/1.1 + +### +DELETE http://localhost:4001/logs/server HTTP/1.1 \ No newline at end of file