@adobe/aio-cli-plugin-api-mesh 3.7.0 → 3.8.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +3 -3
  2. package/oclif.manifest.json +1 -1
  3. package/package.json +3 -3
  4. package/src/commands/__fixtures__/files/requestParams.json +2 -2
  5. package/src/commands/__fixtures__/openapi-schema.json +3 -3
  6. package/src/commands/__fixtures__/requestParams.json +2 -2
  7. package/src/commands/__fixtures__/sample_fully_qualified_mesh.json +28 -28
  8. package/src/commands/__fixtures__/sample_mesh_files.json +22 -22
  9. package/src/commands/__fixtures__/sample_mesh_invalid_file_content.json +13 -13
  10. package/src/commands/__fixtures__/sample_mesh_invalid_file_name.json +24 -26
  11. package/src/commands/__fixtures__/sample_mesh_invalid_paths.json +22 -22
  12. package/src/commands/__fixtures__/sample_mesh_invalid_type.json +24 -26
  13. package/src/commands/__fixtures__/sample_mesh_mismatching_path.json +28 -28
  14. package/src/commands/__fixtures__/sample_mesh_outside_workspace_dir.json +22 -22
  15. package/src/commands/__fixtures__/sample_mesh_path_from_home.json +13 -13
  16. package/src/commands/__fixtures__/sample_mesh_subdirectory.json +22 -22
  17. package/src/commands/__fixtures__/sample_mesh_with_files_array.json +28 -28
  18. package/src/commands/__fixtures__/sample_secrets_mesh.json +1 -1
  19. package/src/commands/api-mesh/__tests__/log-get-bulk.test.js +253 -0
  20. package/src/commands/api-mesh/__tests__/log-get.test.js +234 -0
  21. package/src/commands/api-mesh/__tests__/log-list.test.js +162 -0
  22. package/src/commands/api-mesh/log-get-bulk.js +243 -0
  23. package/src/commands/api-mesh/log-get.js +83 -0
  24. package/src/commands/api-mesh/log-list.js +105 -0
  25. package/src/lib/devConsole.js +139 -0
  26. package/src/utils.js +70 -0
