Optimized logs
MODIFY
src/server/rest/v1/service/LoggingService.ts
src/server/rest/v1/service/LoggingService.ts
@@ -23,16 +23,11 @@ const MODULE_NAME = 'LoggingService';
export default class LoggingService {
public static async handleGetLogs(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Get logs
- const loggings = await LoggingService.getLogs(req);
- // Filter
- LoggingSecurity.filterLogsResponse(loggings, req.user);
- // Return
- res.json(loggings);
+ res.json(await LoggingService.getLogs(req));
next();
}
public static async handleExportLogs(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // Export
await UtilsService.exportToCSV(req, res, 'exported-logs.csv',
LoggingService.getLogs.bind(this), LoggingService.convertToCSV.bind(this));
}
@@ -41,24 +36,21 @@ export default class LoggingService {
// Filter
const filteredRequest = LoggingSecurity.filterLogRequest(req.query);
// Get logs
- const logging = await LoggingStorage.getLog(req.user.tenantID, filteredRequest.ID);
+ const logging = await LoggingStorage.getLog(req.user.tenantID, filteredRequest.ID, [
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'process', 'action', 'message',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages', 'detailedMessages'
+ ]);
// Check auth
if (!Authorizations.canReadLog(req.user)) {
throw new AppAuthError({
errorCode: HTTPAuthError.ERROR,
user: req.user,
- action: Action.READ,
- entity: Entity.LOGGING,
- module: MODULE_NAME,
- method: 'handleGetLog'
+ action: Action.READ, entity: Entity.LOGGING,
+ module: MODULE_NAME, method: 'handleGetLog'
});
}
// Return
- res.json(
- LoggingSecurity.filterLogResponse(
- logging, req.user, true
- )
- );
+ res.json(logging);
next();
}
@@ -137,7 +129,8 @@ export default class LoggingService {
sort: filteredRequest.Sort,
onlyRecordCount: filteredRequest.OnlyRecordCount
}, [
- 'id', 'level', 'timestamp', 'type', 'source', 'host', 'process', 'userFullName', 'action', 'message', 'module', 'method', 'user', 'actionOnUser', 'hasDetailedMessages'
+ 'id', 'level', 'timestamp', 'type', 'source', 'host', 'process', 'action', 'message',
+ 'user.name', 'user.firstName', 'actionOnUser.name', 'actionOnUser.firstName', 'hasDetailedMessages'
]);
return loggings;
}

MODIFY
src/server/rest/v1/service/security/LoggingSecurity.ts
src/server/rest/v1/service/security/LoggingSecurity.ts
-import Authorizations from '../../../../../authorization/Authorizations';
-import { DataResult } from '../../../../../types/DataResult';
import HttpByIDRequest from '../../../../../types/requests/HttpByIDRequest';
import { HttpLogsRequest } from '../../../../../types/requests/HttpLoggingRequest';
-import { Log } from '../../../../../types/Log';
-import UserToken from '../../../../../types/UserToken';
-import Utils from '../../../../../utils/Utils';
import UtilsSecurity from './UtilsSecurity';
import sanitize from 'mongo-sanitize';
@@ -32,57 +27,5 @@ export default class LoggingSecurity {
ID: sanitize(request.ID)
};
}
-
- static filterLogResponse(logging: Log, loggedUser: UserToken, withDetailedMessage = false): Log {
- const filteredLogging: Log = {} as Log;
- if (!logging) {
- return null;
- }
- if (!Authorizations.canReadLog(loggedUser)) {
- return null;
- }
- filteredLogging.id = logging.id;
- filteredLogging.level = logging.level;
- filteredLogging.timestamp = logging.timestamp;
- filteredLogging.type = logging.type;
- filteredLogging.source = logging.source;
- filteredLogging.host = logging.host;
- filteredLogging.process = logging.process;
- filteredLogging.userFullName = logging.userFullName;
- filteredLogging.action = logging.action;
- filteredLogging.message = logging.message;
- filteredLogging.module = logging.module;
- filteredLogging.method = logging.method;
- filteredLogging.hasDetailedMessages = logging.hasDetailedMessages;
- if (withDetailedMessage) {
- filteredLogging.detailedMessages = logging.detailedMessages;
- }
- if (logging.user && typeof logging.user === 'object') {
- // Build user
- filteredLogging.user = Utils.buildUserFullName(logging.user, false);
- }
- if (logging.actionOnUser && typeof logging.actionOnUser === 'object') {
- // Build user
- filteredLogging.actionOnUser = Utils.buildUserFullName(logging.actionOnUser, false);
- }
- return filteredLogging;
- }
-
- static filterLogsResponse(logs: DataResult<Log>, loggedUser: UserToken): void {
- const filteredLogs = [];
- if (!logs.result) {
- return null;
- }
- for (const logging of logs.result) {
- // Filter
- const filteredLogging = LoggingSecurity.filterLogResponse(logging, loggedUser);
- // Ok?
- if (filteredLogging) {
- // Add
- filteredLogs.push(filteredLogging);
- }
- }
- logs.result = filteredLogs;
- }
}

