Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Fix] Improved JSON parsing for statistics #268

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions lambda/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,24 +653,33 @@ module.exports.extractDurationFromText = (log, durationType) => {
* Extract duration (in ms) from a given JSON log (multi-line) and duration type.
*/
module.exports.extractDurationFromJSON = (log, durationType) => {
// extract each line and parse it to JSON object
const lines = log.split('\n').filter((line) => line.startsWith('{')).map((line) => {
try {
return JSON.parse(line);
} catch (e) {
console.error(`Detected invalid JSON line: ${line}`);
return '';
// Check occurance of platform.report in log
if (!log.includes('platform.report')) {
throw new Error('Invalid JSON log does not contain platform.report');
}

if (!log.includes(durationType)) {
throw new Error('Invalid JSON log does not contain durationMs');
}

let lines = [];
try {
// check if lines is array
if (!Array.isArray(JSON.parse(log))) {
lines.push(JSON.parse(log));
} else {
lines = JSON.parse(log);
}
});
// find the log corresponding to the invocation report
const durationLine = lines.find((line) => line.type === 'platform.report');
if (durationLine){
let field = durationType;
// Default to 0 if the specific duration is not found in the log line
return durationLine.record.metrics[field] || 0;
} catch (e) {
// in case the log is not pretty printed, the string needs to be transformed first
console.log('Json Log not pretty printed');
lines = log.split('\n').filter((line) => line.includes('platform.report')).map((line) => {
return JSON.parse(line);
});
}

throw new Error('Unrecognized JSON log');
const durationLine = lines.find((line) => line.type === 'platform.report');
return durationLine.record.metrics[durationType] || 0;
};


Expand Down
117 changes: 117 additions & 0 deletions test/unit/test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1337,4 +1337,121 @@ describe('Lambda Utils', () => {
expect(value).to.be('RAM128-1');
});
});

describe('extractDurationFromJSON', () => {
it('should handle pretty-printed logs from Powertools', () => {
const prettyPrintedLog = `
{
"cold_start": true,
"function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:TestFunction",
"function_memory_size": "128",
"function_name": "TestFunction",
"function_request_id": "test-id",
"level": "INFO",
"message": "Lambda invocation event",
"timestamp": "2024-12-12T17:00:03.173Z",
"type": "platform.report",
"record": {
"metrics": {
"durationMs": 100.0,
"initDurationMs": 200.0
}
}
}`;
const duration = utils.extractDurationFromJSON(prettyPrintedLog, utils.DURATIONS.durationMs);
expect(duration).to.be(100.0);
});
it('should handle multiline pretty printed logs', () => {
const logLine = `
[{
"cold_start": true,
"function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:TestFunction",
"function_memory_size": "128",
"function_name": "TestFunction",
"function_request_id": "test-id",
"level": "INFO",
"message": "Lambda invocation event",
"timestamp": "2024-12-12T17:00:03.173Z",
"type": "platform.report",
"record": {
"metrics": {
"durationMs": 100.0,
"initDurationMs": 200.0
}
}
},{
"cold_start": true,
"function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:TestFunction",
"function_memory_size": "128",
"function_name": "TestFunction",
"function_request_id": "test-id",
"level": "INFO",
"message": "Lambda invocation event",
"timestamp": "2024-12-12T17:00:03.173Z",
"type": "platform.test",
"record": {
"metrics": {
"durationMs": 100.0,
"initDurationMs": 200.0
}
}
}]`;
const duration = utils.extractDurationFromJSON(logLine, utils.DURATIONS.durationMs);
expect(duration).to.be(100.0);
});
it('should handle empty lines in logs pretty printed', () => {
const logWithEmptyLines = `

[

{
"type": "platform.report",
"record": {
"metrics": {
"durationMs": 100.0
}
}
}
,
{
"some": "other log"
}

]

`;
const duration = utils.extractDurationFromJSON(logWithEmptyLines, utils.DURATIONS.durationMs);
expect(duration).to.be(100.0);
});
it('should handle logs with no platform.report', () => {
const logWithNoPlatformReport = `
{
"message": "some log"
}
{
"another": "log"
}`;
expect(() => utils.extractDurationFromJSON(logWithNoPlatformReport, utils.DURATIONS.durationMs)).to.throwError();
});
it('should handle logs with no platform.report', () => {
const logWithoutDurationMS = `
{
"cold_start": true,
"function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:TestFunction",
"function_memory_size": "128",
"function_name": "TestFunction",
"function_request_id": "test-id",
"level": "INFO",
"message": "Lambda invocation event",
"timestamp": "2024-12-12T17:00:03.173Z",
"type": "platform.report",
"record": {
"metrics": {
"initDurationMs": 200.0
}
}
}`;
expect(() => utils.extractDurationFromJSON(logWithoutDurationMS, utils.DURATIONS.durationMs)).to.throwError();
});
});
});
Loading