diff --git a/.changeset/hip-tables-prove.md b/.changeset/hip-tables-prove.md new file mode 100644 index 0000000000..bc2c4e83d6 --- /dev/null +++ b/.changeset/hip-tables-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Updated the `mockServices.rootLogger` options to accept a single level option instead. diff --git a/.changeset/selfish-frogs-punch.md b/.changeset/selfish-frogs-punch.md new file mode 100644 index 0000000000..6484576af8 --- /dev/null +++ b/.changeset/selfish-frogs-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: Switched out `LogMeta` type for `JsonObject`. diff --git a/.changeset/wild-donkeys-rescue.md b/.changeset/wild-donkeys-rescue.md new file mode 100644 index 0000000000..43dc3743bb --- /dev/null +++ b/.changeset/wild-donkeys-rescue.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-app-api': patch +--- + +Updated usages of `LogMeta`. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 8dc85d1728..e72959b1b2 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -18,10 +18,10 @@ import { HelmetOptions } from 'helmet'; import * as http from 'http'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { LogMeta } from '@backstage/backend-plugin-api'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginCacheManager } from '@backstage/backend-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; @@ -267,21 +267,21 @@ export class WinstonLogger implements RootLoggerService { // (undocumented) addRedactions(redactions: Iterable): void; // (undocumented) - child(meta: LogMeta): LoggerService; + child(meta: JsonObject): LoggerService; static colorFormat(): Format; static create(options: WinstonLoggerOptions): WinstonLogger; // (undocumented) - debug(message: string, meta?: LogMeta): void; + debug(message: string, meta?: JsonObject): void; // (undocumented) - error(message: string, meta?: LogMeta): void; + error(message: string, meta?: JsonObject): void; // (undocumented) - info(message: string, meta?: LogMeta): void; + info(message: string, meta?: JsonObject): void; static redacter(): { format: Format; add: (redactions: Iterable) => void; }; // (undocumented) - warn(message: string, meta?: LogMeta): void; + warn(message: string, meta?: JsonObject): void; } // @public (undocumented) @@ -291,7 +291,7 @@ export interface WinstonLoggerOptions { // (undocumented) level: string; // (undocumented) - meta?: LogMeta; + meta?: JsonObject; // (undocumented) transports: transport[]; } diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index d32b816523..06a189508b 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -16,9 +16,9 @@ import { LoggerService, - LogMeta, RootLoggerService, } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { Format, TransformableInfo } from 'logform'; import { Logger, @@ -33,7 +33,7 @@ import { escapeRegExp } from '../lib/escapeRegExp'; * @public */ export interface WinstonLoggerOptions { - meta?: LogMeta; + meta?: JsonObject; level: string; format: Format; transports: Transport[]; @@ -150,23 +150,23 @@ export class WinstonLogger implements RootLoggerService { this.#addRedactions = addRedactions; } - error(message: string, meta?: LogMeta): void { + error(message: string, meta?: JsonObject): void { this.#winston.error(message, meta); } - warn(message: string, meta?: LogMeta): void { + warn(message: string, meta?: JsonObject): void { this.#winston.warn(message, meta); } - info(message: string, meta?: LogMeta): void { + info(message: string, meta?: JsonObject): void { this.#winston.info(message, meta); } - debug(message: string, meta?: LogMeta): void { + debug(message: string, meta?: JsonObject): void { this.#winston.debug(message, meta); } - child(meta: LogMeta): LoggerService { + child(meta: JsonObject): LoggerService { return new WinstonLogger(this.#winston.child(meta)); } diff --git a/packages/backend-common/src/logging/loggerToWinstonLogger.ts b/packages/backend-common/src/logging/loggerToWinstonLogger.ts index f15bd5be81..4ba88358d2 100644 --- a/packages/backend-common/src/logging/loggerToWinstonLogger.ts +++ b/packages/backend-common/src/logging/loggerToWinstonLogger.ts @@ -15,6 +15,7 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; import { Logger as WinstonLogger, createLogger } from 'winston'; import Transport, { TransportStreamOptions } from 'winston-transport'; @@ -31,7 +32,7 @@ class BackstageLoggerTransport extends Transport { callback(); return; } - const { level, message, ...meta } = info as { [name: string]: unknown }; + const { level, message, ...meta } = info as JsonObject; switch (level) { case 'error': this.backstageLogger.error(String(message), meta); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 4b8e0298ec..276a4c15b1 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -8,6 +8,7 @@ import { Config } from '@backstage/config'; import { Handler } from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; +import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -311,22 +312,17 @@ export interface LifecycleServiceShutdownOptions { // @public export interface LoggerService { // (undocumented) - child(meta: LogMeta): LoggerService; + child(meta: JsonObject): LoggerService; // (undocumented) - debug(message: string, meta?: Error | LogMeta): void; + debug(message: string, meta?: Error | JsonObject): void; // (undocumented) - error(message: string, meta?: Error | LogMeta): void; + error(message: string, meta?: Error | JsonObject): void; // (undocumented) - info(message: string, meta?: Error | LogMeta): void; + info(message: string, meta?: Error | JsonObject): void; // (undocumented) - warn(message: string, meta?: Error | LogMeta): void; + warn(message: string, meta?: Error | JsonObject): void; } -// @public (undocumented) -export type LogMeta = { - [name: string]: unknown; -}; - // @public (undocumented) export interface PermissionsService extends PermissionEvaluator {} diff --git a/packages/backend-plugin-api/src/services/definitions/LoggerService.ts b/packages/backend-plugin-api/src/services/definitions/LoggerService.ts index 269dfa0166..2e4edc60be 100644 --- a/packages/backend-plugin-api/src/services/definitions/LoggerService.ts +++ b/packages/backend-plugin-api/src/services/definitions/LoggerService.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -/** - * @public - */ -export type LogMeta = { [name: string]: unknown }; +import { JsonObject } from '@backstage/types'; /** * A service that provides a logging facility. @@ -25,10 +22,10 @@ export type LogMeta = { [name: string]: unknown }; * @public */ export interface LoggerService { - error(message: string, meta?: Error | LogMeta): void; - warn(message: string, meta?: Error | LogMeta): void; - info(message: string, meta?: Error | LogMeta): void; - debug(message: string, meta?: Error | LogMeta): void; + error(message: string, meta?: Error | JsonObject): void; + warn(message: string, meta?: Error | JsonObject): void; + info(message: string, meta?: Error | JsonObject): void; + debug(message: string, meta?: Error | JsonObject): void; - child(meta: LogMeta): LoggerService; + child(meta: JsonObject): LoggerService; } diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 720b4d634d..35c2f0251c 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -30,7 +30,7 @@ export type { LifecycleServiceShutdownHook, LifecycleServiceShutdownOptions, } from './LifecycleService'; -export type { LoggerService, LogMeta } from './LoggerService'; +export type { LoggerService } from './LoggerService'; export type { PermissionsService } from './PermissionsService'; export type { PluginMetadataService } from './PluginMetadataService'; export type { RootHttpRouterService } from './RootHttpRouterService'; diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 672c574cb2..d99cf51866 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -91,14 +91,7 @@ export namespace mockServices { export namespace rootLogger { // (undocumented) export type Options = { - levels: - | boolean - | { - error: boolean; - warn: boolean; - info: boolean; - debug: boolean; - }; + level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; const // (undocumented) factory: (options?: Options | undefined) => ServiceFactory; diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts b/packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts new file mode 100644 index 0000000000..a3cbb5c384 --- /dev/null +++ b/packages/backend-test-utils/src/next/services/MockRootLoggerService.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2023 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 { MockRootLoggerService } from './MockRootLoggerService'; + +describe('MockRootLoggerService', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should be silent by default', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create(); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.info).not.toHaveBeenCalled(); + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('shuld be able to set none level', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create({ level: 'none' }); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).not.toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.info).not.toHaveBeenCalled(); + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('shuld be able to set error level', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create({ level: 'error' }); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).toHaveBeenCalled(); + expect(console.warn).not.toHaveBeenCalled(); + expect(console.info).not.toHaveBeenCalled(); + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('shuld be able to set warn level', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create({ level: 'warn' }); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalled(); + expect(console.info).not.toHaveBeenCalled(); + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('shuld be able to set info level', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create({ level: 'info' }); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalled(); + expect(console.info).toHaveBeenCalled(); + expect(console.debug).not.toHaveBeenCalled(); + }); + + it('shuld be able to set debug level', () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(console, 'info').mockImplementation(() => {}); + jest.spyOn(console, 'debug').mockImplementation(() => {}); + + const logger = MockRootLoggerService.create({ level: 'debug' }); + logger.error('error'); + logger.warn('warn'); + logger.info('info'); + logger.debug('debug'); + + expect(console.error).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalled(); + expect(console.info).toHaveBeenCalled(); + expect(console.debug).toHaveBeenCalled(); + }); +}); diff --git a/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts b/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts index 780d330bfb..8b973eeeab 100644 --- a/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts +++ b/packages/backend-test-utils/src/next/services/MockRootLoggerService.ts @@ -16,58 +16,65 @@ import { LoggerService, - LogMeta, RootLoggerService, } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; import type { mockServices } from './mockServices'; -export class MockRootLoggerService implements RootLoggerService { - #levels: Exclude; - #meta: LogMeta; +const levels = { + none: 0, + error: 1, + warn: 2, + info: 3, + debug: 4, +}; - error(message: string, meta?: LogMeta | Error | undefined): void { +export class MockRootLoggerService implements RootLoggerService { + #level: number; + #meta: JsonObject; + + static create( + options?: mockServices.rootLogger.Options, + ): MockRootLoggerService { + const level = options?.level ?? 'none'; + if (!(level in levels)) { + throw new Error(`Invalid log level '${level}'`); + } + return new MockRootLoggerService(levels[level], {}); + } + + error(message: string, meta?: JsonObject | Error | undefined): void { this.#log('error', message, meta); } - warn(message: string, meta?: LogMeta | Error | undefined): void { + warn(message: string, meta?: JsonObject | Error | undefined): void { this.#log('warn', message, meta); } - info(message: string, meta?: LogMeta | Error | undefined): void { + info(message: string, meta?: JsonObject | Error | undefined): void { this.#log('info', message, meta); } - debug(message: string, meta?: LogMeta | Error | undefined): void { + debug(message: string, meta?: JsonObject | Error | undefined): void { this.#log('debug', message, meta); } - child(meta: LogMeta): LoggerService { - return new MockRootLoggerService(this.#levels, { ...this.#meta, ...meta }); + child(meta: JsonObject): LoggerService { + return new MockRootLoggerService(this.#level, { ...this.#meta, ...meta }); } - constructor( - levels: mockServices.rootLogger.Options['levels'], - meta: LogMeta, - ) { - if (typeof levels === 'boolean') { - this.#levels = { - error: levels, - debug: levels, - info: levels, - warn: levels, - }; - } else { - this.#levels = levels; - } + private constructor(level: number, meta: JsonObject) { + this.#level = level; this.#meta = meta; } #log( level: 'error' | 'warn' | 'info' | 'debug', message: string, - meta?: LogMeta | Error | undefined, + meta?: JsonObject | Error | undefined, ) { - if (this.#levels[level]) { + const levelValue = levels[level] ?? 0; + if (levelValue <= this.#level) { const labels = Object.entries(this.#meta) .map(([key, value]) => `${key}=${value}`) .join(','); diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 5f771f13c5..2a56bd78b8 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -67,13 +67,11 @@ export namespace mockServices { } export function rootLogger(options?: rootLogger.Options): LoggerService { - return new MockRootLoggerService(options?.levels ?? false, {}); + return MockRootLoggerService.create(options); } export namespace rootLogger { export type Options = { - levels: - | boolean - | { error: boolean; warn: boolean; info: boolean; debug: boolean }; + level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; export const factory = simpleFactory(coreServices.rootLogger, rootLogger);