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==",
"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",

View File

@ -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",

View File

@ -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: [

View File

@ -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'];

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 {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<Log[]> {
const logsList = await this.logsService.findAll();
return logsList;
async findAllClientLogs(): Promise<ClientLog[]> {
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<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 {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);

View File

@ -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<Log> {
return this.dbConnection.model<Log>(COLLECTION_LOGS, LogSchema);
async findAllClientLogs(): Promise<ClientLog[]> {
return this.dbConnection
.model<ClientLog>(LOG_TYPE.CLIENT, ClientLogSchema)
.find()
.exec();
}
async findAll(): Promise<Log[]> {
return this.logModel.find().exec();
async findAllServerLogs(): Promise<ServerLog[]> {
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 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);
}
}

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

View File

@ -71,7 +71,8 @@ export class StoreService {
}
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> {

View File

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