Skip to content

Commit 93fd778

Browse files
#2180 - Additional log improvement (#2326)
### Log improvements - The date and time are now prepended in the job logger. - Added a visual hierarchy to identify better the child logs. - Changed the hierarchy organization to allow child process to be chronologically added, as seen in the image below (before the green ones, for instance, would be displayed altogether). Created a new method to inspect the summary message to indicate if there is something that needs attention in the logs. Scheduler Enqueuer Query Change The query was not filtering by application status and I thought that it would be better to ensure that only `Submitted` and `Completed` applications would be considered to have a workflow queued. Refactor Adjusted the logs for the `assessment-workflow-enqueuer` and `start-application-assessment` to exclusively use the new `ProcessSummary` class instead of the `QueueProcessSummary`. The suggestion is intended to be a POC to have the `QueueProcessSummary` marked as deprecated.
1 parent b2d13ae commit 93fd778

File tree

9 files changed

+237
-100
lines changed

9 files changed

+237
-100
lines changed

sources/packages/backend/apps/queue-consumers/src/processors/assessment/_tests_/start-application-assessment.processor.e2e-spec.ts

+27-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
createTestingAppModule,
1010
describeProcessorRootTest,
1111
} from "../../../../test/helpers";
12+
import { LogLevels, ProcessSummary } from "@sims/utilities/logger";
1213

1314
describe(
1415
describeProcessorRootTest(QueueNames.StartApplicationAssessment),
@@ -29,7 +30,7 @@ describe(
2930
jest.resetAllMocks();
3031
});
3132

