diff --git a/src/builders.ts b/src/builders.ts index ee655e94..fa4b89b2 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -37,6 +37,7 @@ import { trySanitizeGitUrl } from './_helpers' import { wsAnchoredPackage } from './_yarnCompat' +import { type Logger as ConsoleLoggger } from './logger' import { PropertyNames, PropertyValueBool } from './properties' type ManifestFetcher = (pkg: Package) => Promise @@ -61,7 +62,7 @@ export class BomBuilder { shortPURLs: boolean gatherLicenseTexts: boolean - console: Console + console: ConsoleLoggger constructor ( toolBuilder: BomBuilder['toolBuilder'], @@ -134,7 +135,7 @@ export class BomBuilder { )) { component.licenses.forEach(setLicensesDeclared) - this.console.info('INFO | add component for %s/%s@%s', + this.console.info('add component for %s/%s@%s', component.group ?? '-', component.name, component.version ?? '-' @@ -259,7 +260,7 @@ export class BomBuilder { const component = this.componentBuilder.makeComponent( manifestC as normalizePackageData.Package, type) if (component === undefined) { - this.console.debug('DEBUG | skip broken component: %j', locator) + this.console.debug('skip broken component: %j', locator) return undefined } @@ -382,15 +383,15 @@ export class BomBuilder { fetchManifest, fetchLicenseEvidences) if (_depC === false) { // shall be skipped - this.console.debug('DEBUG | skip impossible component %j', _depIDN) + this.console.debug('skip impossible component %j', _depIDN) continue // for-loop } if (_depC === undefined) { depComponent = new DummyComponent(ComponentType.Library, `InterferedDependency.${_depIDN}`) - this.console.warn('WARN | InterferedDependency %j', _depIDN) + this.console.warn('InterferedDependency %j', _depIDN) } else { depComponent = _depC - this.console.debug('DEBUG | built component %j: %j', _depIDN, depComponent) + this.console.debug('built component %j: %j', _depIDN, depComponent) } yield depComponent knownComponents.set(depPkg.locatorHash, depComponent) diff --git a/src/commands.ts b/src/commands.ts index f9f2b2ba..58a85a34 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -138,8 +138,8 @@ export class MakeSbomCommand extends Command { const projectDir = this.context.cwd const myConsole = makeConsoleLogger(this.verbosity, this.context) - myConsole.debug('DEBUG | YARN_VERSION:', YarnVersionTuple) - myConsole.debug('DEBUG | options: %j', { + myConsole.debug('YARN_VERSION:', YarnVersionTuple) + myConsole.debug('options: %j', { specVersion: this.specVersion, outputFormat: this.outputFormat, outputFile: this.outputFile, @@ -152,20 +152,20 @@ export class MakeSbomCommand extends Command { projectDir }) - myConsole.info('INFO | gathering project & workspace ...') + myConsole.info('gathering project & workspace ...') const { project, workspace } = await Project.find( await Configuration.find(projectDir, this.context.plugins), projectDir) if (workspace === null) { throw new Error(`missing workspace for project ${project.cwd} in ${projectDir}`) } - myConsole.debug('DEBUG | project:', project.cwd) - myConsole.debug('DEBUG | workspace:', workspace.cwd) + myConsole.debug('project:', project.cwd) + myConsole.debug('workspace:', workspace.cwd) await workspace.project.restoreInstallState() const extRefFactory = new PJF.ExternalReferenceFactory() - myConsole.log('LOG | gathering BOM data ...') + myConsole.log('gathering BOM data ...') const bom = await (new BomBuilder( new PJB.ToolBuilder(extRefFactory), new PJB.ComponentBuilder( @@ -198,7 +198,7 @@ export class MakeSbomCommand extends Command { break } - myConsole.log('LOG | serializing BOM ...') + myConsole.log('serializing BOM ...') const serialized = serializer.serialize(bom, { sortLists: this.outputReproducible, space: 2 @@ -206,16 +206,16 @@ export class MakeSbomCommand extends Command { // @TODO validate BOM - see https://github.com/CycloneDX/cyclonedx-node-yarn/issues/23 - myConsole.log('LOG | writing BOM to: %s', this.outputFile) + myConsole.log('writing BOM to: %s', this.outputFile) const written = await writeAllSync( this.outputFile === OutputStdOut ? process.stdout.fd : xfs.openSync(npath.toPortablePath(npath.resolve(process.cwd(), this.outputFile)), 'w'), serialized ) - myConsole.info('INFO | wrote %d bytes to: %s', written, this.outputFile) + myConsole.info('wrote %d bytes to: %s', written, this.outputFile) - return written > 0 + return !myConsole.didLogError && written > 0 ? ExitCode.SUCCESS : ExitCode.FAILURE } diff --git a/src/logger.ts b/src/logger.ts index c4f2a99f..3f13f495 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -19,23 +19,46 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import type { BaseContext } from 'clipanion' -function noop (): void { - // do nothing -} +export class Logger { + /** `true` if this logger instance was used to log any error message. */ + public didLogError = false -export function makeConsoleLogger (level: number, context: BaseContext): Console { - // all output shall be bound to stdError - stdOut is for result output only - const myConsole = new console.Console(context.stderr, context.stderr) + /** + * @param console Native console + * @param logLevel Minimum log level for which outputting messages is desired. + */ + constructor (private readonly console: Console, private readonly logLevel: number) {} - if (level < 3) { - myConsole.debug = noop - if (level < 2) { - myConsole.info = noop - if (level < 1) { - myConsole.log = noop - } + private logWithPrefix (prefix: string, messageLogLevel: number, message: string, ...args: unknown[]): void { + if (this.logLevel >= messageLogLevel) { + this.console.log(`${prefix} | ${message}`, ...args) } } - return myConsole + public info (message: string, ...args: unknown[]): void { + this.logWithPrefix('INFO ', 2, message, ...args) + } + + public debug (message: string, ...args: unknown[]): void { + this.logWithPrefix('DEBUG', 3, message, ...args) + } + + public warn (message: string, ...args: unknown[]): void { + this.logWithPrefix('WARN ', 0, message, ...args) + } + + public log (message: string, ...args: unknown[]): void { + this.logWithPrefix('LOG ', 1, message, ...args) + } + + public error (message: string, ...args: unknown[]): void { + this.didLogError = true + this.logWithPrefix('ERROR', 0, message, ...args) + } +} + +export function makeConsoleLogger (level: number, context: BaseContext): Logger { + // all output shall be bound to stdError - stdOut is for result output only + const myConsole = new console.Console(context.stderr, context.stderr) + return new Logger(myConsole, level) }