diff --git a/automation/capitanodoc/capitanodoc.ts b/automation/capitanodoc/capitanodoc.ts index be21bb21..29303e0c 100644 --- a/automation/capitanodoc/capitanodoc.ts +++ b/automation/capitanodoc/capitanodoc.ts @@ -101,7 +101,7 @@ const capitanoDoc = { }, { title: 'Logs', - files: ['build/actions/logs.js'], + files: ['build/actions-oclif/logs.js'], }, { title: 'Network', diff --git a/doc/cli.markdown b/doc/cli.markdown index 2cd5e0bb..8af5a3c4 100644 --- a/doc/cli.markdown +++ b/doc/cli.markdown @@ -208,7 +208,7 @@ Users are encouraged to regularly update the balena CLI to the latest version. - Logs - - [logs <uuidOrDevice>](#logs-uuidordevice) + - [logs <device>](#logs-device) - Network @@ -1307,9 +1307,9 @@ answer "yes" to all questions (non interactive use) # Logs -## logs <uuidOrDevice> +## logs <device> -Use this command to show logs for a specific device. +Show logs for a specific device. By default, the command prints all log messages and exits. @@ -1322,30 +1322,38 @@ when this command is provided a local mode device. Logs from a single service can be displayed with the --service flag. Just system logs can be shown with the --system flag. Note that these flags can be used together. +Note: --service and --system flags must come after the device parameter, as per examples. + Examples: $ balena logs 23c73a1 $ balena logs 23c73a1 --tail - + $ balena logs 192.168.0.31 $ balena logs 192.168.0.31 --service my-service $ balena logs 192.168.0.31 --service my-service-1 --service my-service-2 - + $ balena logs 23c73a1.local --system $ balena logs 23c73a1.local --system --service my-service +### Arguments + +#### DEVICE + +device UUID, IP, or .local address + ### Options -#### --tail, -t +#### -t, --tail continuously stream output -#### --service, -s <service> +#### -s, --service SERVICE Reject logs not originating from this service. This can be used in combination with --system or other --service flags. -#### --system, -S +#### -S, --system Only show system logs. This can be used in combination with --service. diff --git a/lib/actions-oclif/logs.ts b/lib/actions-oclif/logs.ts new file mode 100644 index 00000000..850a69b6 --- /dev/null +++ b/lib/actions-oclif/logs.ts @@ -0,0 +1,183 @@ +/** + * @license + * Copyright 2016-2020 Balena Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { flags } from '@oclif/command'; +import Command from '../command'; +import * as cf from '../utils/common-flags'; +import { getBalenaSdk, stripIndent } from '../utils/lazy'; +import { LogMessage } from 'balena-sdk'; +import { IArg } from '@oclif/parser/lib/args'; + +interface FlagsDef { + tail?: boolean; + service?: string[]; + system?: boolean; + help: void; +} + +interface ArgsDef { + device: string; +} + +export default class LogsCmd extends Command { + public static description = stripIndent` + Show device logs. + + Show logs for a specific device. + + By default, the command prints all log messages and exits. + + To continuously stream output, and see new logs in real time, use the \`--tail\` option. + + If an IP or .local address is passed to this command, logs are displayed from + a local mode device with that address. Note that --tail is implied + when this command is provided a local mode device. + + Logs from a single service can be displayed with the --service flag. Just system logs + can be shown with the --system flag. Note that these flags can be used together. + + Note: --service and --system flags must come after the device parameter, as per examples. +`; + public static examples = [ + '$ balena logs 23c73a1', + '$ balena logs 23c73a1 --tail', + '', + '$ balena logs 192.168.0.31', + '$ balena logs 192.168.0.31 --service my-service', + '$ balena logs 192.168.0.31 --service my-service-1 --service my-service-2', + '', + '$ balena logs 23c73a1.local --system', + '$ balena logs 23c73a1.local --system --service my-service', + ]; + + public static args: Array> = [ + { + name: 'device', + description: 'device UUID, IP, or .local address', + required: true, + }, + ]; + + public static usage = 'logs '; + + public static flags: flags.Input = { + tail: flags.boolean({ + description: 'continuously stream output', + char: 't', + }), + service: flags.string({ + description: stripIndent` + Reject logs not originating from this service. + This can be used in combination with --system or other --service flags.`, + char: 's', + multiple: true, + }), + system: flags.boolean({ + description: + 'Only show system logs. This can be used in combination with --service.', + char: 'S', + }), + help: cf.help, + }; + + public static primary = true; + + public async run() { + const { args: params, flags: options } = this.parse( + LogsCmd, + ); + + const balena = getBalenaSdk(); + const { ExpectedError } = await import('../errors'); + const { serviceIdToName } = await import('../utils/cloud'); + const { displayDeviceLogs, displayLogObject } = await import( + '../utils/device/logs' + ); + const { validateIPAddress, validateDotLocalUrl } = await import( + '../utils/validation' + ); + const Logger = await import('../utils/logger'); + + const logger = Logger.getLogger(); + + const displayCloudLog = async (line: LogMessage) => { + if (!line.isSystem) { + let serviceName = await serviceIdToName(balena, line.serviceId); + if (serviceName == null) { + serviceName = 'Unknown service'; + } + displayLogObject( + { serviceName, ...line }, + logger, + options.system || false, + options.service, + ); + } else { + displayLogObject( + line, + logger, + options.system || false, + options.service, + ); + } + }; + + if ( + validateIPAddress(params.device) || + validateDotLocalUrl(params.device) + ) { + // Logs from local device + const { DeviceAPI } = await import('../utils/device/api'); + const deviceApi = new DeviceAPI(logger, params.device); + logger.logDebug('Checking we can access device'); + try { + await deviceApi.ping(); + } catch (e) { + throw new ExpectedError( + `Cannot access device at address ${params.device}. Device may not be in local mode.`, + ); + } + + logger.logDebug('Streaming logs'); + const logStream = await deviceApi.getLogStream(); + await displayDeviceLogs( + logStream, + logger, + options.system || false, + options.service, + ); + } else { + // Logs from cloud + await Command.checkLoggedIn(); + if (options.tail) { + const logStream = await balena.logs.subscribe(params.device, { + count: 100, + }); + // Never resolve (quit with CTRL-C), but reject on a broken connection + await new Promise((_resolve, reject) => { + logStream.on('line', displayCloudLog); + logStream.on('error', reject); + }); + } else { + const logMessages = await balena.logs.history(params.device); + for (const logMessage of logMessages) { + await displayCloudLog(logMessage); + } + } + } + } +} diff --git a/lib/actions/index.ts b/lib/actions/index.ts index 3a3adbb1..9b405b9e 100644 --- a/lib/actions/index.ts +++ b/lib/actions/index.ts @@ -17,7 +17,6 @@ limitations under the License. export * as config from './config'; export * as help from './help'; export * as local from './local'; -export * as logs from './logs'; export * as os from './os'; export * as push from './push'; export * as util from './util'; diff --git a/lib/actions/logs.ts b/lib/actions/logs.ts deleted file mode 100644 index 3dcdaa0c..00000000 --- a/lib/actions/logs.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* -Copyright 2016-2019 Balena - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import type { LogMessage } from 'balena-sdk'; -import type { CommandDefinition } from 'capitano'; - -import { getBalenaSdk, stripIndent } from '../utils/lazy'; -import { normalizeUuidProp } from '../utils/normalization'; -import { validateDotLocalUrl } from '../utils/validation'; - -export const logs: CommandDefinition< - { - uuidOrDevice: string; - }, - { - tail?: boolean; - service?: [string] | string; - system?: boolean; - } -> = { - signature: 'logs ', - description: 'show device logs', - help: stripIndent` - Use this command to show logs for a specific device. - - By default, the command prints all log messages and exits. - - To continuously stream output, and see new logs in real time, use the \`--tail\` option. - - If an IP or .local address is passed to this command, logs are displayed from - a local mode device with that address. Note that --tail is implied - when this command is provided a local mode device. - - Logs from a single service can be displayed with the --service flag. Just system logs - can be shown with the --system flag. Note that these flags can be used together. - - Examples: - - $ balena logs 23c73a1 - $ balena logs 23c73a1 --tail - - $ balena logs 192.168.0.31 - $ balena logs 192.168.0.31 --service my-service - $ balena logs 192.168.0.31 --service my-service-1 --service my-service-2 - - $ balena logs 23c73a1.local --system - $ balena logs 23c73a1.local --system --service my-service`, - options: [ - { - signature: 'tail', - description: 'continuously stream output', - boolean: true, - alias: 't', - }, - { - signature: 'service', - description: stripIndent` - Reject logs not originating from this service. - This can be used in combination with --system or other --service flags.`, - parameter: 'service', - alias: 's', - }, - { - signature: 'system', - alias: 'S', - boolean: true, - description: - 'Only show system logs. This can be used in combination with --service.', - }, - ], - primary: true, - async action(params, options) { - normalizeUuidProp(params); - const balena = getBalenaSdk(); - const { ExpectedError } = await import('../errors'); - const { serviceIdToName } = await import('../utils/cloud'); - const { displayDeviceLogs, displayLogObject } = await import( - '../utils/device/logs' - ); - const { validateIPAddress } = await import('../utils/validation'); - const { checkLoggedIn } = await import('../utils/patterns'); - const Logger = await import('../utils/logger'); - - const logger = Logger.getLogger(); - - const servicesToDisplay = - options.service != null - ? Array.isArray(options.service) - ? options.service - : [options.service] - : undefined; - - const displayCloudLog = async (line: LogMessage) => { - if (!line.isSystem) { - let serviceName = await serviceIdToName(balena, line.serviceId); - if (serviceName == null) { - serviceName = 'Unknown service'; - } - displayLogObject( - { serviceName, ...line }, - logger, - options.system || false, - servicesToDisplay, - ); - } else { - displayLogObject( - line, - logger, - options.system || false, - servicesToDisplay, - ); - } - }; - - if ( - validateIPAddress(params.uuidOrDevice) || - validateDotLocalUrl(params.uuidOrDevice) - ) { - const { DeviceAPI } = await import('../utils/device/api'); - const deviceApi = new DeviceAPI(logger, params.uuidOrDevice); - logger.logDebug('Checking we can access device'); - try { - await deviceApi.ping(); - } catch (e) { - throw new ExpectedError( - `Cannot access local mode device at address ${params.uuidOrDevice}`, - ); - } - const logStream = await deviceApi.getLogStream(); - await displayDeviceLogs( - logStream, - logger, - options.system || false, - servicesToDisplay, - ); - } else { - await checkLoggedIn(); - if (options.tail) { - const logStream = await balena.logs.subscribe(params.uuidOrDevice, { - count: 100, - }); - // Never resolve (quit with CTRL-C), but reject on a broken connection - await new Promise((_resolve, reject) => { - logStream.on('line', displayCloudLog); - logStream.on('error', reject); - }); - } else { - const logMessages = await balena.logs.history(params.uuidOrDevice); - for (const logMessage of logMessages) { - await displayCloudLog(logMessage); - } - } - } - }, -}; diff --git a/lib/app-capitano.ts b/lib/app-capitano.ts index e02ebb42..bdc44dfa 100644 --- a/lib/app-capitano.ts +++ b/lib/app-capitano.ts @@ -59,9 +59,6 @@ capitano.command(actions.config.inject); capitano.command(actions.config.reconfigure); capitano.command(actions.config.generate); -// ---------- Logs Module ---------- -capitano.command(actions.logs.logs); - // ---------- Preload Module ---------- capitano.command(actions.preload); diff --git a/lib/preparser.ts b/lib/preparser.ts index e2f975f7..f66a50de 100644 --- a/lib/preparser.ts +++ b/lib/preparser.ts @@ -175,6 +175,7 @@ export const convertedCommands = [ 'leave', 'login', 'logout', + 'logs', 'note', 'os:configure', 'scan', diff --git a/tests/commands/help.spec.ts b/tests/commands/help.spec.ts index f31c9b16..b4ee8840 100644 --- a/tests/commands/help.spec.ts +++ b/tests/commands/help.spec.ts @@ -9,7 +9,7 @@ Primary commands: help [command...] show help login login to balena push Start a remote build on the balena cloud build servers or a local mode device - logs show device logs + logs show device logs ssh [servicename] SSH into the host or application container of a device apps list all applications app display information about a single application