1 | 'use strict';
|
2 |
|
3 | const BbPromise = require('bluebird');
|
4 | const chalk = require('chalk');
|
5 | const moment = require('moment');
|
6 | const path = require('path');
|
7 |
|
8 | class OpenWhiskLogs {
|
9 | constructor(serverless, options) {
|
10 | this.serverless = serverless;
|
11 | this.options = options || {};
|
12 | this.provider = this.serverless.getProvider('openwhisk');
|
13 | this.previous_activations = new Set()
|
14 |
|
15 | this.hooks = {
|
16 | 'logs:logs': () => BbPromise.bind(this)
|
17 | .then(this.validate)
|
18 | .then(this.functionLogs)
|
19 | };
|
20 | }
|
21 |
|
22 | validate () {
|
23 | if (!this.serverless.config.servicePath) {
|
24 | throw new this.serverless.classes.Error('This command can only be run inside a service.');
|
25 | }
|
26 |
|
27 | this.serverless.service.getFunction(this.options.function);
|
28 |
|
29 | this.options.stage = this.options.stage
|
30 | || (this.serverless.service.provider && this.serverless.service.provider.stage)
|
31 | || 'dev';
|
32 | this.options.region = this.options.region
|
33 | || (this.serverless.service.provider && this.serverless.service.provider.region)
|
34 | || 'us-east-1';
|
35 |
|
36 | this.options.interval = this.options.interval || 1000;
|
37 |
|
38 | if (this.options.filter) {
|
39 | this.options.filter = new RegExp(this.options.filter, 'i');
|
40 | }
|
41 |
|
42 | if (this.options.startTime) {
|
43 | this.options.startTime = moment(this.options.startTime)
|
44 | }
|
45 |
|
46 | return this.provider.client().then(client => {
|
47 | this.client = client;
|
48 | });
|
49 | }
|
50 |
|
51 | functionLogs () {
|
52 | return BbPromise.bind(this)
|
53 | .then(this.retrieveInvocationLogs)
|
54 | .then(this.filterFunctionLogs)
|
55 | .then(this.showFunctionLogs)
|
56 | .then(() => {
|
57 | if (this.options.tail) {
|
58 | this.timeout = setTimeout(() => {
|
59 | this.functionLogs()
|
60 | }, this.options.interval)
|
61 | }
|
62 | })
|
63 | }
|
64 |
|
65 | retrieveInvocationLogs () {
|
66 | const functionObject = this.serverless.service.getFunction(this.options.function);
|
67 |
|
68 | const options = {
|
69 | docs: true,
|
70 | limit: 100,
|
71 | namespace: '_'
|
72 | };
|
73 |
|
74 | return this.client.activations.list(options)
|
75 | .catch(err => {
|
76 | throw new this.serverless.classes.Error(
|
77 | `Failed to retrieve activation logs due to error:`
|
78 | + ` ${err.message}`
|
79 | );
|
80 | });
|
81 | }
|
82 |
|
83 | filterFunctionLogs (logs) {
|
84 | const functionObject = this.serverless.service.getFunction(this.options.function);
|
85 | const actionName = functionObject.name || `${this.serverless.service.service}_${this.options.function}`
|
86 |
|
87 |
|
88 | const filtered = logs.filter(log => log.name === actionName
|
89 | && !this.previous_activations.has(log.activationId))
|
90 |
|
91 |
|
92 | if (this.options.filter) {
|
93 | filtered.forEach(log => {
|
94 | log.logs = log.logs.filter(logLine => logLine.match(this.options.filter))
|
95 | })
|
96 | }
|
97 |
|
98 |
|
99 | if (this.options.startTime) {
|
100 | filtered.forEach(log => {
|
101 | log.logs = log.logs.filter(logLine => {
|
102 | const timestamp = logLine.split(" ")[0]
|
103 | return this.options.startTime.isBefore(moment(timestamp))
|
104 | })
|
105 | })
|
106 | }
|
107 |
|
108 | return BbPromise.resolve(filtered);
|
109 | }
|
110 |
|
111 | showFunctionLogs (logs) {
|
112 | if (!this.options.tail && !logs.length) {
|
113 | this.consoleLog(`There's no log data for function "${this.options.function}" available right now…`)
|
114 | return BbPromise.resolve();
|
115 | }
|
116 |
|
117 | logs.filter(log => log.logs.length)
|
118 | .reverse()
|
119 | .map((log, idx, arr) => {
|
120 | if (this.timeout && idx === 0) console.log('')
|
121 |
|
122 | this.previous_activations.add(log.activationId)
|
123 | this.consoleLog(this.formatActivationLine(log))
|
124 | log.logs.map(this.formatLogLine).forEach(this.consoleLog)
|
125 |
|
126 | if (idx != (arr.length - 1)) {
|
127 | this.consoleLog('')
|
128 | }
|
129 | })
|
130 | return BbPromise.resolve();
|
131 | }
|
132 |
|
133 | formatActivationLine (activation) {
|
134 | return `${chalk.blue('activation')} (${chalk.yellow(activation.activationId)}):`
|
135 | }
|
136 |
|
137 | formatLogLine (logLine) {
|
138 | const items = logLine.split(' ').filter(item => item !== '')
|
139 | const format = 'YYYY-MM-DD HH:mm:ss.SSS'
|
140 | const timestamp = chalk.green(moment(items[0]).utc().format(format))
|
141 |
|
142 | let contents = items.slice(2).join(' ')
|
143 | if (items[1] === 'stderr:') {
|
144 | contents = chalk.red(contents)
|
145 | }
|
146 |
|
147 | return `${timestamp} ${contents}`
|
148 | }
|
149 |
|
150 | consoleLog(msg) {
|
151 | console.log(msg);
|
152 | }
|
153 | }
|
154 |
|
155 | module.exports = OpenWhiskLogs;
|