HM-57. Собираются два типа данные: серверные ошибки и пара запрос-ответ пользователю апи. Серверные ошибки расширены доп полями в том числе дата ошибки. Появилась возможность очищать логи

This commit is contained in:
vigdorov
2020-07-19 14:40:07 +03:00
parent 8c003a15d0
commit cdda74475e
12 changed files with 259 additions and 47 deletions

14
package-lock.json generated
View File

@ -1962,6 +1962,15 @@
"integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==", "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==",
"dev": true "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": { "@types/mongodb": {
"version": "3.5.25", "version": "3.5.25",
"resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.25.tgz", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.5.25.tgz",
@ -9199,6 +9208,11 @@
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
}, },
"moment": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
},
"mongodb": { "mongodb": {
"version": "3.5.9", "version": "3.5.9",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.9.tgz",

View File

@ -26,6 +26,7 @@
"@nestjs/mongoose": "^7.0.1", "@nestjs/mongoose": "^7.0.1",
"@nestjs/platform-express": "^7.0.0", "@nestjs/platform-express": "^7.0.0",
"@nestjs/swagger": "^4.5.12", "@nestjs/swagger": "^4.5.12",
"moment": "^2.27.0",
"mongoose": "^5.9.21", "mongoose": "^5.9.21",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@ -38,6 +39,7 @@
"@nestjs/testing": "^7.0.0", "@nestjs/testing": "^7.0.0",
"@types/express": "^4.17.3", "@types/express": "^4.17.3",
"@types/jest": "25.2.3", "@types/jest": "25.2.3",
"@types/moment": "^2.13.0",
"@types/mongoose": "^5.7.29", "@types/mongoose": "^5.7.29",
"@types/node": "^13.9.1", "@types/node": "^13.9.1",
"@types/supertest": "^2.0.8", "@types/supertest": "^2.0.8",

View File

@ -1,12 +1,13 @@
import {Module} from '@nestjs/common'; import {Module, NestModule, MiddlewareConsumer} from '@nestjs/common';
import {MongooseModule} from '@nestjs/mongoose'; 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 {StoreService} from './store/store.service';
import {Store, StoreSchema} from './store/store.schema'; import {Store, StoreSchema} from './store/store.schema';
import {StoreController} from './store/store.controller'; import {StoreController} from './store/store.controller';
import {Log, LogSchema} from './logs/logs.schema';
import {LogsService} from './logs/logs.service'; import {LogsService} from './logs/logs.service';
import {LogsController} from './logs/logs.controller'; import {LogsController} from './logs/logs.controller';
import {ClientLog, ClientLogSchema, ServerLog, ServerLogSchema} from './logs/logs.schema';
@Module({ @Module({
imports: [ imports: [
@ -26,7 +27,8 @@ import {LogsController} from './logs/logs.controller';
{name: Store.name, schema: StoreSchema}, {name: Store.name, schema: StoreSchema},
], DB_TEST_NAME), ], DB_TEST_NAME),
MongooseModule.forFeature([ MongooseModule.forFeature([
{name: Log.name, schema: LogSchema}, {name: ClientLog.name, schema: ClientLogSchema},
{name: ServerLog.name, schema: ServerLogSchema},
], DB_LOGGER), ], DB_LOGGER),
], ],
controllers: [ controllers: [
@ -38,4 +40,4 @@ import {LogsController} from './logs/logs.controller';
LogsService, LogsService,
] ]
}) })
export class AppModule {} export class AppModule {}

View File

@ -4,6 +4,10 @@ export const DB_LOGGER = 'logger';
export const MONGO_URL = 'mongodb://localhost:27017'; export const MONGO_URL = 'mongodb://localhost:27017';
export const COLLECTION_STORE = 'store'; export const COLLECTION_STORE = 'store';
export const COLLECTION_LOGS = 'logs'; 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_ORIGIN_ALL: [string, string] = ['Access-Control-Allow-Origin', '*'];
export const ALLOW_CREDENTIALS: [string, string] = ['Access-Control-Allow-Credentials', 'true']; export const ALLOW_CREDENTIALS: [string, string] = ['Access-Control-Allow-Credentials', 'true'];
export const CONTENT_LENGTH: [string, string] = ['Content-Length', '0']; export const CONTENT_LENGTH: [string, string] = ['Content-Length', '0'];

