Skip to content

Commit

Permalink
feat: add support for function logs streaming to sandbox (#1492)
Browse files Browse the repository at this point in the history
* feat: add support for function logs streaming to sandbox

* update package lock

* update package lock

* update package lock

* PR feedback updates

* PR feedback updates

* PR feedback updates

* try this

* try this

* try this

* Updates to cli options

* add more tests

* fix lint

* PR feedback updates

* remove colors suppressions from printer

* move ArnParser from cdk to sdk

* fix error handling

* PR updates

* Remove color-support

* regenerate package lock

* update tests
  • Loading branch information
Amplifiyer authored Jul 16, 2024
1 parent 6295919 commit 8f23287
Show file tree
Hide file tree
Showing 19 changed files with 9,057 additions and 6,109 deletions.
8 changes: 8 additions & 0 deletions .changeset/cool-coins-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@aws-amplify/cli-core': minor
'@aws-amplify/sandbox': minor
'@aws-amplify/backend-cli': minor
'create-amplify': patch
---

feat: add support for function logs streaming to sandbox
13,717 changes: 7,622 additions & 6,095 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion packages/cli-core/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// <reference types="node" />

import { PackageManagerController } from '@aws-amplify/plugin-types';
import { WriteStream } from 'node:tty';

// @public
export class AmplifyPrompter {
Expand All @@ -21,12 +22,20 @@ export class AmplifyPrompter {
}) => Promise<boolean>;
}

// @public (undocumented)
export type ColorName = (typeof colorNames)[number];

// @public (undocumented)
export const colorNames: readonly ["Green", "Yellow", "Blue", "Magenta", "Cyan"];

