docs/sf/guides/plugins/cli-output.md
Plugins can integrate and extend the CLI output of the Serverless Framework in different ways.
In Serverless Framework v2, plugins could write to the CLI output via serverless.cli.log():
// This approach is deprecated:
serverless.cli.log('Message')
The method above is deprecated. It should no longer be used in Serverless Framework v3.
Instead, plugins can log messages to the CLI output via a standard log interface:
class MyPlugin {
constructor(serverless, cliOptions, { log }) {
log.error('Error')
log.warning('Warning')
log.notice('Message')
log.info('Verbose message') // --verbose log
log.debug('Debug message') // --debug log
}
}
Some aliases exist to make log levels more explicit:
log('Here is a message')
// is an alias to:
log.notice('Here is a message')
log.verbose('Here is a verbose message') // displayed with --verbose
// is an alias to:
log.info('Here is a verbose message') // displayed with --verbose
To write a formatted "success" message, use the following helper:
log.success('The task executed with success')
Success messages render with the checkmark, like the "Service deployed" success message:
Log methods also support the printf format:
log.warning('Here is a %s log', 'formatted')
Best practices:
--verbose output.--verbose message or trigger a deprecation (see below).log.error(), consider throwing an exception: exceptions are automatically caught by the Serverless Framework and formatted with details.--debug level. Debug logs can be namespaced following the debug convention via log.get('my-namespace').debug('Debug message'). Such logs can then be filtered in the CLI output via --debug=plugin-name:my-namespace.By default, logs are written to stderr, which displays in terminals (humans cannot tell the difference). This is intentional: plugins can safely log extra messages to any command, even commands meant to be piped or parsed by another program. Read the next section to learn more.
stdoutBy default, plugins should write messages to stderr via the log object. To write command output to stdout instead, use writeText():
class MyPlugin {
constructor(serverless, cliOptions, { writeText }) {
writeText('Command output')
writeText(['Here is a', 'multi-line output'])
}
}
Best practices:
stdout output is usually meant to be piped to/parsed by another program.stdout in commands they define (to avoid breaking the output of other commands).stdout should be the main output of the command.Take, for example, the serverless invoke command:
stdout, it allows any script to parse the result of the Lambda invocation.stderr: such logs are useful to humans, for example configuration warnings, upgrade notifications, Lambda logs… Since they are written to stderr, they do not break the parsable output of stdout.If unsure, write to stderr (with the log object) instead of stdout. Why: human users will not see any difference, but the door will stay open to write a parsable output later in the future.
To format and color text output, use the chalk package. For example:
log.notice(chalk.gray('Here is a message'))
Best practices:
deploy command, or result of the invoke command). Secondary information is everything else.log.success()), interactive progress…The "Serverless red" color (#fd5750) is used to grab the user's attention:
The Serverless Framework differentiates between 2 errors:
To throw a user error and have it properly formatted, use Serverless' error class:
throw new serverless.classes.Error('Invalid configuration in X')
All other errors are considered programmer errors by default (and are properly formatted in the CLI output as well).
Best practices:
throw.log.error().
serverless-offline should not stop the local server.Plugins can create an interactive progress:
class MyPlugin {
constructor(serverless, cliOptions, { progress }) {
const myProgress = progress.create({
message: 'Doing extra work in my-plugin',
})
// ...
myProgress.update('Almost finished')
// ...
myProgress.remove()
}
}
In case of parallel processing (for example compiling multiple files in parallel), it is possible to create multiple progress items if that is useful to users.
Best practices:
--verbose only.Note that it is possible to give a unique name to a progress. That name can be used to retrieve the progress without having to pass the instance around:
// Progress without any name:
const myProgress = progress.create({
message: 'Doing extra work in my-plugin',
})
// Progress with a unique name
progress.create({
message: 'Doing extra work in my-plugin',
name: 'my-plugin-progress', // Try to make the name unique across all plugins
})
// elsewhere...
progress.get('my-plugin-progress').update('Almost finished')
// elsewhere...
progress.get('my-plugin-progress').remove()
Plugins can add their own sections to the "Service information", i.e. the information displayed after serverless deploy or in serverless info.
To add a single item:
serverless.addServiceOutputSection('my section', 'content')
The example above will be displayed as:
$ serverless info
functions:
...
my section: content
To add a multi-line section:
serverless.addServiceOutputSection('my section', ['line 1', 'line 2'])
The example above will be displayed as:
$ serverless info
functions:
...
my section:
line 1
line 2
Plugins can signal deprecated features to users via logDeprecation():
serverless.logDeprecation(
'DEPRECATION_CODE',
'Feature X of my-plugin is deprecated. Please use Y instead.',
)
These deprecations will integrate with the deprecation system of the Serverless Framework.
Best practices:
OFFLINE_XXX.As shown in the examples above, the I/O API is injected in the constructor of plugins:
class MyPlugin {
constructor(serverless, cliOptions, { writeText, log, progress }) {
// ...
}
}
However, it is also possible to retrieve it from any JavaScript file by requiring the @serverless/utils package:
const { writeText, log, progress } = require('@serverless/utils/log')