Dan Root add4951a4b Better handling for AWS Lambda logging lines.
Refactor duplicated log line formatting from invoke and logs plugins
into utils/formatLambdaLogEvent.js and add tests.

Make log line formatter understand default python logging module output
from Lambda functions.

Add payload output testing to invoke plugin.

Fixes #1796 and #3594
2017-05-19 12:42:02 -07:00

129 lines
3.6 KiB
JavaScript

'use strict';
const BbPromise = require('bluebird');
const _ = require('lodash');
const moment = require('moment');
const validate = require('../lib/validate');
const formatLambdaLogEvent = require('../utils/formatLambdaLogEvent');
class AwsLogs {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options || {};
this.provider = this.serverless.getProvider('aws');
Object.assign(this, validate);
this.hooks = {
'logs:logs': () => BbPromise.bind(this)
.then(this.extendedValidate)
.then(this.getLogStreams)
.then(this.showLogs),
};
}
extendedValidate() {
this.validate();
// validate function exists in service
const lambdaName = this.serverless.service.getFunction(this.options.function).name;
this.options.interval = this.options.interval || 1000;
this.options.logGroupName = this.provider.naming.getLogGroupName(lambdaName);
return BbPromise.resolve();
}
getLogStreams() {
const params = {
logGroupName: this.options.logGroupName,
descending: true,
limit: 50,
orderBy: 'LastEventTime',
};
return this.provider
.request('CloudWatchLogs',
'describeLogStreams',
params,
this.options.stage,
this.options.region)
.then(reply => {
if (!reply || reply.logStreams.length === 0) {
throw new this.serverless.classes
.Error('No existing streams for the function');
}
return _.chain(reply.logStreams)
.filter(stream => stream.logStreamName.includes('[$LATEST]'))
.map('logStreamName')
.value();
});
}
showLogs(logStreamNames) {
if (!logStreamNames || !logStreamNames.length) {
if (this.options.tail) {
return setTimeout((() => this.getLogStreams()
.then(nextLogStreamNames => this.showLogs(nextLogStreamNames))),
this.options.interval);
}
}
const params = {
logGroupName: this.options.logGroupName,
interleaved: true,
logStreamNames,
startTime: this.options.startTime,
};
if (this.options.filter) params.filterPattern = this.options.filter;
if (this.options.nextToken) params.nextToken = this.options.nextToken;
if (this.options.startTime) {
const since = (['m', 'h', 'd']
.indexOf(this.options.startTime[this.options.startTime.length - 1]) !== -1);
if (since) {
params.startTime = moment().subtract(this.options
.startTime.replace(/\D/g, ''), this.options
.startTime.replace(/\d/g, '')).valueOf();
} else {
params.startTime = moment.utc(this.options.startTime).valueOf();
}
}
return this.provider
.request('CloudWatchLogs',
'filterLogEvents',
params,
this.options.stage,
this.options.region)
.then(results => {
if (results.events) {
results.events.forEach(e => {
process.stdout.write(formatLambdaLogEvent(e.message));
});
}
if (results.nextToken) {
this.options.nextToken = results.nextToken;
} else {
delete this.options.nextToken;
}
if (this.options.tail) {
if (results.events && results.events.length) {
this.options.startTime = _.last(results.events).timestamp + 1;
}
return setTimeout((() => this.getLogStreams()
.then(nextLogStreamNames => this.showLogs(nextLogStreamNames))),
this.options.interval);
}
return BbPromise.resolve();
});
}
}
module.exports = AwsLogs;