mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
174 lines
4.8 KiB
JavaScript
174 lines
4.8 KiB
JavaScript
import _ from 'lodash'
|
|
import dayjs from 'dayjs'
|
|
import utc from 'dayjs/plugin/utc.js'
|
|
import wait from 'timers-ext/promise/sleep.js'
|
|
import utils from '@serverlessinc/sf-core/src/utils.js'
|
|
import validate from './lib/validate.js'
|
|
import formatLambdaLogEvent from './utils/format-lambda-log-event.js'
|
|
import ServerlessError from '../../serverless-error.js'
|
|
|
|
const { style } = utils
|
|
|
|
dayjs.extend(utc)
|
|
|
|
class AwsLogs {
|
|
constructor(serverless, options, pluginUtils) {
|
|
this.serverless = serverless
|
|
this.options = options || {}
|
|
this.provider = this.serverless.getProvider('aws')
|
|
this.logger = pluginUtils.log
|
|
this.progress = pluginUtils.progress
|
|
|
|
Object.assign(this, validate)
|
|
|
|
this.hooks = {
|
|
'logs:logs': async () => {
|
|
if (!this.options.tail) {
|
|
this.progress.notice('Fetching logs')
|
|
} else {
|
|
this.logger.aside(
|
|
`Streaming new logs for function: "${this.options.function}"`,
|
|
)
|
|
this.progress.notice('Listening')
|
|
}
|
|
this.extendedValidate()
|
|
const logStreamNames = await this.getLogStreams()
|
|
await this.showLogs(logStreamNames)
|
|
},
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
async getLogStreams() {
|
|
const params = {
|
|
logGroupName: this.options.logGroupName,
|
|
descending: true,
|
|
limit: 50,
|
|
orderBy: 'LastEventTime',
|
|
}
|
|
|
|
const reply = await this.provider.request(
|
|
'CloudWatchLogs',
|
|
'describeLogStreams',
|
|
params,
|
|
)
|
|
if (!reply || reply.logStreams.length === 0) {
|
|
throw new ServerlessError(
|
|
'No existing streams for the function',
|
|
'NO_EXISTING_LOG_STREAMS',
|
|
)
|
|
}
|
|
|
|
return reply.logStreams.map((logStream) => logStream.logStreamName)
|
|
}
|
|
|
|
/**
|
|
* Show logs
|
|
* @param {Array} logStreamNames - Array of log stream names
|
|
* @returns {Promise}
|
|
*/
|
|
async showLogs(logStreamNames) {
|
|
if (!logStreamNames || !logStreamNames.length) {
|
|
if (this.options.tail) {
|
|
const newLogStreamNames = await this.getLogStreams()
|
|
await this.showLogs(newLogStreamNames)
|
|
}
|
|
}
|
|
|
|
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 = dayjs()
|
|
.subtract(
|
|
this.options.startTime.replace(/\D/g, ''),
|
|
this.options.startTime.replace(/\d/g, ''),
|
|
)
|
|
.valueOf()
|
|
} else {
|
|
params.startTime = dayjs.utc(this.options.startTime).valueOf()
|
|
}
|
|
} else {
|
|
params.startTime = dayjs().subtract(10, 'm').valueOf()
|
|
if (this.options.tail) {
|
|
params.startTime = dayjs().subtract(15, 's').valueOf()
|
|
}
|
|
}
|
|
|
|
const results = await this.provider.request(
|
|
'CloudWatchLogs',
|
|
'filterLogEvents',
|
|
params,
|
|
)
|
|
|
|
const startTimeHumanReadable = dayjs(params.startTime).format(
|
|
'YYYY-MM-DD HH:mm:ss',
|
|
)
|
|
|
|
// Remove progress before printing results
|
|
this.progress.remove()
|
|
|
|
// If not tailing, and no logs found, show a message
|
|
if (!this.options.tail && (!results.events || !results.events.length)) {
|
|
this.logger.aside(
|
|
`No logs found from start time: ${startTimeHumanReadable}`,
|
|
)
|
|
return
|
|
}
|
|
|
|
// If logs found
|
|
if (results.events && results.events.length) {
|
|
if (!this.options.tail) {
|
|
this.logger.aside(`Logs Start Time: ${startTimeHumanReadable}`)
|
|
this.logger.blankLine()
|
|
}
|
|
|
|
// Print log messages w/ custom formatting
|
|
results.events.forEach((e) => {
|
|
this.logger.notice(formatLambdaLogEvent(e.message))
|
|
})
|
|
}
|
|
|
|
if (results.nextToken) {
|
|
this.options.nextToken = results.nextToken
|
|
} else {
|
|
delete this.options.nextToken
|
|
}
|
|
|
|
if (this.options.tail) {
|
|
// Set listening message
|
|
this.progress.notice(`Listening ${style.aside(startTimeHumanReadable)}`)
|
|
|
|
if (results.events && results.events.length) {
|
|
this.options.startTime = _.last(results.events).timestamp + 1
|
|
}
|
|
|
|
await wait(this.options.interval)
|
|
const newLogStreamNames = await this.getLogStreams()
|
|
await this.showLogs(newLogStreamNames)
|
|
}
|
|
}
|
|
}
|
|
|
|
export default AwsLogs
|