Monitor emails performances
MODIFY
src/notification/email/EMailNotificationTask.ts
src/notification/email/EMailNotificationTask.ts
-import fs from 'fs';
-
-import ejs from 'ejs';
+import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
import { Message, SMTPClient, SMTPError } from 'emailjs';
-import rfc2047 from 'rfc2047';
import BackendError from '../../exception/BackendError';
-import EmailConfiguration from '../../types/configuration/EmailConfiguration';
-import global from '../../types/GlobalType';
-import { ServerAction } from '../../types/Server';
-import Tenant from '../../types/Tenant';
-import User from '../../types/User';
-import { AccountVerificationNotification, AdminAccountVerificationNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EmailNotificationMessage, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserCreatePassword, VerificationEmailNotification } from '../../types/UserNotifications';
import Configuration from '../../utils/Configuration';
import Constants from '../../utils/Constants';
+import EmailConfiguration from '../../types/configuration/EmailConfiguration';
import Logging from '../../utils/Logging';
+import NotificationTask from '../NotificationTask';
+import { ServerAction } from '../../types/Server';
import TemplateManager from '../../utils/TemplateManager';
+import Tenant from '../../types/Tenant';
+import User from '../../types/User';
import Utils from '../../utils/Utils';
-import NotificationTask from '../NotificationTask';
+import ejs from 'ejs';
+import fs from 'fs';
+import global from '../../types/GlobalType';
+import rfc2047 from 'rfc2047';
const MODULE_NAME = 'EMailNotificationTask';
@@ -296,7 +295,10 @@ export default class EMailNotificationTask implements NotificationTask {
}
private async prepareAndSendEmail(templateName: string, data: any, user: User, tenant: Tenant, severity: NotificationSeverity, useSmtpClientBackup = false): Promise<void> {
+ let startTime: number;
+ let emailContent = {} as EmailNotificationMessage;
try {
+ startTime = Logging.traceNotificationStart();
if (!user) {
throw new BackendError({
action: ServerAction.EMAIL_NOTIFICATION,
@@ -398,13 +400,14 @@ export default class EMailNotificationTask implements NotificationTask {
htmlTemp = ejs.render(fs.readFileSync(`${global.appRoot}/assets/server/notification/email/body-html.template`, 'utf8'), emailTemplate);
}
const html = htmlTemp;
- // Send the email
- await this.sendEmail({
+ emailContent = {
to: user.email,
subject: subject,
text: html,
html: html
- }, data, tenant, user, severity, useSmtpClientBackup);
+ };
+ // Send the email
+ await this.sendEmail(emailContent, data, tenant, user, severity, useSmtpClientBackup);
} catch (error) {
await Logging.logError({
tenantID: tenant.id,
@@ -418,6 +421,8 @@ export default class EMailNotificationTask implements NotificationTask {
actionOnUser: user,
detailedMessages: { error: error.stack }
});
+ } finally {
+ await Logging.traceNotificationEnd(tenant, MODULE_NAME, 'prepareAndSendEmail', startTime, templateName, emailContent, user.id);
}
}

MODIFY
src/notification/remote-push-notification/RemotePushNotificationTask.ts
src/notification/remote-push-notification/RemotePushNotificationTask.ts
@@ -435,6 +435,10 @@ export default class RemotePushNotificationTask implements NotificationTask {
private async sendRemotePushNotificationToUser(tenant: Tenant, notificationType: UserNotificationType,
title: string, body: string, user: User, data?: Record<string, string>, severity?: NotificationSeverity): Promise<void> {
+ let startTime: number;
+ let message = {} as admin.messaging.MessagingPayload;
+ try {
+ startTime = Logging.traceNotificationStart();
// Checks
if (!this.initialized) {
return Promise.resolve();
@@ -456,7 +460,7 @@ export default class RemotePushNotificationTask implements NotificationTask {
return Promise.resolve();
}
// Create message
- const message = this.createMessage(tenant, notificationType, title, body, data, severity);
+ message = this.createMessage(tenant, notificationType, title, body, data, severity);
// Send message
admin.messaging().sendToDevice(
user.mobileToken,
@@ -490,6 +494,9 @@ export default class RemotePushNotificationTask implements NotificationTask {
detailedMessages: { error: error.stack, message }
});
});
+ } finally {
+ await Logging.traceNotificationEnd(tenant, MODULE_NAME, 'sendRemotePushNotificationToUser', startTime, notificationType, message, user.id);
+ }
}
private createMessage(tenant: Tenant, notificationType: UserNotificationType, title: string, body: string,

MODIFY
src/types/Performance.ts
src/types/Performance.ts
@@ -34,6 +34,7 @@ export enum PerformanceRecordGroup {
WIT = 'wit',
SAP_SMART_CHARGING = 'sap-smart-charging',
SAP_CONCUR = 'sap-concur',
+ NOTIFICATION = 'notification',
UNKNOWN = 'unknown',
}

MODIFY
src/utils/Logging.ts
src/utils/Logging.ts
@@ -68,7 +68,7 @@ export default class Logging {
}
}
if (executionDurationMillis > Constants.PERF_MAX_RESPONSE_TIME_MILLIS) {
- const error = new Error(`Execution must be < ${Constants.PERF_MAX_RESPONSE_TIME_MILLIS} ms, got ${executionDurationMillis} ms`);
+ const error = new Error(`Execution must be < ${Constants.PERF_MAX_RESPONSE_TIME_MILLIS} ms`);
await Logging.logWarning({
tenantID: tenant.id,
action: ServerAction.PERFORMANCES,
@@ -96,6 +96,48 @@ export default class Logging {
);
}
+ public static traceNotificationStart(): number {
+ return Date.now();
+ }
+
+ public static async traceNotificationEnd(tenant: Tenant, module: string, method: string, timeStartMillis: number,
+ templateName: string, data: any, userID: string): Promise<void> {
+ // Compute duration if provided
+ const executionDurationMillis = new Date().getTime() - timeStartMillis;
+ const sizeOfRequestDataKB = Utils.truncTo(Utils.createDecimal(
+ sizeof(data)).div(1024).toNumber(), 2);
+ const message = `${module}.${method} - ${templateName} - ${executionDurationMillis.toString()} ms - Data ${sizeOfRequestDataKB} KB`;
+ Utils.isDevelopmentEnv() && Logging.logConsoleInfo(message);
+ if (executionDurationMillis > Constants.PERF_MAX_RESPONSE_TIME_MILLIS) {
+ const error = new Error(`Execution must be < ${Constants.PERF_MAX_RESPONSE_TIME_MILLIS} ms`);
+ await Logging.logWarning({
+ tenantID: tenant.id,
+ action: ServerAction.PERFORMANCES,
+ module, method,
+ message: `${message}: ${error.message}`,
+ detailedMessages: { error: error.stack }
+ });
+ if (Utils.isDevelopmentEnv()) {
+ Logging.logConsoleWarning('====================================');
+ Logging.logConsoleWarning(`Tenant ID '${tenant.id}'`);
+ Logging.logConsoleWarning(error.stack);
+ Logging.logConsoleWarning(message);
+ Logging.logConsoleWarning('====================================');
+ }
+ }
+ await PerformanceStorage.savePerformanceRecord(
+ Utils.buildPerformanceRecord({
+ tenantSubdomain: tenant.subdomain,
+ group: PerformanceRecordGroup.NOTIFICATION,
+ durationMs: executionDurationMillis,
+ reqSizeKb: sizeOfRequestDataKB,
+ resSizeKb: 0,
+ action: `${module}.${method}.${templateName}`,
+ userID
+ })
+ );
+ }
+
public static async logDebug(log: Log): Promise<string> {
log.level = LogLevel.DEBUG;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -642,7 +684,7 @@ export default class Logging {
const message = `${direction} OCPP Request '${action}' on '${chargingStationID}' has been processed ${executionDurationMillis ? 'in ' + executionDurationMillis.toString() + ' ms' : ''} - Res ${(sizeOfResponseDataKB > 0) ? sizeOfResponseDataKB : '?'} KB`;
Utils.isDevelopmentEnv() && Logging.logConsoleInfo(message);
if (executionDurationMillis > Constants.PERF_MAX_RESPONSE_TIME_MILLIS) {
- const error = new Error(`Execution must be < ${Constants.PERF_MAX_RESPONSE_TIME_MILLIS} ms, got ${executionDurationMillis} ms`);
+ const error = new Error(`Execution must be < ${Constants.PERF_MAX_RESPONSE_TIME_MILLIS} ms`);
await Logging.logWarning({
tenantID: tenant?.id,
action: ServerAction.PERFORMANCES,

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -1479,9 +1479,9 @@ export default class Utils {
}
public static buildPerformanceRecord(params: {
- tenantSubdomain?: string; durationMs?: number; resSizeKb?: number;
- reqSizeKb?: number; action: ServerAction|string; group?: PerformanceRecordGroup;
- httpUrl?: string; httpMethod?: string; httpResponseCode?: number; chargingStationID?: string,
+ tenantSubdomain?: string; durationMs?: number; resSizeKb?: number; reqSizeKb?: number;
+ action: ServerAction|string; group?: PerformanceRecordGroup; httpUrl?: string;
+ httpMethod?: string; httpResponseCode?: number; chargingStationID?: string, userID?: string
}): PerformanceRecord {
const performanceRecord: PerformanceRecord = {
tenantSubdomain: params.tenantSubdomain,
@@ -1511,6 +1511,9 @@ export default class Utils {
if (params.httpResponseCode) {
performanceRecord.httpResponseCode = params.httpResponseCode;
}
+ if (params.userID) {
+ performanceRecord.userID = params.userID;
+ }
if (global.serverType) {
performanceRecord.server = global.serverType;
}

Monitor emails performances