View File

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

View File

@ -1,8 +1,8 @@
import {Controller, Get, Header} from '@nestjs/common'; import {Controller, Get, Header, Req, NotFoundException, Delete, Options, HttpCode} from '@nestjs/common';
import {ApiTags, ApiResponse} from '@nestjs/swagger'; import {ApiTags, ApiResponse, ApiParam} from '@nestjs/swagger';
import {LogsService} from './logs.service'; import {LogsService} from './logs.service';
import {ALLOW_ORIGIN_ALL, COLLECTION_LOGS} from 'src/consts'; import {ALLOW_ORIGIN_ALL, COLLECTION_LOGS, LOG_TYPE, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS} from 'src/consts';
import {Log, LogRequest} from './logs.schema'; import {ClienLogRequest, ClientLog, ServerLog} from './logs.schema';
@Controller(COLLECTION_LOGS) @Controller(COLLECTION_LOGS)
@ApiTags(COLLECTION_LOGS) @ApiTags(COLLECTION_LOGS)
@ -11,15 +11,48 @@ export class LogsController {
private readonly logsService: LogsService private readonly logsService: LogsService
) {} ) {}
@Get() @Get('/client')
@Header(...ALLOW_ORIGIN_ALL) @Header(...ALLOW_ORIGIN_ALL)
@ApiResponse({ @ApiResponse({
status: 200, status: 200,
description: 'Возвращает список всех логов', description: 'Возвращает список всех логов',
type: [LogRequest], type: [ClienLogRequest]
}) })
async findAll(): Promise<Log[]> { async findAllClientLogs(): Promise<ClientLog[]> {
const logsList = await this.logsService.findAll(); return await this.logsService.findAllClientLogs();
return logsList; }
@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<ServerLog[]> {
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<string> {
return '';
} }
} }

View File