// @public
export class Format {
constructor(packageManagerRunnerName?: string);
// (undocumented)
bold: (message: string) => string;
// (undocumented)
color: (message: string, colorName: ColorName) => string;
// (undocumented)
command: (command: string) => string;
// (undocumented)
dim: (message: string) => string;
Expand Down Expand Up @@ -73,7 +82,7 @@ export class PackageManagerControllerFactory {

// @public
export class Printer {
constructor(minimumLogLevel: LogLevel, stdout?: NodeJS.WriteStream, stderr?: NodeJS.WriteStream, refreshRate?: number);
constructor(minimumLogLevel: LogLevel, stdout?: WriteStream | NodeJS.WritableStream, stderr?: WriteStream | NodeJS.WritableStream, refreshRate?: number);
indicateProgress(message: string, callback: () => Promise<void>): Promise<void>;
log(message: string, level?: LogLevel): void;
print: (message: string) => void;
Expand Down
16 changes: 15 additions & 1 deletion packages/cli-core/src/format/format.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as os from 'node:os';
import * as assert from 'node:assert';
import { after, before, describe, it } from 'node:test';
import { after, before, beforeEach, describe, it } from 'node:test';
import { Format, format } from './format.js';
import { $, blue, bold, cyan, green, red, underline } from 'kleur/colors';

void describe('format', () => {
beforeEach(() => {
$.enabled = true;
});

void it('should format ampx command with yarn', { concurrency: 1 }, () => {
const formatter = new Format('yarn');
assert.strictEqual(
Expand Down Expand Up @@ -157,3 +161,13 @@ void describe('format when terminal colors disabled', async () => {
assert.strictEqual(coloredMessage, 'npx ampx hello');
});
});

void describe('format.color', async () => {
void it('should format colors as requested', () => {
const input = 'something to color';
const expectedOutput = green(input);
const actualOutput = format.color(input, 'Green');
assert.strictEqual(actualOutput, expectedOutput);
assert.notStrictEqual(actualOutput, red(input)); // confirm that coloring actually works
});
});
23 changes: 23 additions & 0 deletions packages/cli-core/src/format/format.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import * as os from 'node:os';
import {
Colorize,
blue,
bold,
cyan,
dim,
green,
grey,
magenta,
red,
underline,
yellow,
} from 'kleur/colors';
import { AmplifyFault } from '@aws-amplify/platform-core';
import { getPackageManagerRunnerName } from '../package-manager-controller/get_package_manager_name.js';
Expand Down Expand Up @@ -71,6 +74,26 @@ export class Format {
Object.entries(record)
.map(([key, value]) => `${key}: ${String(value)}`)
.join(os.EOL);
color = (message: string, colorName: ColorName) => colors[colorName](message);
}

// Map to connect colorName to kleur color
const colors: Record<ColorName, Colorize> = {
Green: green,
Yellow: yellow,
Blue: blue,
Magenta: magenta,
Cyan: cyan,
};

export const colorNames = [
'Green',
'Yellow',
'Blue',
'Magenta',
'Cyan',
] as const;

export type ColorName = (typeof colorNames)[number];

export const format = new Format();
2 changes: 1 addition & 1 deletion packages/cli-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './prompter/amplify_prompts.js';
export * from './printer/printer.js';
export * from './printer.js';
export { format, Format } from './format/format.js';
export { ColorName, colorNames, format, Format } from './format/format.js';
export * from './package-manager-controller/package_manager_controller_factory.js';
12 changes: 8 additions & 4 deletions packages/cli-core/src/printer/printer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { WriteStream } from 'node:tty';
import { EOL } from 'os';

export type RecordValue = string | number | string[] | Date;

/**
Expand All @@ -19,8 +19,12 @@ export class Printer {
*/
constructor(
private readonly minimumLogLevel: LogLevel,
private readonly stdout: NodeJS.WriteStream = process.stdout,
private readonly stderr: NodeJS.WriteStream = process.stderr,
private readonly stdout:
| WriteStream
| NodeJS.WritableStream = process.stdout,
private readonly stderr:
| WriteStream
| NodeJS.WritableStream = process.stderr,
private readonly refreshRate: number = 500
) {}

Expand Down Expand Up @@ -91,7 +95,7 @@ export class Printer {
* Checks if the environment is TTY
*/
private isTTY() {
return this.stdout.isTTY;
return this.stdout instanceof WriteStream && this.stdout.isTTY;
}

/**
Expand Down
37 changes: 35 additions & 2 deletions packages/cli/src/commands/sandbox/sandbox_command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { AmplifyPrompter, format, printer } from '@aws-amplify/cli-core';
import { EventHandler, SandboxCommand } from './sandbox_command.js';
import { createSandboxCommand } from './sandbox_command_factory.js';
import { SandboxDeleteCommand } from './sandbox-delete/sandbox_delete_command.js';
import { Sandbox, SandboxSingletonFactory } from '@aws-amplify/sandbox';
import {
Sandbox,
SandboxFunctionStreamingOptions,
SandboxSingletonFactory,
} from '@aws-amplify/sandbox';
import { createSandboxSecretCommand } from './sandbox-secret/sandbox_secret_command_factory.js';
import { ClientConfigGeneratorAdapter } from '../../client-config/client_config_generator_adapter.js';
import { CommandMiddleware } from '../../command_middleware.js';
Expand Down Expand Up @@ -118,6 +122,9 @@ void describe('sandbox command', () => {
assert.match(output, /--outputs-format/);
assert.match(output, /--outputs-out-dir/);
assert.match(output, /--once/);
assert.match(output, /--stream-function-logs/);
assert.match(output, /--logs-filter/);
assert.match(output, /--logs-out-file/);
assert.equal(mockHandleProfile.mock.callCount(), 0);
});

Expand Down Expand Up @@ -341,7 +348,7 @@ void describe('sandbox command', () => {
);
});

void it('--once flag is mutually exclusive with dir-to-watch & exclude', async () => {
void it('--once flag is mutually exclusive with dir-to-watch, exclude and stream-function-logs', async () => {
assert.match(
await commandRunner.runCommand(
'sandbox --once --dir-to-watch nonExistentDir'
Expand All @@ -352,5 +359,31 @@ void describe('sandbox command', () => {
await commandRunner.runCommand('sandbox --once --exclude test'),
/Arguments once and exclude are mutually exclusive/
);
assert.match(
await commandRunner.runCommand('sandbox --once --stream-function-logs'),
/Arguments once and stream-function-logs are mutually exclusive/
);
});

void it('fails if --logs-out-file is provided without enabling --stream-function-logs', async () => {
assert.match(
await commandRunner.runCommand('sandbox --logs-out-file someFile'),
/Missing dependent arguments:\n logs-out-file -> stream-function-logs/
);
});

void it('starts sandbox with log watching options', async () => {
await commandRunner.runCommand(
'sandbox --stream-function-logs --logs-filter func1 --logs-filter func2 --logs-out-file someFile'
);
assert.equal(sandboxStartMock.mock.callCount(), 1);
assert.deepStrictEqual(
sandboxStartMock.mock.calls[0].arguments[0].functionStreamingOptions,
{
enabled: true,
logsFilters: ['func1', 'func2'],
logsOutFile: 'someFile',
} as SandboxFunctionStreamingOptions
);
});
});
57 changes: 55 additions & 2 deletions packages/cli/src/commands/sandbox/sandbox_command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs';
import fs from 'fs';
import fsp from 'fs/promises';
import { AmplifyPrompter } from '@aws-amplify/cli-core';
import { SandboxSingletonFactory } from '@aws-amplify/sandbox';
import {
SandboxFunctionStreamingOptions,
SandboxSingletonFactory,
} from '@aws-amplify/sandbox';
import {
ClientConfigFormat,
ClientConfigVersion,
Expand All @@ -26,6 +29,9 @@ export type SandboxCommandOptionsKebabCase = ArgumentsKebabCase<
outputsOutDir: string | undefined;
outputsVersion: string;
once: boolean | undefined;
streamFunctionLogs: boolean | undefined;
logsFilter: string[] | undefined;
logsOutFile: string | undefined;
} & SandboxCommandGlobalOptions
>;

Expand Down Expand Up @@ -123,12 +129,29 @@ export class SandboxCommand
}

watchExclusions.push(clientConfigWritePath);

let functionStreamingOptions: SandboxFunctionStreamingOptions = {
enabled: false,
};
if (args.streamFunctionLogs) {
// turn on function logs streaming
functionStreamingOptions = {
enabled: true,
logsFilters: args.logsFilter,
logsOutFile: args.logsOutFile,
};

if (args.logsOutFile) {
watchExclusions.push(args.logsOutFile);
}
}
await sandbox.start({
dir: args.dirToWatch,
exclude: watchExclusions,
identifier: args.identifier,
profile: args.profile,
watchForChanges: !args.once,
functionStreamingOptions,
});
process.once('SIGINT', () => void this.sigIntHandler());
};
Expand Down Expand Up @@ -196,6 +219,30 @@ export class SandboxCommand
boolean: true,
global: false,
})
.option('stream-function-logs', {
describe:
'Whether to stream function execution logs. Default: false. Use --logs-filter in addition to this flag to stream specific function logs',
boolean: true,
global: false,
group: 'Logs streaming',
})
.option('logs-filter', {
describe: `Regex pattern to filter logs from only matched functions. E.g. to stream logs for a function, specify it's name, and to stream logs from all functions starting with auth specify 'auth' Default: Stream all logs`,
array: true,
type: 'string',
group: 'Logs streaming',
implies: ['stream-function-logs'],
requiresArg: true,
})
.option('logs-out-file', {
describe:
'File to append the streaming logs. The file is created if it does not exist. Default: stdout',
array: false,
type: 'string',
group: 'Logs streaming',
implies: ['stream-function-logs'],
requiresArg: true,
})
.check(async (argv) => {
if (argv['dir-to-watch']) {
await this.validateDirectory('dir-to-watch', argv['dir-to-watch']);
Expand All @@ -210,7 +257,13 @@ export class SandboxCommand
}
return true;
})
.conflicts('once', ['exclude', 'dir-to-watch'])
.conflicts('once', [
'exclude',
'dir-to-watch',
'stream-function-logs',
'logs-filter',
'logs-out-file',
])
.middleware([this.commandMiddleware.ensureAwsCredentialAndRegion])
);
};
Expand Down
8 changes: 8 additions & 0 deletions packages/sandbox/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export type SandboxDeleteOptions = {
// @public (undocumented)
export type SandboxEvents = 'successfulDeployment' | 'failedDeployment' | 'successfulDeletion';

// @public (undocumented)
export type SandboxFunctionStreamingOptions = {
enabled: boolean;
logsFilters?: string[];
logsOutFile?: string;
};

// @public (undocumented)
export type SandboxOptions = {
dir?: string;
Expand All @@ -38,6 +45,7 @@ export type SandboxOptions = {
format?: ClientConfigFormat;
profile?: string;
watchForChanges?: boolean;
functionStreamingOptions?: SandboxFunctionStreamingOptions;
};

// @public
Expand Down
5 changes: 4 additions & 1 deletion packages/sandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
"@aws-amplify/deployed-backend-client": "^1.0.2",
"@aws-amplify/platform-core": "^1.0.2",
"@aws-sdk/client-cloudformation": "^3.465.0",
"@aws-sdk/credential-providers": "^3.465.0",
"@aws-sdk/client-cloudwatch-logs": "^3.465.0",
"@aws-sdk/client-lambda": "^3.465.0",
"@aws-sdk/client-ssm": "^3.465.0",
"@aws-sdk/credential-providers": "^3.465.0",
"@aws-sdk/types": "^3.465.0",
"@aws-sdk/util-arn-parser": "^3.465.0",
"@parcel/watcher": "^2.4.1",
"debounce-promise": "^3.1.2",
"glob": "^10.2.7",
Expand Down
Loading

0 comments on commit 8f23287

Please sign in to comment.