@@ -0,0 +1,162 @@
1
+ const fs = require('fs');
2
+ const ListLogsCommand = require('../log-list');
3
+ const { initSdk, promptConfirm } = require('../../../helpers');
4
+ const { getMeshId, listLogs } = require('../../../lib/devConsole');
5
+
6
+ jest.mock('fs');
7
+ jest.mock('axios');
8
+ jest.mock('../../../helpers', () => ({
9
+ initSdk: jest.fn().mockResolvedValue({}),
10
+ initRequestId: jest.fn().mockResolvedValue({}),
11
+ promptConfirm: jest.fn().mockResolvedValue(true),
12
+ }));
13
+ jest.mock('../../../lib/devConsole');
14
+ jest.mock('../../../classes/logger');
15
+
16
+ describe('List Logs Command', () => {
17
+ let parseSpy;
18
+ let logSpy;
19
+
20
+ beforeEach(() => {
21
+ // Setup spies and mock functions
22
+ parseSpy = jest.spyOn(ListLogsCommand.prototype, 'parse').mockResolvedValue({
23
+ flags: {
24
+ filename: 'test.csv',
25
+ ignoreCache: false,
26
+ },
27
+ });
28
+
29
+ logSpy = jest.spyOn(ListLogsCommand.prototype, 'log');
30
+
31
+ // initRequestId.mockResolvedValue();
32
+ initSdk.mockResolvedValue({
33
+ imsOrgId: 'orgId',
34
+ imsOrgCode: 'orgCode',
35
+ projectId: 'projectId',
36
+ workspaceId: 'workspaceId',
37
+ workspaceName: 'workspaceName',
38
+ });
39
+ getMeshId.mockResolvedValue('meshId');
40
+ listLogs.mockResolvedValue([
41
+ {
42
+ rayId: '8c171e8a9a47c16d',
43
+ timestamp: 1726052061861,
44
+ responseStatus: 200,
45
+ level: 'info',
46
+ },
47
+ {
48
+ rayId: '8c171dd35860c16d',
49
+ timestamp: 1726052032540,
50
+ responseStatus: 200,
51
+ level: 'info',
52
+ },
53
+ {
54
+ rayId: '8c171dd22f00c16d',
55
+ timestamp: 1726052032348,
56
+ responseStatus: 200,
57
+ level: 'info',
58
+ },
59
+ {
60
+ rayId: '8c171dd10df2c16d',
61
+ timestamp: 1726052032167,
62
+ responseStatus: 200,
63
+ level: 'info',
64
+ },
65
+ ]);
66
+
67
+ fs.existsSync.mockReturnValue(false);
68
+ fs.appendFileSync.mockReturnValue();
69
+ promptConfirm.mockResolvedValue(true);
70
+ global.requestId = 'dummy_request_id';
71
+ });
72
+
73
+ afterEach(() => {
74
+ jest.clearAllMocks();
75
+ });
76
+
77
+ test('Throws an error if filename is not of csv extension', async () => {
78
+ // Mock the file system checks even if they are not the focus of this test
79
+ parseSpy.mockResolvedValue({
80
+ flags: {
81
+ filename: 'test.txt',
82
+ ignoreCache: false,
83
+ },
84
+ });
85
+
86
+ const command = new ListLogsCommand([], {});
87
+ await expect(command.run()).rejects.toThrow(
88
+ 'Invalid file type. Provide a filename with a .csv extension.',
89
+ );
90
+ });
91
+
92
+ test('Throws an error if file already exists', async () => {
93
+ fs.existsSync.mockReturnValue(true);
94
+
95
+ const command = new ListLogsCommand([], {});
96
+ await expect(command.run()).rejects.toThrow(
97
+ 'File test.csv already exists. Provide a new file name.',
98
+ );
99
+ });
100
+
101
+ test('Throws an error is meshId is not found', async () => {
102
+ getMeshId.mockResolvedValue(null);
103
+
104
+ const command = new ListLogsCommand([], {});
105
+ const result = command.run();
106
+ result.catch(err => {
107
+ expect(err.message).toMatchInlineSnapshot(
108
+ `"Unable to get mesh config. No mesh found for Org(orgId) -> Project(projectId) -> Workspace(workspaceId). Check the details and try again. RequestId: dummy_request_id"`,
109
+ );
110
+ });
111
+ });
112
+
113
+ test('Logs are listed successfully with file as output', async () => {
114
+ const command = new ListLogsCommand([], {});
115
+ await command.run();
116
+ expect(fs.appendFileSync).toHaveBeenCalled();
117
+ });
118
+
119
+ test('Logs are listed successfully', async () => {
120
+ parseSpy.mockResolvedValue({
121
+ flags: {
122
+ ignoreCache: false,
123
+ },
124
+ });
125
+ const command = new ListLogsCommand([], {});
126
+ await command.run();
127
+ expect(fs.appendFileSync).not.toHaveBeenCalled();
128
+ });
129
+
130
+ test('No logs found message displayed when sms returns empty array with file as output', async () => {
131
+ listLogs.mockResolvedValue([]);
132
+ const command = new ListLogsCommand([], {});
133
+ await command.run();
134
+ expect(fs.appendFileSync).not.toHaveBeenCalled();
135
+ expect(logSpy).toHaveBeenCalledWith(
136
+ `No recent logs found. Alternatively, you can use the following command to get all logs for a 30 minute time period: \naio api-mesh log-get-bulk --startTime YYYY-MM-DDTHH:MM:SSZ --endTime YYYY-MM-DDTHH:MM:SSZ --filename mesh_logs.csv`,
137
+ );
138
+ });
139
+
140
+ test('No logs found message displayed when sms returns empty array', async () => {
141
+ parseSpy.mockResolvedValue({
142
+ flags: {
143
+ ignoreCache: false,
144
+ },
145
+ });
146
+ listLogs.mockResolvedValue([]);
147
+ const command = new ListLogsCommand([], {});
148
+ await command.run();
149
+ expect(logSpy).toHaveBeenCalledWith(
150
+ `No recent logs found. Alternatively, you can use the following command to get all logs for a 30 minute time period: \naio api-mesh log-get-bulk --startTime YYYY-MM-DDTHH:MM:SSZ --endTime YYYY-MM-DDTHH:MM:SSZ --filename mesh_logs.csv`,
151
+ );
152
+ expect(fs.appendFileSync).not.toHaveBeenCalled();
153
+ });
154
+
155
+ test('Throw an error if SMS call fails', async () => {
156
+ listLogs.mockRejectedValue(new Error('SMS call failed'));
157
+ const command = new ListLogsCommand([], {});
158
+ await expect(command.run()).rejects.toThrow(
159
+ 'Failed to list recent logs, RequestId: dummy_request_id',
160
+ );
161
+ });
162
+ });
@@ -0,0 +1,243 @@
1
+ const { Command } = require('@oclif/core');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const { initRequestId, initSdk, promptConfirm } = require('../../helpers');
5
+ const { getMeshId, getPresignedUrls } = require('../../lib/devConsole');
6
+ const logger = require('../../classes/logger');
7
+ const axios = require('axios');
8
+ const {
9
+ ignoreCacheFlag,
10
+ startTimeFlag,
11
+ endTimeFlag,
12
+ logFilenameFlag,
13
+ suggestCorrectedDateFormat,
14
+ } = require('../../utils');
15
+
16
+ require('dotenv').config();
17
+
18
+ class GetBulkLogCommand extends Command {
19
+ static flags = {
20
+ ignoreCache: ignoreCacheFlag,
21
+ startTime: startTimeFlag,
22
+ endTime: endTimeFlag,
23
+ filename: logFilenameFlag,
24
+ };
25
+
26
+ async run() {
27
+ // Column headers to be written as the first row in the output file
28
+ const columnHeaders =
29
+ 'EventTimestampMs,Exceptions,Logs,Outcome,MeshId,RayID,URL,Request Method,Response Status,Level';
30
+
31
+ await initRequestId();
32
+ logger.info(`RequestId: ${global.requestId}`);
33
+ const { flags } = await this.parse(GetBulkLogCommand);
34
+ const ignoreCache = await flags.ignoreCache;
35
+
36
+ const filename = await flags.filename;
37
+
38
+ // Only supports files that end with .csv
39
+ if (!filename || path.extname(filename).toLowerCase() !== '.csv') {
40
+ this.error('Invalid file type. Provide a filename with a .csv extension.');
41
+ return;
42
+ }
43
+ // Regular expression to validate the input date format YYYY-MM-DDTHH:MM:SSZ
44
+ const dateTimeRegex = /^(?:(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T(0[0-9]|1[0-9]|2[0-3]):([0-5]\d):([0-5]\d)Z)$/;
45
+
46
+ // Validate user provided startTime format
47
+ if (!dateTimeRegex.test(flags.startTime)) {
48
+ const correctedStartTime = suggestCorrectedDateFormat(flags.startTime);
49
+ if (!correctedStartTime) {
50
+ this.error('Found invalid date components for startTime. Check and correct the date.');
51
+ } else {
52
+ this.error(
53
+ `Use the format YYYY-MM-DDTHH:MM:SSZ for startTime. Did you mean ${correctedStartTime}?`,
54
+ );
55
+ }
56
+
57
+ return;
58
+ }
59
+
60
+ // Validate user provided endTime format
61
+ if (!dateTimeRegex.test(flags.endTime)) {
62
+ const correctedEndTime = suggestCorrectedDateFormat(flags.endTime);
63
+ //check for incorrect date components
64
+ if (!correctedEndTime) {
65
+ this.error('Found invalid date components for endTime. Check and correct the date.');
66
+ } else {
67
+ this.error(
68
+ `Use the format YYYY-MM-DDTHH:MM:SSZ for endTime. Did you mean ${correctedEndTime}?`,
69
+ );
70
+ }
71
+ return;
72
+ }
73
+
74
+ // Properly format startTime and endTime strings before handing it over to SMS
75
+ const formattedStartTime = flags.startTime.replace(/-|:|Z/g, '').replace('T', 'T');
76
+ const formattedEndTime = flags.endTime.replace(/-|:|Z/g, '').replace('T', 'T');
77
+
78
+ // Convert formatted times to Date objects for comparison
79
+ const startTime = new Date(flags.startTime);
80
+ const endTime = new Date(flags.endTime);
81
+
82
+ // Require both startTime and endTime
83
+ if (!startTime || !endTime) {
84
+ this.error('Provide both startTime and endTime.');
85
+ return;
86
+ }
87
+
88
+ // Get the current date and calculate the date 30 days ago, both in UTC
89
+ const today = new Date();
90
+ const thirtyDaysAgo = new Date(today);
91
+ thirtyDaysAgo.setUTCDate(today.getUTCDate() - 30);
92
+ // Validate that logs from beyond 30 days from today are not available
93
+ if (startTime < thirtyDaysAgo || endTime < thirtyDaysAgo) {
94
+ this.error('Cannot get logs more than 30 days old. Adjust your time range.');
95
+ return;
96
+ }
97
+
98
+ // Validate required filename flag
99
+ if (!filename) {
100
+ this.error('Missing filename. Provide a valid file in the current working directory.');
101
+ return;
102
+ }
103
+
104
+ // Check if the file exists
105
+ const outputFile = path.resolve(process.cwd(), filename);
106
+
107
+ // Check if file exists and if doesn't, create one in the cwd and continue
108
+ if (!fs.existsSync(outputFile)) {
109
+ fs.writeFileSync(outputFile, '');
110
+ }
111
+
112
+ //check if the file is empty before proceeding
113
+ const stats = fs.statSync(outputFile);
114
+ if (stats.size > 0) {
115
+ throw new Error(`Make sure the file: ${filename} is empty`);
116
+ }
117
+ // truncate milliseconds to ensure comparison is only done up to seconds
118
+ startTime.setMilliseconds(0);
119
+ endTime.setMilliseconds(0);
120
+
121
+ // Validate startTime < endTime
122
+ if (startTime > endTime) {
123
+ this.error('endTime must be greater than startTime');
124
+ }
125
+
126
+ // 4. Check if the duration between start and end times is greater than 30 minutes (1800 seconds)
127
+ const timeDifferenceInSeconds = (endTime.getTime() - startTime.getTime()) / 1000;
128
+
129
+ if (timeDifferenceInSeconds > 1800) {
130
+ const hours = Math.floor(timeDifferenceInSeconds / 3600); //hours calculation
131
+ const minutes = Math.floor((timeDifferenceInSeconds % 3600) / 60); //minutes calculation
132
+ const seconds = timeDifferenceInSeconds % 60; //seconds calculation
133
+
134
+ this.error(
135
+ `Max duration between startTime and endTime should be 30 minutes. Current duration is ${hours} hour${
136
+ hours !== 1 ? 's' : ''
137
+ } ${minutes} minute${minutes !== 1 ? 's' : ''} and ${seconds} second${
138
+ seconds !== 1 ? 's' : ''
139
+ }.`,
140
+ );
141
+ return;
142
+ }
143
+ logger.info('Calling initSdk...');
144
+ const { imsOrgId, imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({
145
+ ignoreCache,
146
+ });
147
+
148
+ // Retrieve meshId
149
+ let meshId = null;
150
+ try {
151
+ meshId = await getMeshId(imsOrgId, projectId, workspaceId, workspaceName);
152
+ } catch (err) {
153
+ this.error(`Unable to get mesh ID: ${err.message}.`);
154
+ }
155
+
156
+ if (!meshId) {
157
+ this.error('Mesh ID not found.');
158
+ }
159
+
160
+ // 5. Call downloadFiles
161
+ const { presignedUrls, totalSize } = await getPresignedUrls(
162
+ imsOrgCode,
163
+ projectId,
164
+ workspaceId,
165
+ meshId,
166
+ formattedStartTime,
167
+ formattedEndTime,
168
+ );
169
+ //If presigned URLs are not found, throw error saying that no logs are found
170
+ if (!presignedUrls || presignedUrls.length === 0) {
171
+ this.error('No logs found for the given time range.');
172
+ }
173
+
174
+ let shouldDownload = false;
175
+ if (totalSize > 0) {
176
+ const totalSizeKB = (totalSize / 1024).toFixed(2); // Convert bytes to KB
177
+ // 7. Get user confirmation
178
+ shouldDownload = await promptConfirm(
179
+ `The expected file size is ${totalSizeKB} KB. Confirm ${filename} download? (y/n)`,
180
+ );
181
+ if (shouldDownload) {
182
+ //create a writer and proceed with download
183
+ const writer = fs.createWriteStream(outputFile, { flags: 'a' });
184
+
185
+ // Write the column headers before appending the log content
186
+ writer.write(`${columnHeaders}\n`);
187
+
188
+ // Stream the data from the signed URLs
189
+ for (const urlObj of presignedUrls) {
190
+ const { key, url } = urlObj;
191
+ logger.info(`Downloading ${key} and appending to ${outputFile}...`);
192
+
193
+ try {
194
+ const fileContentStream = await this.downloadFileContent(url);
195
+ fileContentStream.pipe(writer, { end: false });
196
+
197
+ await new Promise((resolve, reject) => {
198
+ fileContentStream.on('end', resolve);
199
+ fileContentStream.on('error', reject);
200
+ });
201
+ // write a newline after each file write
202
+ writer.write('\n');
203
+
204
+ logger.info(`${key} content appended successfully.`);
205
+ } catch (error) {
206
+ logger.error(`Error downloading or appending content of ${key}:`, error);
207
+ }
208
+ }
209
+ // Ensure the stream is closed
210
+ writer.end();
211
+
212
+ this.log(`Successfully downloaded the logs to ${filename}.`);
213
+ } else {
214
+ this.log('Log files not downloaded.');
215
+ }
216
+ } else {
217
+ this.error('No logs available to download');
218
+ }
219
+ }
220
+ /**
221
+ * Downloads the content of a file from the provided presigned URL.
222
+ *
223
+ * @param {string} url - presigned URL to download the log from
224
+ * @returns {Promise<Stream.Readable>} - A promise that resolves to a readable stream of the file content
225
+ */
226
+
227
+ async downloadFileContent(url) {
228
+ return axios({
229
+ method: 'get',
230
+ url: url,
231
+ responseType: 'stream',
232
+ })
233
+ .then(response => response.data)
234
+ .catch(error => {
235
+ logger.error('Error downloading log content:', error.message);
236
+ throw error;
237
+ });
238
+ }
239
+ }
240
+
241
+ GetBulkLogCommand.description = 'Download all mesh logs for a selected time period.';
242
+
243
+ module.exports = GetBulkLogCommand;
@@ -0,0 +1,83 @@
1
+ /*
2
+ Copyright 2021 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ Unless required by applicable law or agreed to in writing, software distributed under
7
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8
+ OF ANY KIND, either express or implied. See the License for the specific language
9
+ governing permissions and limitations under the License.
10
+ */
11
+ const { Command } = require('@oclif/core');
12
+ const logger = require('../../classes/logger');
13
+ const { initSdk, initRequestId } = require('../../helpers');
14
+ const { ignoreCacheFlag } = require('../../utils');
15
+ const { getMeshId, getLogsByRayId } = require('../../lib/devConsole');
16
+ require('dotenv').config();
17
+
18
+ class FetchLogsCommand extends Command {
19
+ static args = [{ name: 'rayId', required: true, description: 'Fetch a single log by rayID' }];
20
+ static flags = {
21
+ ignoreCache: ignoreCacheFlag,
22
+ };
23
+
24
+ async run() {
25
+ await initRequestId();
26
+
27
+ logger.info(`RequestId: ${global.requestId}`);
28
+
29
+ const { args, flags } = await this.parse(FetchLogsCommand);
30
+
31
+ const ignoreCache = flags.ignoreCache;
32
+ const rayId = args.rayId;
33
+
34
+ const { imsOrgId, imsOrgCode, projectId, workspaceId } = await initSdk({
35
+ ignoreCache,
36
+ });
37
+
38
+ let meshId = null;
39
+ try {
40
+ meshId = await getMeshId(imsOrgId, projectId, workspaceId, meshId);
41
+ if (!meshId) {
42
+ throw new Error('MeshIdNotFound');
43
+ }
44
+ } catch (err) {
45
+ this.error(
46
+ `Unable to get mesh ID. Please check the details and try again. RequestId: ${global.requestId}`,
47
+ );
48
+ }
49
+
50
+ try {
51
+ const meshLog = await getLogsByRayId(imsOrgCode, projectId, workspaceId, meshId, rayId);
52
+ if (meshLog) {
53
+ this.log('Event Timestamp : %s', meshLog.eventTimestampMs);
54
+ this.log('Exceptions : %s', meshLog.exceptions);
55
+ this.log('Logs : %s', meshLog.logs);
56
+ this.log('Outcome : %s', meshLog.outcome);
57
+ this.log('Mesh ID : %s', meshLog.meshId);
58
+ this.log('RayId : %s', meshLog.rayId);
59
+ this.log('Mesh URL : %s', meshLog.url);
60
+ this.log('Request Method : %s', meshLog.requestMethod);
61
+ this.log('Request Status : %s', meshLog.responseStatus);
62
+ }
63
+ } catch (error) {
64
+ if (error.message === 'LogNotFound') {
65
+ this.error(
66
+ `No logs found for RayID ${rayId}. Check the RayID and try again. RequestId: ${global.requestId}. Alternatively, you can use the following command to get all logs for a 30 minute time period: \naio api-mesh log-get-bulk --startTime YYYY-MM-DDTHH:MM:SSZ --endTime YYYY-MM-DDTHH:MM:SSZ --filename mesh_logs.csv`,
67
+ );
68
+ } else if (error.message === 'ServerError') {
69
+ this.error(
70
+ `Server error while fetching logs for RayId ${rayId}. Please try again later. RequestId: ${global.requestId}`,
71
+ );
72
+ } else {
73
+ this.error(
74
+ `Unable to get mesh logs. Please check the details and try again. If the error persists please contact support. RequestId: ${global.requestId}`,
75
+ );
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ FetchLogsCommand.description = 'Get the Log of a given mesh by RayId';
82
+
83
+ module.exports = FetchLogsCommand;
@@ -0,0 +1,105 @@
1
+ const { Command } = require('@oclif/core');
2
+
3
+ const logger = require('../../classes/logger');
4
+ const { initSdk, initRequestId } = require('../../helpers');
5
+ const { ignoreCacheFlag, fileNameFlag } = require('../../utils');
6
+ const { getMeshId, listLogs } = require('../../lib/devConsole');
7
+ const { appendFileSync, existsSync } = require('fs');
8
+ const { ux } = require('@oclif/core/lib/cli-ux');
9
+ const path = require('path');
10
+
11
+ require('dotenv').config();
12
+ class ListLogsCommand extends Command {
13
+ static flags = {
14
+ ignoreCache: ignoreCacheFlag,
15
+ filename: fileNameFlag,
16
+ };
17
+
18
+ static enableJsonFlag = true;
19
+
20
+ async run() {
21
+ await initRequestId();
22
+
23
+ logger.info(`RequestId: ${global.requestId}`);
24
+
25
+ const { flags } = await this.parse(ListLogsCommand);
26
+
27
+ const { ignoreCache, filename } = await flags;
28
+
29
+ if (filename) {
30
+ if (path.extname(filename).toLowerCase() !== '.csv') {
31
+ this.error('Invalid file type. Provide a filename with a .csv extension.');
32
+ }
33
+ const file = path.resolve(process.cwd(), filename);
34
+ if (existsSync(file)) {
35
+ this.error(`File ${filename} already exists. Provide a new file name.`);
36
+ }
37
+ }
38
+
39
+ const { imsOrgId, imsOrgCode, projectId, workspaceId, workspaceName } = await initSdk({
40
+ ignoreCache,
41
+ });
42
+
43
+ let meshId = null;
44
+
45
+ try {
46
+ meshId = await getMeshId(imsOrgId, projectId, workspaceId, workspaceName);
47
+ } catch (err) {
48
+ this.error(
49
+ `Unable to get mesh ID. Check the details and try again. RequestId: ${global.requestId}`,
50
+ );
51
+ }
52
+ if (meshId) {
53
+ try {
54
+ const logs = await listLogs(imsOrgCode, projectId, workspaceId, meshId, filename);
55
+
56
+ if (logs && logs.length > 0) {
57
+ // add a new line
58
+ this.log();
59
+ ux.table(
60
+ logs,
61
+ {
62
+ rayId: {
63
+ header: 'Ray ID',
64
+ minWidth: 15,
65
+ },
66
+ timestamp: {
67
+ header: 'Timestamp',
68
+ minWidth: 15,
69
+ },
70
+ responseStatus: {
71
+ header: 'Response Status',
72
+ minWidth: 15,
73
+ },
74
+ level: {
75
+ header: 'Level',
76
+ minWidth: 15,
77
+ },
78
+ },
79
+ {
80
+ printLine: filename
81
+ ? line => appendFileSync(filename, line + '\n')
82
+ : line => this.log(line),
83
+ csv: filename,
84
+ ...flags,
85
+ },
86
+ );
87
+ } else {
88
+ this.log(
89
+ 'No recent logs found. Alternatively, you can use the following command to get all logs for a 30 minute time period: \naio api-mesh log-get-bulk --startTime YYYY-MM-DDTHH:MM:SSZ --endTime YYYY-MM-DDTHH:MM:SSZ --filename mesh_logs.csv',
90
+ );
91
+ }
92
+ } catch (error) {
93
+ this.error(`Failed to list recent logs, RequestId: ${global.requestId}`);
94
+ }
95
+ } else {
96
+ this.error(
97
+ `Unable to get mesh config. No mesh found for Org(${imsOrgId}) -> Project(${projectId}) -> Workspace(${workspaceId}). Check the details and try again. RequestId: ${global.requestId}`,
98
+ );
99
+ }
100
+ }
101
+ }
102
+
103
+ ListLogsCommand.description = 'Get recent logs of requests made to the API Mesh.';
104
+
105
+ module.exports = ListLogsCommand;