chore: duplicate some of the logging work

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-03-11 16:19:10 +01:00
parent e9663a9cde
commit 9f03f04916
4 changed files with 211 additions and 27 deletions
+26 -26
View File
@@ -1,18 +1,30 @@
{
"name": "@backstage/plugin-scaffolder-backend",
"description": "The Backstage backend plugin that helps you create new things",
"version": "1.22.0-next.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"description": "The Backstage backend plugin that helps you create new things",
"backstage": {
"role": "backend-plugin"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/scaffolder-backend"
},
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"alpha": [
@@ -23,30 +35,23 @@
]
}
},
"backstage": {
"role": "backend-plugin"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/scaffolder-backend"
},
"keywords": [
"backstage"
"files": [
"dist",
"migrations",
"config.d.ts",
"assets"
],
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"build:assets": "node scripts/build-nunjucks.js",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"clean": "backstage-cli package clean",
"build:assets": "node scripts/build-nunjucks.js"
"start": "backstage-cli package start",
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-common": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
@@ -82,6 +87,7 @@
"jsonschema": "^1.2.6",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"logform": "^2.3.2",
"luxon": "^3.0.0",
"nunjucks": "^3.2.3",
"p-limit": "^3.1.0",
@@ -106,11 +112,5 @@
"supertest": "^6.1.3",
"wait-for-expect": "^3.0.2"
},
"files": [
"dist",
"migrations",
"config.d.ts",
"assets"
],
"configSchema": "config.d.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 '@backstage/backend-app-api';
import { WinstonLogger } from './logger';
type NunjucksWorkflowRunnerOptions = {
workingDirectory: string;
@@ -374,6 +374,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
await action.handler({
input: iteration.input,
secrets: task.secrets ?? {},
// TODO(blam): move to LoggerService and away from Winston
logger: loggerToWinstonLogger(taskLogger),
logStream: streamLogger,
workspacePath,
@@ -0,0 +1,182 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 {
LoggerService,
RootLoggerService,
} 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';
/**
* Escapes a given string to be used inside a RegExp.
*
* Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
*/
const escapeRegExp = (text: string) => {
return text.replace(/[.*+?^${}(\)|[\]\\]/g, '\\$&');
};
interface WinstonLoggerOptions {
meta?: JsonObject;
level: string;
format: Format;
transports: Transport[];
}
export class WinstonLogger implements RootLoggerService {
#winston: Logger;
#addRedactions?: (redactions: Iterable<string>) => void;
/**
* Creates a {@link WinstonLogger} instance.
*/
static create(options: WinstonLoggerOptions): WinstonLogger {
const redacter = WinstonLogger.redacter();
let logger = createLogger({
level: options.level,
format: format.combine(redacter.format, options.format),
transports: options.transports ?? new transports.Console(),
});
if (options.meta) {
logger = logger.child(options.meta);
}
return new WinstonLogger(logger, redacter.add);
}
/**
* Creates a winston log formatter for redacting secrets.
*/
static redacter(): {
format: Format;
add: (redactions: Iterable<string>) => void;
} {
const redactionSet = new Set<string>();
let redactionPattern: RegExp | undefined = undefined;
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]');
}
return info;
})(),
add(newRedactions) {
let added = 0;
for (const redactionToTrim of newRedactions) {
// Trimming the string ensures that we don't accdentally get extra
// newlines or other whitespace interfering with the redaction; this
// can happen for example when using string literals in yaml
const redaction = redactionToTrim.trim();
// Exclude secrets that are empty or just one character in length. These
// typically mean that you are running local dev or tests, or using the
// --lax flag which sets things to just 'x'.
if (redaction.length <= 1) {
continue;
}
if (!redactionSet.has(redaction)) {
redactionSet.add(redaction);
added += 1;
}
}
if (added > 0) {
const redactions = Array.from(redactionSet)
.map(r => escapeRegExp(r))
.join('|');
redactionPattern = new RegExp(`(${redactions})`, 'g');
}
},
};
}
/**
* Creates a pretty printed winston log formatter.
*/
static colorFormat(): Format {
const colorizer = format.colorize();
return format.combine(
format.timestamp(),
format.colorize({
colors: {
timestamp: 'dim',
prefix: 'blue',
field: 'cyan',
debug: 'grey',
},
}),
format.printf((info: TransformableInfo) => {
const { timestamp, level, message, plugin, service, ...fields } = info;
const prefix = plugin || service;
const timestampColor = colorizer.colorize('timestamp', timestamp);
const prefixColor = colorizer.colorize('prefix', prefix);
const extraFields = Object.entries(fields)
.map(
([key, value]) =>
`${colorizer.colorize('field', `${key}`)}=${value}`,
)
.join(' ');
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
}),
);
}
private constructor(
winston: Logger,
addRedactions?: (redactions: Iterable<string>) => void,
) {
this.#winston = winston;
this.#addRedactions = addRedactions;
}
error(message: string, meta?: JsonObject): void {
this.#winston.error(message, meta);
}
warn(message: string, meta?: JsonObject): void {
this.#winston.warn(message, meta);
}
info(message: string, meta?: JsonObject): void {
this.#winston.info(message, meta);
}
debug(message: string, meta?: JsonObject): void {
this.#winston.debug(message, meta);
}
child(meta: JsonObject): LoggerService {
return new WinstonLogger(this.#winston.child(meta));
}
addRedactions(redactions: Iterable<string>) {
this.#addRedactions?.(redactions);
}
}
@@ -31,6 +31,7 @@ export type ActionContext<
TActionInput extends JsonObject,
TActionOutput extends JsonObject = JsonObject,
> = {
// TODO(blam): move this to LoggerService
logger: Logger;
/** @deprecated - use `ctx.logger` instead */
logStream: Writable;