@ -2,18 +2,70 @@ import { Document } from 'mongoose';
import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose'; import {Prop, Schema, SchemaFactory} from '@nestjs/mongoose';
import {ApiProperty} from '@nestjs/swagger'; 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() @ApiProperty()
message: string; message: string;
@ApiProperty()
trace: string;
@ApiProperty()
date: string;
} }
@Schema() @Schema()
export class Log extends Document { export class ServerLog extends Document {
@Prop({ @Prop({required: true, type: String})
required: true, type: string;
type: String,
}) @Prop({required: true, type: String})
message: 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);

View File

@ -1,8 +1,8 @@
import {Injectable} from '@nestjs/common'; import {Injectable} from '@nestjs/common';
import {InjectConnection} from '@nestjs/mongoose'; import {InjectConnection} from '@nestjs/mongoose';
import {DB_LOGGER, COLLECTION_LOGS} from 'src/consts'; import {DB_LOGGER, LOG_TYPE} from 'src/consts';
import {Connection, Model} from 'mongoose'; import {Connection} from 'mongoose';
import {LogSchema, Log} from './logs.schema'; import {ClientLog, ServerLog, ClientLogSchema, ServerLogSchema} from './logs.schema';
@Injectable() @Injectable()
export class LogsService { export class LogsService {
@ -10,11 +10,27 @@ export class LogsService {
@InjectConnection(DB_LOGGER) private dbConnection: Connection, @InjectConnection(DB_LOGGER) private dbConnection: Connection,
) {} ) {}
get logModel(): Model<Log> { async findAllClientLogs(): Promise<ClientLog[]> {
return this.dbConnection.model<Log>(COLLECTION_LOGS, LogSchema); return this.dbConnection
.model<ClientLog>(LOG_TYPE.CLIENT, ClientLogSchema)
.find()
.exec();
} }
async findAll(): Promise<Log[]> { async findAllServerLogs(): Promise<ServerLog[]> {
return this.logModel.find().exec(); return this.dbConnection
.model<ServerLog>(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 [];
} }
} }

View File

@ -1,37 +1,55 @@
import * as mongoose from 'mongoose'; import * as mongoose from 'mongoose';
import * as moment from 'moment';
import {Logger as DefaultLogger} from '@nestjs/common'; 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}); mongoose.connect(`${MONGO_URL}/logger`, {useNewUrlParser: true});
const ERRORS = {
LOG: 'LOG',
ERROR: 'ERROR',
WARN: 'WARN',
DEBUG: 'DEBUG',
VERBOSE: 'VERBODE',
};
const errorSchema = new mongoose.Schema({ const errorSchema = new mongoose.Schema({
type: String,
message: 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 { export class Logger extends DefaultLogger {
log(message: string): void { log(message: string): void {
super.log(message); super.log(message);
// saveError(ERRORS.LOG, message);
} }
error(message: string, trace: string): void { error(message: string, trace: string): void {
super.error(message, trace); super.error(message, trace);
const error = new ErrorModel({ saveError(ERRORS.ERROR, message, trace);
message: `ERROR: message = ${message}, trace = ${trace}`
});
error.save();
} }
warn(message: string): void { warn(message: string): void {
super.warn(message); super.warn(message);
const error = new ErrorModel({ saveError(ERRORS.WARN, message);
message: `WARN: message = ${message}`
});
error.save();
} }
debug(message: string): void { debug(message: string): void {
super.debug(message); super.debug(message);
// saveError(ERRORS.DEBUG, message);
} }
verbose(message: string): void { verbose(message: string): void {
super.verbose(message); super.verbose(message);
// saveError(ERRORS.VERBOSE, message);
} }
} }

View File

@ -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 {StoreService} from './store.service';
import {Store, StoreRequest} from './store.schema'; import {Store, StoreRequest} from './store.schema';
import {ApiResponse, ApiTags, ApiParam, ApiBody} from '@nestjs/swagger'; 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 {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, COLLECTION_STORE} from 'src/consts';
import {Request} from 'express'; import {Request} from 'express';
import {LoggingInterceptor} from 'src/logs/logging.interceptor';
const prepareStoreToStoreRequest = ({ const prepareStoreToStoreRequest = ({
key, value, description, service_name, author key, value, description, service_name, author
@ -16,6 +17,7 @@ const makeApiHeader = (request: Request): string => {
return typeof apiHeader === 'string' ? apiHeader : ''; return typeof apiHeader === 'string' ? apiHeader : '';
}; };
@UseInterceptors(LoggingInterceptor)
@Controller(COLLECTION_STORE) @Controller(COLLECTION_STORE)
@ApiTags(COLLECTION_STORE) @ApiTags(COLLECTION_STORE)
export class StoreController { export class StoreController {

View File

@ -71,7 +71,8 @@ export class StoreService {
} }
async findOne(api: string, key: string): Promise<Store> { async findOne(api: string, key: string): Promise<Store> {
return this.storeModel(api).findOne({key}) const searchStore = await this.storeModel(api).findOne({key});
return searchStore;
} }
async removeOne(api: string, key: string): Promise<Store> { async removeOne(api: string, key: string): Promise<Store> {

View File

@ -1,13 +1,12 @@
###
GET http://localhost:4001/store HTTP/1.1 GET http://localhost:4001/store HTTP/1.1
Api-Name: store-service-test Api-Name: store-service-test
### ###
GET http://localhost:4001/store/testApi-4345345345 HTTP/1.1
GET http://localhost:4001/store/testApi-4 HTTP/1.1
Api-Name: store-service-test Api-Name: store-service-test
### ###
POST http://localhost:4001/store HTTP/1.1 POST http://localhost:4001/store HTTP/1.1
content-type: application/json content-type: application/json
Api-Name: store-service-test Api-Name: store-service-test
@ -23,13 +22,12 @@ Api-Name: store-service-test
} }
### Update Request ### Update Request
PUT http://localhost:4001/store HTTP/1.1 PUT http://localhost:4001/store HTTP/1.1
content-type: application/json content-type: application/json
Api-Name: store-service-test Api-Name: store-service-test
{ {
"key": "testApi-4", "key": "testApi-4234222",
"value": { "value": {
}, },
@ -39,10 +37,17 @@ Api-Name: store-service-test
} }
### ###
DELETE http://localhost:4001/store/testApi-433 HTTP/1.1 DELETE http://localhost:4001/store/testApi-433 HTTP/1.1
Api-Name: store-service-test 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