diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b967580154..e092470bab 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -95,6 +95,7 @@ "prom-client": "^15.0.0", "uuid": "^9.0.0", "winston": "^3.2.1", + "winston-transport": "^4.7.0", "yaml": "^2.0.0", "zen-observable": "^0.10.0", "zod": "^3.22.4" diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 81897a96f8..5d24ac7cc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alp import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { WinstonLogger } from './logger'; +import { BackstageLoggerTransport, WinstonLogger } from './logger'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -98,9 +98,11 @@ const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { const createStepLogger = ({ task, step, + rootLogger, }: { task: TaskContext; step: TaskStep; + rootLogger: winston.Logger; }) => { const stepLogStream = new PassThrough(); stepLogStream.on('data', async data => { @@ -117,8 +119,8 @@ const createStepLogger = ({ winston.format.simple(), ), transports: [ - new winston.transports.Console(), new winston.transports.Stream({ stream: stepLogStream }), + new BackstageLoggerTransport(rootLogger), ], }); @@ -262,7 +264,11 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const action: TemplateAction = this.options.actionRegistry.get(step.action); - const { taskLogger, streamLogger } = createStepLogger({ task, step }); + const { taskLogger, streamLogger } = createStepLogger({ + task, + step, + rootLogger: this.options.logger, + }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index b96b747077..9a318d66b7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -19,13 +19,8 @@ import { } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { Format, TransformableInfo } from 'logform'; -import { - Logger, - format, - createLogger, - transports, - transport as Transport, -} from 'winston'; +import Transport, { TransportStreamOptions } from 'winston-transport'; +import { Logger, format, createLogger, transports } from 'winston'; /** * Escapes a given string to be used inside a RegExp. @@ -43,6 +38,42 @@ interface WinstonLoggerOptions { transports: Transport[]; } +// This is a workaround for being able to preserve the log format of the root logger. +// Will revisit all of this implementation once we can break the router to use only `LoggerService`. +export class BackstageLoggerTransport extends Transport { + constructor( + private readonly backstageLogger: LoggerService, + opts?: TransportStreamOptions, + ) { + super(opts); + } + + log(info: unknown, callback: VoidFunction) { + if (typeof info !== 'object' || info === null) { + callback(); + return; + } + const { level, message, ...meta } = info as JsonObject; + switch (level) { + case 'error': + this.backstageLogger.error(String(message), meta); + break; + case 'warn': + this.backstageLogger.warn(String(message), meta); + break; + case 'info': + this.backstageLogger.info(String(message), meta); + break; + case 'debug': + this.backstageLogger.debug(String(message), meta); + break; + default: + this.backstageLogger.info(String(message), meta); + } + callback(); + } +} + export class WinstonLogger implements RootLoggerService { #winston: Logger; #addRedactions?: (redactions: Iterable) => void; @@ -78,11 +109,12 @@ export class WinstonLogger implements RootLoggerService { return { format: format(info => { - if (redactionPattern && typeof info.message === 'string') { - info.message = info.message.replace(redactionPattern, '[REDACTED]'); - } - if (redactionPattern && typeof info.stack === 'string') { - info.stack = info.stack.replace(redactionPattern, '[REDACTED]'); + if (redactionPattern) { + for (const [key, value] of Object.entries(info)) { + if (typeof value === 'string') { + info[key] = value.replace(redactionPattern, '[REDACTED]'); + } + } } return info; })(), diff --git a/yarn.lock b/yarn.lock index af96b924bd..e92793be01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6591,6 +6591,7 @@ __metadata: uuid: ^9.0.0 wait-for-expect: ^3.0.2 winston: ^3.2.1 + winston-transport: ^4.7.0 yaml: ^2.0.0 zen-observable: ^0.10.0 zod: ^3.22.4