Skip to content

feat(logs): adds command to access new logging functions #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18,880 changes: 0 additions & 18,880 deletions package-lock.json

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"author": "Dominik Kundel <[email protected]>",
"license": "MIT",
"dependencies": {
"@twilio-labs/serverless-api": "^1.0.3",
"@twilio-labs/serverless-api": "^1.1.0",
"@twilio-labs/serverless-runtime-types": "^1.1.7",
"@types/express": "^4.17.0",
"@types/inquirer": "^6.0.3",
Expand Down
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ListCommand from './commands/list';
import * as NewCommand from './commands/new';
import * as StartCommand from './commands/start';
import * as ListTemplatesCommand from './commands/list-templates';
import * as LogsCommand from './commands/logs';
import './utils/debug';

export async function run(rawArgs: string[]) {
Expand All @@ -15,5 +16,6 @@ export async function run(rawArgs: string[]) {
.command(DeployCommand)
.command(ListCommand)
.command(ActivateCommand)
.command(LogsCommand)
.parse(rawArgs.slice(2));
}
143 changes: 143 additions & 0 deletions src/commands/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
TwilioServerlessApiClient,
LogApiResource,
} from '@twilio-labs/serverless-api';
import { LogsCliFlags, LogsConfig, getConfigFromFlags } from '../config/logs';
import { Argv } from 'yargs';
import { checkConfigForCredentials } from '../checks/check-credentials';
import checkForValidServiceSid from '../checks/check-service-sid';
import { printLogs, printLog } from '../printers/logs';
import { getDebugFunction, logger, setLogLevelByName } from '../utils/logger';
import { ExternalCliOptions, sharedCliOptions } from './shared';
import { CliInfo } from './types';
import { getFullCommand } from './utils';

const debug = getDebugFunction('twilio-run:logs');

function logError(msg: string) {
logger.error(msg);
}

function handleError(err: Error) {
debug('%O', err);
logError(err.message);
process.exit(1);
}

export async function handler(
flags: LogsCliFlags,
externalCliOptions?: ExternalCliOptions
): Promise<void> {
setLogLevelByName(flags.logLevel);

let config: LogsConfig;
try {
config = await getConfigFromFlags(flags, externalCliOptions);
} catch (err) {
debug(err);
logError(err.message);
process.exit(1);
return;
}

if (!config) {
logError('Internal Error');
process.exit(1);
return;
}

checkConfigForCredentials(config);
const command = getFullCommand(flags);
checkForValidServiceSid(command, config.serviceSid);

try {
const client = new TwilioServerlessApiClient(config);
if (flags.tail) {
const stream = await client.getLogsStream({ ...config });
stream.on('data', (log: LogApiResource) => {
printLog(log, config.outputFormat);
});
} else {
const result = (await client.getLogs({ ...config })) as LogApiResource[];
printLogs(result, config, config.outputFormat);
}
} catch (err) {
handleError(err);
}
}

export const cliInfo: CliInfo = {
options: {
...sharedCliOptions,
'service-sid': {
type: 'string',
describe: 'Specific Serverless Service SID to retrieve logs for',
},
environment: {
type: 'string',
describe: 'The environment to retrieve the logs for',
default: 'dev',
},
'function-sid': {
type: 'string',
describe: 'Specific Function SID to retrieve logs for',
},
tail: {
type: 'boolean',
describe: 'Continuously stream the logs',
},
'output-format': {
type: 'string',
alias: 'o',
default: '',
describe: 'Output the log in a different format',
choices: ['', 'json'],
},
'account-sid': {
type: 'string',
alias: 'u',
describe:
'A specific account SID to be used for deployment. Uses fields in .env otherwise',
},
'auth-token': {
type: 'string',
describe:
'Use a specific auth token for deployment. Uses fields from .env otherwise',
},
env: {
type: 'string',
describe:
'Path to .env file for environment variables that should be installed',
},
},
};

function optionBuilder(yargs: Argv<any>): Argv<LogsCliFlags> {
yargs = yargs
.example(
'$0 logs',
'Prints the last 50 logs for the current project in the dev environment'
)
.example(
'$0 logs --environment=production',
'Prints the last 50 logs for the current project in the production environment'
)
.example(
'$0 logs --tail',
'Tails and prints the logs of the current project'
)
.example(
'$0 logs --functionSid=ZFXXX',
'Only prints the logs from the named function'
);

yargs = Object.keys(cliInfo.options).reduce((yargs, name) => {
return yargs.option(name, cliInfo.options[name]);
}, yargs);

return yargs;
}

export const command = ['logs'];
export const describe = 'Print logs from your Twilio Serverless project';
export const builder = optionBuilder;
2 changes: 2 additions & 0 deletions src/config/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ActivateCliFlags } from './activate';
import { DeployCliFlags } from './deploy';
import { ListCliFlags } from './list';
import { StartCliFlags } from './start';
import { LogsCliFlags } from './logs';