MODIFY
src/storage/mongodb/LoggingStorage.ts
src/storage/mongodb/LoggingStorage.ts
@@ -84,13 +84,14 @@ export default class LoggingStorage {
}
}
- public static async getLog(tenantID: string, id: string = Constants.UNKNOWN_OBJECT_ID): Promise<Log> {
+ public static async getLog(tenantID: string, id: string = Constants.UNKNOWN_OBJECT_ID, projectFields: string[]): Promise<Log> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getLog');
// Query single Site
const logsMDB = await LoggingStorage.getLogs(tenantID,
- { logID: id },
- Constants.DB_PARAMS_SINGLE_RECORD);
+ { logIDs: [id] },
+ Constants.DB_PARAMS_SINGLE_RECORD,
+ projectFields);
// Debug
Logging.traceEnd(tenantID, MODULE_NAME, 'getLog', uniqueTimerID, { id });
return logsMDB.count === 1 ? logsMDB.result[0] : null;
@@ -98,8 +99,8 @@ export default class LoggingStorage {
public static async getLogs(tenantID: string, params: {
startDateTime?: Date; endDateTime?: Date; levels?: string[]; sources?: string[]; type?: string; actions?: string[];
- hosts?: string[]; userIDs?: string[]; search?: string; logID?: string;
- } = {}, dbParams: DbParams, projectFields?: string[]): Promise<DataResult<Log>> {
+ hosts?: string[]; userIDs?: string[]; search?: string; logIDs?: string[];
+ } = {}, dbParams: DbParams, projectFields: string[]): Promise<DataResult<Log>> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'getLogs');
// Check Tenant
@@ -145,33 +146,33 @@ export default class LoggingStorage {
filters.host = { $in: params.hosts };
}
// Filter on users
- if (params.userIDs && params.userIDs.length > 0) {
+ if (!Utils.isEmptyArray(params.userIDs)) {
filters.$or = [
- { userID: { $in: params.userIDs.map((user) => Utils.convertToObjectID(user)) } },
- { actionOnUserID: { $in: params.userIDs.map((user) => Utils.convertToObjectID(user)) } }
+ { userID: { $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID)) } },
+ { actionOnUserID: { $in: params.userIDs.map((userID) => Utils.convertToObjectID(userID)) } }
];
}
+ // Log ID
+ if (!Utils.isEmptyArray(params.logIDs)) {
+ filters._id = {
+ $in: params.logIDs.map((logID) => Utils.convertToObjectID(logID))
+ };
+ }
// Search
- if (params.logID) {
- filters._id = Utils.convertToObjectID(params.logID);
- } else if (params.search) {
- // Set
+ if (params.search) {
const searchArray = [
{ 'source': { $regex: params.search, $options: 'i' } },
{ 'host': { $regex: params.search, $options: 'i' } },
{ 'message': { $regex: params.search, $options: 'i' } },
- { 'detailedMessages': { $regex: params.search, $options: 'i' } },
{ 'action': { $regex: params.search, $options: 'i' } }
];
// Already exists?
if (filters.$or) {
- // Add them all
filters.$and = [
{ $or: [...filters.$or] },
{ $or: [...searchArray] },
];
} else {
- // Only one
filters.$or = searchArray;
}
}
@@ -198,9 +199,7 @@ export default class LoggingStorage {
.toArray();
// Check if only the total count is requested
if (dbParams.onlyRecordCount) {
- // Debug
Logging.traceEnd(tenantID, MODULE_NAME, 'getLogs', uniqueTimerID);
- // Return only the count
return {
count: (loggingsCountMDB.length > 0 ? loggingsCountMDB[0].count : 0),
result: []
@@ -232,7 +231,9 @@ export default class LoggingStorage {
foreignField: '_id',
oneToOneCardinality: true,
oneToOneCardinalityNotNull: false
- });
+ }, [
+ { $project: { name: 1, firstName: 1 } }
+ ]);
DatabaseUtils.pushUserLookupInAggregation({
tenantID,
aggregation: aggregation,
@@ -241,21 +242,21 @@ export default class LoggingStorage {
foreignField: '_id',
oneToOneCardinality: true,
oneToOneCardinalityNotNull: false
- });
+ }, [
+ { $project: { name: 1, firstName: 1 } }
+ ]);
// Check if it has detailed messages
- aggregation.push(
- {
+ aggregation.push({
$addFields: {
'hasDetailedMessages': { $gt: ['$detailedMessages', null] }
}
- }
- );
+ });
// Change ID
DatabaseUtils.pushRenameDatabaseID(aggregation);
// Project
DatabaseUtils.projectFields(aggregation, projectFields);
// Read DB
- const loggingsMDB = await global.database.getCollection<any>(tenantID, 'logs')
+ const loggingsMDB = await global.database.getCollection<Log>(tenantID, 'logs')
.aggregate(aggregation, { allowDiskUse: true })
.toArray();
// Debug

MODIFY
src/types/Log.ts
src/types/Log.ts
import { ServerAction } from './Server';
import User from './User';
-import UserToken from './UserToken';
export interface Log {
tenantID: string;
@@ -14,12 +13,11 @@ export interface Log {
timestamp?: Date;
action?: ServerAction;
type?: LogType;
- message: string|any;
- user?: User|UserToken|string;
- userFullName?: string;
- actionOnUser?: User|UserToken|string;
+ message: string;
+ user?: User;
+ actionOnUser?: User;
hasDetailedMessages?: boolean;
- detailedMessages?: string|any;
+ detailedMessages?: any;
}
export enum LogLevel {

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -10,7 +10,7 @@ export default class Constants {
public static readonly AXIOS_TIMEOUT = 60000;
public static readonly DB_RECORD_COUNT_DEFAULT = 100;
- public static readonly DB_RECORD_COUNT_CEIL = 2000;
+ public static readonly DB_RECORD_COUNT_CEIL = 500;
public static readonly DB_RECORD_COUNT_NO_LIMIT = Number.MAX_SAFE_INTEGER;
public static readonly DB_UNDETERMINED_NBR_OF_RECORDS = -1;

Optimized logs