32-
it("Should throw an error when the workflow createProcessInstance method throws an error.", async () => {
33+
it("Should log an error when the workflow createProcessInstance method throws an unexpected error.", async () => {
3334
// Arrange
3435
const dummyException = new Error("Dummy error");
3536
const job = createMock<Job<StartAssessmentQueueInDTO>>({
@@ -41,9 +42,32 @@ describe(
4142
zbClientMock.createProcessInstance = jest.fn().mockImplementation(() => {
4243
throw dummyException;
4344
});
45+
// Capture the processSummary for later assertion.
46+
let processSummary: ProcessSummary;
47+
processor.logger.logProcessSummary = jest.fn((providedProcessSummary) => {
48+
processSummary = providedProcessSummary;
49+
});
50+
51+
// Act
52+
const result = await processor.startAssessment(job);
4453

45-
// Act and Assert.
46-
await expect(processor.startAssessment(job)).rejects.toBe(dummyException);
54+
// Assert.
55+
56+
// Assert process summary was provided.
57+
expect(processSummary).toBeDefined();
58+
// Assert expected result message.
59+
expect(result).toBe(
60+
"Unexpected error while executing the job, check logs for further details.",
61+
);
62+
// Assert error message was added to the logs.
63+
const hasExpectedLogErrorMessage = processSummary
64+
.flattenLogs()
65+
.some(
66+
(log) =>
67+
log.level === LogLevels.Error &&
68+
log.message.includes("Dummy error"),
69+
);
70+
expect(hasExpectedLogErrorMessage).toBeTruthy();
4771
});
4872

4973
it("Should invoke the workflow create instance method with the received job parameters.", async () => {

sources/packages/backend/apps/queue-consumers/src/processors/assessment/start-application-assessment.processor.ts

+42-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { StartAssessmentQueueInDTO } from "@sims/services/queue";
44
import { WorkflowClientService } from "@sims/services";
55
import { QueueNames } from "@sims/utilities";
66
import { StudentAssessmentService } from "../../services";
7+
import {
8+
InjectLogger,
9+
LoggerService,
10+
ProcessSummary,
11+
} from "@sims/utilities/logger";
12+
import { logProcessSummaryToJobLogger } from "../../utilities";
713

814
/**
915
* Process messages sent to start assessment queue.
@@ -15,20 +21,43 @@ export class StartApplicationAssessmentProcessor {
1521
private readonly studentAssessmentService: StudentAssessmentService,
1622
) {}
1723

24+
/**
25+
* Call Camunda to start the workflow.
26+
* @param job job details with assessment if and optional workflow name.
27+
* @returns process summary.
28+
*/
1829
@Process()
19-
async startAssessment(job: Job<StartAssessmentQueueInDTO>) {
20-
let workflowName = job.data.workflowName;
21-
if (!workflowName) {
22-
const applicationData =
23-
await this.studentAssessmentService.getApplicationDynamicData(
24-
job.data.assessmentId,
25-
);
26-
workflowName = applicationData.workflowName;
30+
async startAssessment(job: Job<StartAssessmentQueueInDTO>): Promise<string> {
31+
const processSummary = new ProcessSummary();
32+
try {
33+
processSummary.info("Processing the start assessment job.");
34+
let workflowName = job.data.workflowName;
35+
if (!workflowName) {
36+
const applicationData =
37+
await this.studentAssessmentService.getApplicationDynamicData(
38+
job.data.assessmentId,
39+
);
40+
workflowName = applicationData.workflowName;
41+
}
42+
processSummary.info(
43+
`Starting assessment id ${job.data.assessmentId} using workflow ${workflowName}.`,
44+
);
45+
await this.workflowClientService.startApplicationAssessment(
46+
workflowName,
47+
job.data.assessmentId,
48+
);
49+
const successMessage = "Workflow call executed with success.";
50+
return successMessage;
51+
} catch (error) {
52+
processSummary.error("Unexpected error while executing the job.", error);
53+
return "Unexpected error while executing the job, check logs for further details.";
54+
} finally {
55+
this.logger.logProcessSummary(processSummary);
56+
await logProcessSummaryToJobLogger(processSummary, job);
57+
// Todo: add queue history cleaning logic as in schedulers.
2758
}
28-
await this.workflowClientService.startApplicationAssessment(
29-
workflowName,
30-
job.data.assessmentId,
31-
);
32-
// Todo: add queue history cleaning logic as in schedulers.
3359
}
60+
61+
@InjectLogger()
62+
logger: LoggerService;
3463
}

sources/packages/backend/apps/queue-consumers/src/processors/models/processors.models.ts

+1-22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { LoggerService } from "@nestjs/common";
2-
import { ProcessSummary } from "@sims/utilities/logger";
32

43
export interface QueueProcessSummaryResult {
54
/**
@@ -19,6 +18,7 @@ export interface QueueProcessSummaryResult {
1918
/**
2019
* Allow the logs aggregation for a process summary and optionally
2120
* allow the same logs to be saved to different outputs.
21+
* @deprecated please use ProcessSummary from sims/utilities/logger.
2222
*/
2323
export class QueueProcessSummary {
2424
private summary: string[] = [];
@@ -82,27 +82,6 @@ export class QueueProcessSummary {
8282
errors: this.errors.length ? this.errors : undefined,
8383
};
8484
}
85-
86-
/**
87-
* Writes all the log entries.
88-
* @param processSummary process summary logs.
89-
* @param logger optionally provides a job logger
90-
* in case one is not present.
91-
*/
92-
async logProcessSummaryToJobLogger(
93-
processSummary: ProcessSummary,
94-
logger?: JobLogger,
95-
): Promise<void> {
96-
const jobLogger = logger ?? this.loggers?.jobLogger;
97-
if (!jobLogger) {
98-
throw new Error("No job logger was provided.");
99-
}
100-
for (const logEntry of processSummary.flattenLogs()) {
101-
await jobLogger.log(
102-
`${logEntry.level.toUpperCase()}: ${logEntry.message}`,
103-
);
104-
}
105-
}
10685
}
10786

10887
/**

sources/packages/backend/apps/queue-consumers/src/processors/schedulers/workflow/assessment-workflow-enqueuer.scheduler.ts

+23-29
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { InjectQueue, Process, Processor } from "@nestjs/bull";
22
import { Job, Queue } from "bull";
33
import { BaseScheduler } from "../base-scheduler";
4-
import { QueueNames, parseJSONError } from "@sims/utilities";
4+
import { QueueNames } from "@sims/utilities";
55
import { QueueService } from "@sims/services/queue";
6-
import {
7-
QueueProcessSummary,
8-
QueueProcessSummaryResult,
9-
} from "../../models/processors.models";
106
import { WorkflowEnqueuerService } from "../../../services";
117
import {
128
InjectLogger,
139
LoggerService,
1410
ProcessSummary,
1511
} from "@sims/utilities/logger";
12+
import {
13+
getSuccessMessageWithAttentionCheck,
14+
logProcessSummaryToJobLogger,
15+
} from "../../../utilities";
1616

1717
/**
1818
* Search for assessments that have some pending operation, for instance,
@@ -35,39 +35,33 @@ export class AssessmentWorkflowEnqueuerScheduler extends BaseScheduler<void> {
3535
* @returns processing result.
3636
*/
3737
@Process()
38-
async enqueueAssessmentOperations(
39-
job: Job<void>,
40-
): Promise<QueueProcessSummaryResult> {
41-
const summary = new QueueProcessSummary({
42-
appLogger: this.logger,
43-
jobLogger: job,
44-
});
45-
await summary.info(
46-
"Checking application assessments to be queued for start.",
47-
);
48-
// Process summary to be populated by the enqueueStartAssessmentWorkflows.
49-
// In case an unexpected error happen the finally block will still be able to
50-
// output the partial information captured by the processSummary.
38+
async enqueueAssessmentOperations(job: Job<void>): Promise<string[]> {
5139
const processSummary = new ProcessSummary();
5240
try {
41+
processSummary.info(
42+
"Checking application assessments to be queued for start.",
43+
);
44+
// Process summary to be populated by the enqueueStartAssessmentWorkflows.
45+
// In case an unexpected error happen the finally block will still be able to
46+
// output the partial information captured by the processSummary.
47+
const serviceProcessSummary = new ProcessSummary();
48+
processSummary.children(serviceProcessSummary);
5349
await this.workflowEnqueuerService.enqueueStartAssessmentWorkflows(
54-
processSummary,
50+
serviceProcessSummary,
5551
);
56-
await summary.info(
57-
"All application assessments queued with success, check logs for further details.",
52+
return getSuccessMessageWithAttentionCheck(
53+
"Process finalized with success.",
54+
processSummary,
5855
);
5956
} catch (error: unknown) {
60-
await summary.error(
61-
`Unexpected error while executing the job check logs for further details. ${parseJSONError(
62-
error,
63-
)}`,
64-
);
57+
const errorMessage = "Unexpected error while executing the job.";
58+
processSummary.error(errorMessage, error);
59+
return [errorMessage];
6560
} finally {
6661
this.logger.logProcessSummary(processSummary);
67-
await summary.logProcessSummaryToJobLogger(processSummary);
62+
await logProcessSummaryToJobLogger(processSummary, job);
63+
await this.cleanSchedulerQueueHistory();
6864
}
69-
await this.cleanSchedulerQueueHistory();
70-
return summary.getSummary();
7165
}
7266

7367
@InjectLogger()

sources/packages/backend/apps/queue-consumers/src/services/application/application.service.ts

+3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ export class ApplicationService {
9494
);
9595
}),
9696
)
97+
.andWhere("application.applicationStatus IN (:...status)", {
98+
status: [ApplicationStatus.Submitted, ApplicationStatus.Completed],
99+
})
97100
.andWhere(
98101
"studentAssessment.studentAssessmentStatus = :submittedStatus",
99102
{

sources/packages/backend/apps/queue-consumers/src/services/workflow/workflow-enqueuer.service.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
StudentAssessmentStatus,
1111
} from "@sims/sims-db";
1212
import { DataSource } from "typeorm";
13-
import { LogScopes, ProcessSummary } from "@sims/utilities/logger";
13+
import { ProcessSummary } from "@sims/utilities/logger";
1414

1515
/**
1616
* Manages the operations to search assessments that requires some
@@ -37,27 +37,23 @@ export class WorkflowEnqueuerService {
3737
try {
3838
summary.info(
3939
"Checking database for applications with assessments waiting to be triggered.",
40-
LogScopes.Summary,
4140
);
4241
const applications =
4342
await this.applicationService.getApplicationsToStartAssessments();
44-
summary.info(
45-
`Found ${applications.length} applications.`,
46-
LogScopes.Summary,
47-
);
43+
summary.info(`Found ${applications.length} applications.`);
4844
if (!applications.length) {
4945
return;
5046
}
51-
summary.children = await processInParallel(
47+
const children = await processInParallel(
5248
(application: Application) => this.queueNextAssessment(application),
5349
applications,
5450
);
55-
summary.info("All assessments were processed.", LogScopes.Summary);
51+
summary.children(...children);
52+
summary.info("All assessments were processed.");
5653
} catch (error: unknown) {
5754
summary.error(
5855
"Error while enqueueing assessment workflows to be processed.",
5956
error,
60-
LogScopes.Summary,
6157
);
6258
}
6359
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./log-utils";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { ProcessSummary } from "@sims/utilities/logger";
2+
import * as dayjs from "dayjs";
3+
4+
/**
5+
* Used for log indentation to create a hierarchy
6+
* for log children at different levels.
7+
*/
8+
const LOG_INDENTATION = "--";
9+
10+
/**
11+
* Log capability provided by a queue job.
12+
*/
13+
export interface JobLogger {
14+
log(message: string): Promise<void>;
15+
}
16+
17+
/**
18+
* Writes all the log entries to job logger.
19+
* @param processSummary process summary logs.
20+
* @param logger optionally provides a job logger 3r
21+
* in case one is not present.
22+
*/
23+
export async function logProcessSummaryToJobLogger(
24+
processSummary: ProcessSummary,
25+
logger: JobLogger,
26+
): Promise<void> {
27+
for (const logEntry of processSummary.flattenLogs()) {
28+
const logIndentation = LOG_INDENTATION.repeat(logEntry.indentationLevel);
29+
const dateTimeEntry = dayjs(logEntry.date).format("YYYY-MM-DD HH:mm:ss");
30+
await logger.log(
31+
`[${dateTimeEntry}]${logIndentation}${logEntry.level.toUpperCase()}: ${
32+
logEntry.message
33+
}`,
34+
);
35+
}
36+
}
37+
38+
/**
39+
* Takes a regular success messages and append possible
40+
* attention messages if there are errors or warnings.
41+
* @param successMessage generic success message.
42+
* @param processSummary processSummary to check if an
43+
* attention message is required.
44+
* @returns generic success message or success message with
45+
* attention messages appended.
46+
*/
47+
export function getSuccessMessageWithAttentionCheck(
48+
successMessage: string,
49+
processSummary: ProcessSummary,
50+
): string[] {
51+
const message: string[] = [];
52+
message.push(successMessage);
53+
const logsSum = processSummary.getLogLevelSum();
54+
if (logsSum.error || logsSum.warn) {
55+
message.push(
56+
"Attention, process finalized with success but some errors and/or warnings messages may require some attention.",
57+
);
58+
message.push(
59+
`Error(s): ${logsSum.error}, Warning(s): ${logsSum.warn}, Info: ${logsSum.info}`,
60+
);
61+
}
62+
return message;
63+
}

0 commit comments

Comments
 (0)