Skip to content

Commit e19eae4

Browse files
authored
Merge pull request #27 from irvinlim/system-log-docker-lambda
Capture system logs from docker-lambda
2 parents af0cd1f + 81025a2 commit e19eae4

4 files changed

Lines changed: 159 additions & 32 deletions

File tree

package-lock.json

Lines changed: 41 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@
2323
"dotenv": "^5.0.0",
2424
"fs-extra": "^5.0.0",
2525
"jest": "^22.0.0",
26-
"rimraf": "^2.6.2"
26+
"rimraf": "^2.6.2",
27+
"strip-ansi": "^4.0.0"
2728
},
2829
"jest": {
2930
"testEnvironment": "node",
30-
"testRegex": "\\.test\\.js$"
31+
"testRegex": "\\.test\\.js$",
32+
"noStackTrace": true
3133
},
3234
"scripts": {
3335
"build": "npm run build:init && npm run build:js && npm run build:install",

test/util/logs.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import stripAnsi from 'strip-ansi';
2+
3+
// Define which system logs to strip out when parsing stderr from docker-lambda.
4+
const BLACKLISTED_GREPS = [
5+
/^START RequestId: /,
6+
/^END RequestId: /,
7+
/^REPORT RequestId: /,
8+
];
9+
10+
// Define the spliterator for syslog headers.
11+
const SYSLOG_HEADER_GREP = /\n?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\t[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\t/;
12+
13+
/**
14+
* Returns an array of lines that were logged using console.log()
15+
* within an invocation of AWS Lambda, extracted from the system log.
16+
*
17+
* @param {string} stderr Raw log from AWS Lambda.
18+
* @returns {array} Array of lines that were logged to the console.
19+
*/
20+
function getSysLogLines(stderr) {
21+
return stderr
22+
.trim()
23+
.split('\n')
24+
.map(stripAnsi)
25+
.filter(line => BLACKLISTED_GREPS.every(grep => !grep.test(line)))
26+
.join('\n')
27+
.split(SYSLOG_HEADER_GREP)
28+
.filter(x => x && x.length);
29+
}
30+
31+
/**
32+
* Formats a list of console.log() message to be displayed in
33+
* the test runner.
34+
*
35+
* @param {string[]} messages Messages to be displayed.
36+
* @returns {string} Formatted console messages.
37+
*/
38+
function formatLogForConsole(messages) {
39+
const lines = [
40+
'\u001b[1;33mCaptured logs while executing Lambda function:\u001b[0m',
41+
...messages,
42+
];
43+
44+
return lines.join('\n\n');
45+
}
46+
47+
/**
48+
* Formats an error message to be displayed in the test runner
49+
* when the Lambda invocation encounters an error.
50+
*
51+
* @param {string} message Error message to be displayed.
52+
* @param {string} stderr AWS system log redirected from stderr.
53+
* @returns {string} Formatted error message.
54+
*/
55+
function formatLogForError(message, stderr) {
56+
const lines = [
57+
'\u001b[1;31mError while executing Lambda function:\u001b[0m',
58+
message + '\n',
59+
'\u001b[1;33mDisplaying AWS Lambda system log:\u001b[0m',
60+
stripAnsi(stderr),
61+
];
62+
63+
return lines.join('\n');
64+
}
65+
66+
module.exports = {
67+
getSysLogLines,
68+
formatLogForConsole,
69+
formatLogForError,
70+
};

test/util/runner.js

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { formatLogForConsole, formatLogForError, getSysLogLines } from './logs';
2+
13
import dockerLambda from 'docker-lambda';
24
import dotenv from 'dotenv';
35
import path from 'path';
@@ -6,35 +8,51 @@ import path from 'path';
68
dotenv.config();
79

810
function run(event) {
9-
// Run the Lambda function.
10-
let result;
11-
12-
try {
13-
// Run the Lambda in Docker.
14-
result = dockerLambda({
15-
// Use the Node.js 6.10.0 image.
16-
dockerImage: 'lambci/lambda:nodejs6.10',
17-
// Bind the build directory as a volume to /var/task.
18-
taskDir: path.join(__dirname, '../dist'),
19-
// Pass an event to the Lambda function.
20-
event,
21-
// Pass AWS credentials from environment.
22-
// NOTE: Jest needs the environment variable values explicitly,
23-
// see https://github.com/facebook/jest/issues/5362.
24-
dockerArgs: [
25-
'-e',
26-
`AWS_ACCESS_KEY_ID=${process.env.AWS_ACCESS_KEY_ID}`,
27-
'-e',
28-
`AWS_SECRET_ACCESS_KEY=${process.env.AWS_SECRET_ACCESS_KEY}`,
29-
],
30-
});
31-
} catch (err) {
11+
// Run the Lambda in Docker.
12+
const result = dockerLambda({
13+
// Use the Node.js 6.10.0 image.
14+
dockerImage: 'lambci/lambda:nodejs6.10',
15+
16+
// Bind the build directory as a volume to /var/task.
17+
taskDir: path.join(__dirname, '../dist'),
18+
19+
// Capture both stderr and stdout, instead of catching Errors.
20+
returnSpawnResult: true,
21+
22+
// Pass an event to the Lambda function.
23+
event,
24+
25+
// Pass AWS credentials from environment.
26+
// NOTE: Jest needs the environment variable values explicitly,
27+
// see https://github.com/facebook/jest/issues/5362.
28+
dockerArgs: [
29+
'-e',
30+
`AWS_ACCESS_KEY_ID=${process.env.AWS_ACCESS_KEY_ID}`,
31+
'-e',
32+
`AWS_SECRET_ACCESS_KEY=${process.env.AWS_SECRET_ACCESS_KEY}`,
33+
],
34+
});
35+
36+
// Catch Lambda errors, based on spawn status codes/errors.
37+
if (result.error || result.status !== 0) {
3238
// Throw errors back to test runner.
33-
console.error('Error while executing Lambda function: ', err.message);
34-
throw err;
39+
const { errorMessage } = JSON.parse(result.stdout);
40+
41+
// Format error message to be displayed in test runner.
42+
const combinedErrorMsg = formatLogForError(errorMessage, result.stderr);
43+
44+
throw new Error(combinedErrorMsg);
45+
}
46+
47+
// Capture any console.log occurrences by inspecting the syslog.
48+
const logs = getSysLogLines(result.stderr);
49+
50+
// Display all messages in a single block in the test runner.
51+
if (logs.length) {
52+
console.log(formatLogForConsole(logs));
3553
}
3654

37-
return result;
55+
return JSON.parse(result.stdout);
3856
}
3957

4058
module.exports = run;

0 commit comments

Comments
 (0)