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
28 changes: 7 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
77 changes: 77 additions & 0 deletions src/config/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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 tail = !!flags.tail;
const outputFormat = flags.outputFormat || externalCliOptions?.outputFormat;
const filterByFunction = flags.functionSid;

return {
cwd,
accountSid,
authToken,
environment,
serviceSid,
tail,
outputFormat,
filterByFunction,
};
}
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 { logger } from '../utils/logger';

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') {
logger.info(JSON.stringify(log));
} else {
logger.info(`[${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