KwirthLog logo

Backstage KwirthLog for Backstage Plugin

Created by Julio Fernandez

KwirthLog for Backstage brings live Kubernetes logs into your Backstage entity pages. It streams logs in real time so you can investigate issues without leaving Backstage. You get a focused view that follows the entity you are working on. The goal is simple. Keep context in one place while you debug.

The plugin links entities to their pods using standard Backstage Kubernetes annotations or label selectors. It looks across all clusters that you have added to Backstage. You choose a cluster and a namespace. Then you select the pod and the container you care about. The view merges lines from every selected source and keeps them in time order. The backend enforces access rules. Teams can scope visibility by pod or namespace or cluster.

Typical use cases include on call triage. Verifying a rollout right after a deploy. Chasing a spike in errors in production. Watching a job run to completion. Comparing behavior in staging versus production. It reduces context switching from kubectl or other tools. Your team can stay inside Backstage while they trace issues.

KwirthLog relies on Kwirth running in your clusters. Kwirth exports logs so Backstage can stream them. This plugin focuses on clear log viewing. Configuration and permissions live on the backend side.

Installation Instructions

These instructions apply to self-hosted Backstage only.

Install Kwirth on your clusters

KwirthLog needs Kwirth running in your clusters. Use Kwirth version 0.3.160 or newer. Install Kwirth in each cluster you want to read logs from.

Install the backend plugin legacy backend

Add the package

Copy
# From your Backstage root directory
yarn --cwd packages/backend add @jfvilas/plugin-kwirth-backend

Wire the router

Create a backend plugin file.

Copy
// packages/backend/src/plugins/kwirth.ts
import { createRouter } from '@jfvilas/plugin-kwirth-backend';
import { PluginEnvironment } from '../types';
import { Router } from 'express';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  // Adjust the options to match your app environment
  // These keys are common in Backstage routers
  return await createRouter({
    logger: env.logger,
    config: env.config,
    discovery: env.discovery,
    tokenManager: env.tokenManager,
    permissions: env.permissions,
  });
}

Register the router in the legacy backend index.

Copy
// packages/backend/src/index.ts
import kwirth from './plugins/kwirth';

// inside your main bootstrap
async function main() {
  // create the PluginEnvironment as in your app
  const env = useHotMemoize(module, () => createEnv('backend'));
  const apiRouter = Router();

  // other plugin mounts

  apiRouter.use(
    '/kwirth',
    await kwirth(env),
  );

  // mount apiRouter under the service builder
  // builder.addRouter('/api', apiRouter) or equivalent in your app
}

Mount path under api varies by app. The usual pattern places it under the api router. Example final path becomes api slash kwirth.

Install the backend plugin new backend system

If your app uses the new backend system with createBackend, add a small module that mounts the legacy router.

Add the package

Copy
# From your Backstage root directory
yarn --cwd packages/backend add @jfvilas/plugin-kwirth-backend

Register a backend module

Copy
// packages/backend/src/modules/kwirth.ts
import { createBackendModule, coreServices } from '@backstage/backend-plugin-api';
import { createRouter } from '@jfvilas/plugin-kwirth-backend';

export const kwirthModule = createBackendModule({
  pluginId: 'kwirth',
  moduleId: 'router',
  register(env) {
    env.registerInit({
      deps: {
        logger: coreServices.logger,
        config: coreServices.rootConfig,
        discovery: coreServices.discovery,
        auth: coreServices.auth,
        permissions: coreServices.permissions,
        httpRouter: coreServices.httpRouter,
        tokenManager: coreServices.tokenManager,
      },
      async init({ logger, config, discovery, auth, permissions, httpRouter, tokenManager }) {
        const router = await createRouter({
          logger,
          config,
          discovery,
          auth,
          permissions,
          tokenManager,
        });
        httpRouter.use('/kwirth', router);
      },
    });
  },
});

Add the module to your backend.

Copy
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
import { kwirthModule } from './modules/kwirth';

const backend = createBackend();

// other modules
backend.add(kwirthModule());

backend.start();

Path mounts under httpRouter. Many apps expose this under api. Example final path becomes api slash kwirth.

Install the frontend plugin

Add the packages

Copy
# From your Backstage root directory
yarn --cwd packages/app add @jfvilas/plugin-kwirth-log @jfvilas/plugin-kwirth-common @jfvilas/kwirth-common

Add the tab to your Entity page

Import the components.

Copy
// packages/app/src/components/catalog/EntityPage.tsx
import { EntityKwirthLogContent } from '@jfvilas/plugin-kwirth-log';
import { isKwirthAvailable } from '@jfvilas/plugin-kwirth-common';

Add a route in your entity layout.

Copy
// packages/app/src/components/catalog/EntityPage.tsx
const serviceEntityPage = (
  <EntityLayout>
    {/* other tabs */}
    <EntityLayout.Route if={isKwirthAvailable} path="/kwirthlog" title="KwirthLog">
      <EntityKwirthLogContent enableRestart={false} />
    </EntityLayout.Route>
  </EntityLayout>
);

Do the same for other entity page variants in this file if needed.

Restart the Backstage app.

Configure catalog entities

You must tag your entities so the plugin can match them to Kubernetes pods.

Strategy 1 one to one with kubernetes id

Add an annotation in your catalog info file.

Copy
# catalog-info.yaml
metadata:
  annotations:
    backstage.io/kubernetes-id: entity001

Add matching labels in your Kubernetes objects. Place the label on the Deployment and also on the pod template.

Copy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ijkl
  labels:
    backstage.io/kubernetes-id: ijkl
spec:
  selector:
    matchLabels:
      app: ijkl
  template:
    metadata:
      name: ijkl-pod
      labels:
        app: ijkl
        backstage.io/kubernetes-id: ijkl
    spec:
      containers:
        - name: ijkl
          image: your-OCI-image

Strategy 2 using a label selector

Add a label selector annotation in your catalog info file. Use Kubernetes label selector syntax.

Copy
# catalog-info.yaml
metadata:
  annotations:
    backstage.io/kubernetes-label-selector: app=core,artifact=backend

If you use a selector you do not need to add new labels beyond what the selector matches. Ensure your pods already carry those labels.

Notes on permissions and clusters

The backend plugin enforces access by user. You can limit clusters, namespaces, and pods in backend configuration. The plugin uses the clusters you have added to Backstage Kubernetes. Ensure those clusters are reachable by the backend and by Kwirth.

Version hints

Match KwirthLog with a compatible Kwirth server. KwirthLog requires Kwirth version 0.3.160 or newer. If you hit version issues, upgrade Kwirth and the plugin to the versions listed by the maintainer.

Changelog

This changelog is produced from commits made to the KwirthLog plugin since 6 months ago, and based on the code located here. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.

Features

  • Add label selector annotation support 1 month ago
  • Implement Backstage alerts 3 months ago

UI

  • Update cards visualization 1 month ago
  • Update KubeLog card view 3 months ago
  • Remove unused fonts Fix objects Add restart option Improve UI 4 months ago
  • Update KwirthLog logo 3 weeks ago

Documentation

  • Update docs 1 month ago
  • Update install docs 2 months ago
  • Update docs info 2 months ago
  • Update docs 2 months ago

Publishing

  • Publish on backstage io 3 weeks ago
  • Update logo URL in publish yaml 3 weeks ago

Chore

  • New git config 2 months ago

Set up Backstage in minutes with Roadie