diff --git a/.changeset/ten-pots-walk.md b/.changeset/ten-pots-walk.md new file mode 100644 index 0000000000..cf540fd0e2 --- /dev/null +++ b/.changeset/ten-pots-walk.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-logs': patch +--- + +Creates a new module to make logging catalog errors simple. This module subscribes to catalog events and logs them. + +See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install +and configure the plugin. diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 61366a91f7..3bbbfd6716 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -177,3 +177,106 @@ here. Setting this value too low risks exhausting rate limits on external systems that are queried by processors, such as version control systems housing catalog-info files. + +## Subscribing to Catalog Errors + +Catalog errors are published to the [events plugin](https://github.com/backstage/backstage/tree/master/plugins/events-node): `@backstage/plugin-events-node`. You can subscribe to events and respond to errors, for example you may wish to log them. + +The first step is to add the events backend plugin to your Backstage application. Navigate to your Backstage application directory and add the plugin package. + +```ts +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-events-node +``` + +Now you can install the events backend plugin in your backend. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend/alpha')); +``` + +### Logging Errors + +If you want to log catalog errors you can install the `@backstage/plugin-catalog-backend-module-logs` module. + +Install the catalog logs module. + +```ts +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-logs +``` + +Add the module to your backend. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend-module-logs')); +``` + +This will log errors with a level of `warn`. + +You should now see logs as the catalog emits events. Example: + +``` +[1] 2024-06-07T00:00:28.787Z events warn Policy check failed for user:default/guest; caused by Error: Malformed envelope, /metadata/tags must be array entity=user:default/guest location=file:/Users/foobar/code/backstage-demo-instance/examples/org.yaml +``` + +### Custom Error Handling + +If you wish to handle catalog errors with specific logic different from logging the errors the following should help you get started. For example, you may wish to send a notification or create a ticket for someone to investigate. + +Create a backend module that subscribes to the catalog error events. The topic is `experimental.catalog.errors`. + +```ts title="packages/backend/src/index.ts" +import { CATALOG_ERRORS_TOPIC } from '@backstage/plugin-catalog-backend'; +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { eventsServiceRef, EventParams } from '@backstage/plugin-events-node'; + +interface EventsPayload { + entity: string; + location?: string; + errors: Error[]; +} + +interface EventsParamsWithPayload extends EventParams { + eventPayload: EventsPayload; +} + +const eventsModuleCatalogErrors = createBackendModule({ + pluginId: 'events', + moduleId: 'catalog-errors', + register(env) { + env.registerInit({ + deps: { + events: eventsServiceRef, + logger: coreServices.logger, + }, + async init({ events, logger }) { + events.subscribe({ + id: 'catalog', + topics: [CATALOG_ERRORS_TOPIC], + async onEvent(params: EventParams): Promise { + const event = params as EventsParamsWithPayload; + const { entity, location, errors } = event.eventPayload; + // Add custom logic here for responding to errors + for (const error of errors) { + logger.warn(error.message, { + entity, + location, + }); + } + }, + }); + }, + }); + }, +}); +``` + +Now install your module. + +```ts title="packages/backend/src/index.ts" +backend.add(eventsModuleCatalogErrors); +``` diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index 13fe496fc1..1489688720 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -197,13 +197,15 @@ cannot be parsed successfully, etc. There are two main ways that these errors are surfaced. -First, the catalog backend will produce detailed logs that should contain -sufficient information for a reader to find the causes for errors. Since these -logs are typically not easily found by end users, this can mainly be a useful +First, the catalog backend will emit events using the [events backend plugin](https://github.com/backstage/backstage/tree/master/plugins/events-node). You can subscribe to the events. The events should contain +sufficient information for a reader to find the causes for errors. See the [configuration documentation](./configuration.md#subscribing-to-catalog-errors) for how to subscribe and log these error events. +Since these events are typically not easily found by end users, this can mainly be a useful tool for Backstage operators who want to debug problems either with statically registered entities that are under their control, or to help end users find problems. +> Prior to Backstage version v1.26.0 and `@backstage/plugin-catalog-backend` v1.21.9 catalog errors were logged by default. + Second, for most classes of errors, the entity itself will contain a status field that describes the problem. The contents of this field is shown at the top of your entity page in Backstage, if you have placed the corresponding error diff --git a/plugins/catalog-backend-module-logs/.eslintrc.js b/plugins/catalog-backend-module-logs/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-logs/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-logs/README.md b/plugins/catalog-backend-module-logs/README.md new file mode 100644 index 0000000000..1771dbe02c --- /dev/null +++ b/plugins/catalog-backend-module-logs/README.md @@ -0,0 +1,8 @@ +# backstage-plugin-catalog-backend-module-logs + +A module that subscribes to catalog related events and logs them. + +## Getting started + +See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install +and configure the plugin. diff --git a/plugins/catalog-backend-module-logs/api-report.md b/plugins/catalog-backend-module-logs/api-report.md new file mode 100644 index 0000000000..0be2197c9a --- /dev/null +++ b/plugins/catalog-backend-module-logs/api-report.md @@ -0,0 +1,11 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-logs" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; + +// @public +const catalogModuleLogs: BackendFeatureCompat; +export default catalogModuleLogs; +``` diff --git a/plugins/catalog-backend-module-logs/catalog-info.yaml b/plugins/catalog-backend-module-logs/catalog-info.yaml new file mode 100644 index 0000000000..f2223174c3 --- /dev/null +++ b/plugins/catalog-backend-module-logs/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-logs + title: '@backstage/plugin-catalog-backend-module-logs' + description: A module that subscribes to catalog releated events and logs them. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json new file mode 100644 index 0000000000..858f7ec198 --- /dev/null +++ b/plugins/catalog-backend-module-logs/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-logs", + "version": "0.0.0", + "description": "A module that subscribes to catalog releated events and logs them.", + "backstage": { + "role": "backend-plugin-module", + "pluginId": "catalog", + "pluginPackage": "@backstage/plugin-catalog-backend" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-logs" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-events-node": "workspace:^" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-events-backend-test-utils": "workspace:^" + } +} diff --git a/plugins/catalog-backend-module-logs/src/index.ts b/plugins/catalog-backend-module-logs/src/index.ts new file mode 100644 index 0000000000..77a7d424eb --- /dev/null +++ b/plugins/catalog-backend-module-logs/src/index.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * A catalog module that logs catalog errors using the logger service. + * + * @packageDocumentation + */ +export { catalogModuleLogs as default } from './module'; diff --git a/plugins/catalog-backend-module-logs/src/module.test.ts b/plugins/catalog-backend-module-logs/src/module.test.ts new file mode 100644 index 0000000000..79f1b8c295 --- /dev/null +++ b/plugins/catalog-backend-module-logs/src/module.test.ts @@ -0,0 +1,45 @@ +/* + * 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { catalogModuleLogs } from './module'; +import { createServiceFactory } from '@backstage/backend-plugin-api'; +import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; + +describe('catalogModuleLogs', () => { + it('should be correctly wired and set up', async () => { + const events = new TestEventsService(); + const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: {}, + async factory({}) { + return events; + }, + }); + + await startTestBackend({ + features: [ + mockServices.logger.factory(), + eventsServiceFactory(), + catalogModuleLogs(), + ], + }); + + expect(events.subscribed).toHaveLength(1); + expect(events.subscribed[0].id).toEqual('catalog'); + }); +}); diff --git a/plugins/catalog-backend-module-logs/src/module.ts b/plugins/catalog-backend-module-logs/src/module.ts new file mode 100644 index 0000000000..bdc916e958 --- /dev/null +++ b/plugins/catalog-backend-module-logs/src/module.ts @@ -0,0 +1,67 @@ +/* + * 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { CATALOG_ERRORS_TOPIC } from '@backstage/plugin-catalog-backend'; +import { eventsServiceRef, EventParams } from '@backstage/plugin-events-node'; + +interface EventsPayload { + entity: string; + location?: string; + errors: Error[]; +} + +interface EventsParamsWithPayload extends EventParams { + eventPayload: EventsPayload; +} + +/** + * A catalog module that logs catalog errors using the logger service. + * + * @packageDocumentation + * @public + */ +export const catalogModuleLogs = createBackendModule({ + pluginId: 'catalog', + moduleId: 'logs', + register(env) { + env.registerInit({ + deps: { + events: eventsServiceRef, + logger: coreServices.logger, + }, + async init({ events, logger }) { + events.subscribe({ + id: 'catalog', + topics: [CATALOG_ERRORS_TOPIC], + async onEvent(params: EventParams): Promise { + const event = params as EventsParamsWithPayload; + const { entity, location, errors } = event.eventPayload; + for (const error of errors) { + logger.warn(error.message, { + entity, + location, + }); + } + }, + }); + }, + }); + }, +}); diff --git a/yarn.lock b/yarn.lock index 7f0bd34e43..af72fdba43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5440,6 +5440,19 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs": + version: 0.0.0-use.local + resolution: "@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs" + dependencies: + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-events-backend-test-utils": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" + languageName: unknown + linkType: soft + "@backstage/plugin-catalog-backend-module-msgraph@workspace:plugins/catalog-backend-module-msgraph": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-msgraph@workspace:plugins/catalog-backend-module-msgraph"