const DEFAULT_CONFIG_NAME = '.twilio-functions';

Expand All @@ -11,6 +12,7 @@ type CommandConfigurations = {
listConfig: Partial<ListCliFlags>;
startConfig: Partial<StartCliFlags & { serviceSid: string }>;
activateConfig: Partial<ActivateCliFlags>;
logsConfig: Partial<LogsCliFlags>;
};

type ProjectConfigurations = Partial<CommandConfigurations> & {
Expand Down
75 changes: 75 additions & 0 deletions src/config/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { LogsConfig as ApiLogsConfig } from '@twilio-labs/serverless-api';
import path from 'path';
import { Arguments } from 'yargs';
import checkForValidServiceSid from '../checks/check-service-sid';
import { cliInfo } from '../commands/logs';
import {
ExternalCliOptions,
SharedFlagsWithCrdentials,
} from '../commands/shared';
import { getFullCommand } from '../commands/utils';
import { readSpecializedConfig } from './global';
import { getCredentialsFromFlags } from './utils';
import { mergeFlagsAndConfig } from './utils/mergeFlagsAndConfig';

export type LogsConfig = ApiLogsConfig & {
cwd: string;
accountSid: string;
authToken: string;
properties?: string[];
outputFormat?: string;
};

export type LogsCliFlags = Arguments<
SharedFlagsWithCrdentials & {
cwd?: string;
environment?: string;
serviceSid?: string;
functionSid?: string;
tail: boolean;
outputFormat?: string;
}
>;

export async function getConfigFromFlags(
flags: LogsCliFlags,
externalCliOptions?: ExternalCliOptions
): Promise<LogsConfig> {
let cwd = flags.cwd ? path.resolve(flags.cwd) : process.cwd();
flags.cwd = cwd;

let environment = flags.environment || 'dev';
flags.environment = environment;

const configFlags = readSpecializedConfig(cwd, flags.config, 'logsConfig', {
projectId:
flags.accountSid ||
(externalCliOptions && externalCliOptions.accountSid) ||
undefined,
environmentSuffix: environment,
});

flags = mergeFlagsAndConfig(configFlags, flags, cliInfo);
cwd = flags.cwd || cwd;
environment = flags.environment || environment;

const { accountSid, authToken } = await getCredentialsFromFlags(
flags,
externalCliOptions
);

const command = getFullCommand(flags);
const serviceSid = checkForValidServiceSid(command, flags.serviceSid);
const outputFormat = flags.outputFormat || externalCliOptions?.outputFormat;

return {
cwd,
accountSid,
authToken,
environment,
serviceSid,
outputFormat,
filterByFunction: flags.functionSid,
tail: flags.tail,
};
}
19 changes: 19 additions & 0 deletions src/printers/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LogList, LogApiResource } from '@twilio-labs/serverless-api';
import { LogsConfig } from '../config/logs';
import { writeOutput } from '../utils/output';

export function printLogs(
result: LogApiResource[],
config: LogsConfig,
outputFormat?: string
) {
result.forEach(log => printLog(log, outputFormat));
}

export function printLog(log: LogApiResource, outputFormat?: string) {
if (outputFormat === 'json') {
writeOutput(JSON.stringify(log));
} else {
writeOutput(`[${log.level}][${log.date_created}]: ${log.message}`);
}
}
6 changes: 5 additions & 1 deletion src/serverless-api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export type ApiErrorResponse = {
export async function getFunctionServiceSid(
cwd: string,
configName: string,
commandConfig: 'deployConfig' | 'listConfig' | 'activateConfig',
commandConfig:
| 'deployConfig'
| 'listConfig'
| 'activateConfig'
| 'logsConfig',
projectId?: string
): Promise<string | undefined> {
const twilioConfig = readSpecializedConfig(cwd, configName, commandConfig, {
Expand Down