backend-defaults: refactor DefaultRootAuditorService
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -13,21 +13,18 @@ import type { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import type { PluginMetadataService } from '@backstage/backend-plugin-api';
|
||||
import type { Request as Request_2 } from 'express';
|
||||
import type { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import * as winston from 'winston';
|
||||
|
||||
// @public
|
||||
export type AuditorEvent = [
|
||||
eventId: string,
|
||||
meta: {
|
||||
plugin: string;
|
||||
severityLevel: AuditorServiceEventSeverityLevel;
|
||||
actor: AuditorEventActorDetails;
|
||||
meta?: JsonObject;
|
||||
request?: AuditorEventRequest;
|
||||
} & AuditorEventStatus,
|
||||
];
|
||||
export type AuditorEvent = {
|
||||
plugin: string;
|
||||
eventId: string;
|
||||
severityLevel: AuditorServiceEventSeverityLevel;
|
||||
actor: AuditorEventActorDetails;
|
||||
meta?: JsonObject;
|
||||
request?: AuditorEventRequest;
|
||||
} & AuditorEventStatus;
|
||||
|
||||
// @public (undocumented)
|
||||
export type AuditorEventActorDetails = {
|
||||
@@ -65,7 +62,7 @@ export type AuditorEventStatus =
|
||||
};
|
||||
|
||||
// @public
|
||||
export const auditorFieldFormat: Format;
|
||||
export type AuditorLogFunction = (event: AuditorEvent) => void | Promise<void>;
|
||||
|
||||
// @public
|
||||
export const auditorServiceFactory: ServiceFactory<
|
||||
@@ -77,7 +74,7 @@ export const auditorServiceFactory: ServiceFactory<
|
||||
// @public
|
||||
export class DefaultAuditorService implements AuditorService {
|
||||
static create(
|
||||
impl: DefaultRootAuditorService,
|
||||
logFn: AuditorLogFunction,
|
||||
deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
@@ -90,32 +87,25 @@ export class DefaultAuditorService implements AuditorService {
|
||||
): Promise<AuditorServiceEvent>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const defaultFormatter: Format;
|
||||
|
||||
// @public (undocumented)
|
||||
export class DefaultRootAuditorService {
|
||||
static create(options?: RootAuditorOptions): DefaultRootAuditorService;
|
||||
// @public
|
||||
export class WinstonRootAuditorService {
|
||||
static create(
|
||||
options?: WinstonRootAuditorServiceOptions,
|
||||
): WinstonRootAuditorService;
|
||||
// (undocumented)
|
||||
forPlugin(deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
plugin: PluginMetadataService;
|
||||
}): AuditorService;
|
||||
// (undocumented)
|
||||
log(auditorEvent: AuditorEvent): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type RootAuditorOptions =
|
||||
| {
|
||||
meta?: JsonObject;
|
||||
format?: Format;
|
||||
transports?: winston.transport[];
|
||||
}
|
||||
| {
|
||||
rootLogger: RootLoggerService;
|
||||
};
|
||||
export type WinstonRootAuditorServiceOptions = {
|
||||
meta?: JsonObject;
|
||||
format?: Format;
|
||||
transports?: winston.transport[];
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 } from '@backstage/backend-test-utils';
|
||||
import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
|
||||
const mockDeps = {
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
plugin: {
|
||||
getId: () => 'test',
|
||||
},
|
||||
};
|
||||
|
||||
describe('DefaultAuditorService', () => {
|
||||
it('creates a auditor instance with default options', () => {
|
||||
const auditor = DefaultAuditorService.create(jest.fn(), mockDeps);
|
||||
expect(auditor).toBeInstanceOf(DefaultAuditorService);
|
||||
});
|
||||
|
||||
it('should log a status "initiated" using createEvent', async () => {
|
||||
const logFn = jest.fn();
|
||||
const auditor = DefaultAuditorService.create(logFn, mockDeps);
|
||||
|
||||
await auditor.createEvent({
|
||||
eventId: 'test-event',
|
||||
});
|
||||
|
||||
expect(logFn).toHaveBeenCalledWith({
|
||||
eventId: 'test-event',
|
||||
status: 'initiated',
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a status "succeeded" using createEvent', async () => {
|
||||
const logFn = jest.fn();
|
||||
const auditor = DefaultAuditorService.create(logFn, mockDeps);
|
||||
|
||||
const auditorEvent = await auditor.createEvent({
|
||||
eventId: 'test-event',
|
||||
});
|
||||
|
||||
await auditorEvent.success();
|
||||
|
||||
expect(logFn).toHaveBeenCalledTimes(2);
|
||||
expect(logFn).toHaveBeenLastCalledWith({
|
||||
eventId: 'test-event',
|
||||
status: 'succeeded',
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a status "failed"', async () => {
|
||||
const logFn = jest.fn();
|
||||
const auditor = DefaultAuditorService.create(logFn, mockDeps);
|
||||
|
||||
const auditorEvent = await auditor.createEvent({
|
||||
eventId: 'test-event',
|
||||
});
|
||||
|
||||
const error = new Error('error');
|
||||
await auditorEvent.fail({ error });
|
||||
|
||||
expect(logFn).toHaveBeenCalledTimes(2);
|
||||
expect(logFn).toHaveBeenLastCalledWith({
|
||||
eventId: 'test-event',
|
||||
status: 'failed',
|
||||
error: error.toString(),
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should use root meta', async () => {
|
||||
const logFn = jest.fn();
|
||||
const auditor = DefaultAuditorService.create(logFn, mockDeps);
|
||||
|
||||
const auditorEvent = await auditor.createEvent({
|
||||
eventId: 'test-event',
|
||||
meta: {
|
||||
initiated: 'test',
|
||||
},
|
||||
});
|
||||
|
||||
await auditorEvent.success({ meta: { succeeded: 'test' } });
|
||||
|
||||
const error = new Error('error');
|
||||
await auditorEvent.fail({ error, meta: { failed: 'test' } });
|
||||
|
||||
expect(logFn).toHaveBeenCalledTimes(3);
|
||||
expect(logFn).toHaveBeenNthCalledWith(1, {
|
||||
eventId: 'test-event',
|
||||
status: 'initiated',
|
||||
meta: {
|
||||
initiated: 'test',
|
||||
},
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
expect(logFn).toHaveBeenNthCalledWith(2, {
|
||||
eventId: 'test-event',
|
||||
status: 'succeeded',
|
||||
meta: {
|
||||
initiated: 'test',
|
||||
succeeded: 'test',
|
||||
},
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
expect(logFn).toHaveBeenNthCalledWith(3, {
|
||||
eventId: 'test-event',
|
||||
status: 'failed',
|
||||
meta: {
|
||||
initiated: 'test',
|
||||
failed: 'test',
|
||||
},
|
||||
error: error.toString(),
|
||||
plugin: 'test',
|
||||
severityLevel: 'low',
|
||||
actor: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
+58
-138
@@ -23,14 +23,10 @@ import type {
|
||||
BackstageCredentials,
|
||||
HttpAuthService,
|
||||
PluginMetadataService,
|
||||
RootLoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import type { Request } from 'express';
|
||||
import type { Format } from 'logform';
|
||||
import * as winston from 'winston';
|
||||
import { WinstonLogger } from '../rootLogger';
|
||||
|
||||
/** @public */
|
||||
export type AuditorEventActorDetails = {
|
||||
@@ -82,56 +78,61 @@ export type AuditorEventOptions<TMeta extends JsonObject> = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type AuditorEvent = [
|
||||
eventId: string,
|
||||
meta: {
|
||||
plugin: string;
|
||||
severityLevel: AuditorServiceEventSeverityLevel;
|
||||
actor: AuditorEventActorDetails;
|
||||
meta?: JsonObject;
|
||||
request?: AuditorEventRequest;
|
||||
} & AuditorEventStatus,
|
||||
];
|
||||
|
||||
/** @public */
|
||||
export const defaultFormatter = winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.splat(),
|
||||
winston.format.json(),
|
||||
);
|
||||
export type AuditorEvent = {
|
||||
plugin: string;
|
||||
eventId: string;
|
||||
severityLevel: AuditorServiceEventSeverityLevel;
|
||||
actor: AuditorEventActorDetails;
|
||||
meta?: JsonObject;
|
||||
request?: AuditorEventRequest;
|
||||
} & AuditorEventStatus;
|
||||
|
||||
/**
|
||||
* Adds `isAuditorEvent` field
|
||||
*
|
||||
* Logging function used by the auditor.
|
||||
* @public
|
||||
*/
|
||||
export const auditorFieldFormat = winston.format(info => {
|
||||
return { ...info, isAuditorEvent: true };
|
||||
})();
|
||||
export type AuditorLogFunction = (event: AuditorEvent) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* A {@link @backstage/backend-plugin-api#AuditorService} implementation based on winston.
|
||||
* A {@link @backstage/backend-plugin-api#AuditorService} implementation that logs events using a provided callback.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export const auditorServiceFactory = createServiceFactory({
|
||||
* service: coreServices.auditor,
|
||||
* deps: {
|
||||
* logger: coreServices.logger,
|
||||
* auth: coreServices.auth,
|
||||
* httpAuth: coreServices.httpAuth,
|
||||
* plugin: coreServices.pluginMetadata,
|
||||
* },
|
||||
* factory({ logger, plugin, auth, httpAuth }) {
|
||||
* const auditLogger = logger.child({ isAuditorEvent: true });
|
||||
* return DefaultAuditorService.create(
|
||||
* event => auditLogger.info(`${event.plugin}.${event.eventId}`, event),
|
||||
* { plugin, auth, httpAuth },
|
||||
* );
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class DefaultAuditorService implements AuditorService {
|
||||
private readonly impl: DefaultRootAuditorService;
|
||||
private readonly logFn: AuditorLogFunction;
|
||||
private readonly auth: AuthService;
|
||||
private readonly httpAuth: HttpAuthService;
|
||||
private readonly plugin: PluginMetadataService;
|
||||
|
||||
private constructor(
|
||||
impl: DefaultRootAuditorService,
|
||||
logFn: AuditorLogFunction,
|
||||
deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
plugin: PluginMetadataService;
|
||||
},
|
||||
) {
|
||||
this.impl = impl;
|
||||
this.logFn = logFn;
|
||||
this.auth = deps.auth;
|
||||
this.httpAuth = deps.httpAuth;
|
||||
this.plugin = deps.plugin;
|
||||
@@ -141,21 +142,40 @@ export class DefaultAuditorService implements AuditorService {
|
||||
* Creates a {@link DefaultAuditorService} instance.
|
||||
*/
|
||||
static create(
|
||||
impl: DefaultRootAuditorService,
|
||||
logFn: AuditorLogFunction,
|
||||
deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
plugin: PluginMetadataService;
|
||||
},
|
||||
): DefaultAuditorService {
|
||||
return new DefaultAuditorService(impl, deps);
|
||||
return new DefaultAuditorService(logFn, deps);
|
||||
}
|
||||
|
||||
private async log<TMeta extends JsonObject>(
|
||||
options: AuditorEventOptions<TMeta>,
|
||||
): Promise<void> {
|
||||
const auditEvent = await this.reshapeAuditorEvent(options);
|
||||
this.impl.log(auditEvent);
|
||||
const { eventId, severityLevel = 'low', request, meta, ...rest } = options;
|
||||
|
||||
await this.logFn({
|
||||
plugin: this.plugin.getId(),
|
||||
eventId,
|
||||
severityLevel,
|
||||
actor: {
|
||||
actorId: await this.getActorId(request),
|
||||
ip: request?.ip,
|
||||
hostname: request?.hostname,
|
||||
userAgent: request?.get('user-agent'),
|
||||
},
|
||||
request: request
|
||||
? {
|
||||
url: request?.originalUrl,
|
||||
method: request?.method,
|
||||
}
|
||||
: undefined,
|
||||
meta: Object.keys(meta ?? {}).length === 0 ? undefined : meta,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
|
||||
async createEvent(
|
||||
@@ -175,7 +195,7 @@ export class DefaultAuditorService implements AuditorService {
|
||||
await this.log({
|
||||
...options,
|
||||
...params,
|
||||
error: params.error.toString(),
|
||||
error: String(params.error),
|
||||
meta: { ...options.meta, ...params?.meta },
|
||||
status: 'failed',
|
||||
});
|
||||
@@ -207,104 +227,4 @@ export class DefaultAuditorService implements AuditorService {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async reshapeAuditorEvent<T extends JsonObject>(
|
||||
options: AuditorEventOptions<T>,
|
||||
): Promise<AuditorEvent> {
|
||||
const { eventId, severityLevel = 'low', request, meta, ...rest } = options;
|
||||
|
||||
const auditEvent: AuditorEvent = [
|
||||
`${this.plugin.getId()}.${eventId}`,
|
||||
{
|
||||
plugin: this.plugin.getId(),
|
||||
severityLevel,
|
||||
actor: {
|
||||
actorId: await this.getActorId(request),
|
||||
ip: request?.ip,
|
||||
hostname: request?.hostname,
|
||||
userAgent: request?.get('user-agent'),
|
||||
},
|
||||
request: request
|
||||
? {
|
||||
url: request?.originalUrl,
|
||||
method: request?.method,
|
||||
}
|
||||
: undefined,
|
||||
meta: Object.keys(meta ?? {}).length === 0 ? undefined : meta,
|
||||
...rest,
|
||||
},
|
||||
];
|
||||
|
||||
return auditEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a root auditor.
|
||||
* If `rootLogger` is provided, the root auditor will default to using it.
|
||||
* Otherwise, a new logger will be created using the provided `meta`, `format`, and `transports`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type RootAuditorOptions =
|
||||
| {
|
||||
meta?: JsonObject;
|
||||
format?: Format;
|
||||
transports?: winston.transport[];
|
||||
}
|
||||
| {
|
||||
rootLogger: RootLoggerService;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export class DefaultRootAuditorService {
|
||||
private readonly impl: WinstonLogger;
|
||||
|
||||
private constructor(impl: WinstonLogger) {
|
||||
this.impl = impl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DefaultRootAuditorService} instance.
|
||||
*/
|
||||
static create(options?: RootAuditorOptions): DefaultRootAuditorService {
|
||||
if (options && 'rootLogger' in options) {
|
||||
return new DefaultRootAuditorService(
|
||||
options.rootLogger.child({ isAuditorEvent: true }) as WinstonLogger,
|
||||
);
|
||||
}
|
||||
|
||||
let auditor = WinstonLogger.create({
|
||||
meta: {
|
||||
service: 'backstage',
|
||||
},
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
auditorFieldFormat,
|
||||
options?.format ?? defaultFormatter,
|
||||
),
|
||||
transports: options?.transports,
|
||||
});
|
||||
|
||||
if (options?.meta) {
|
||||
auditor = auditor.child(options.meta) as WinstonLogger;
|
||||
}
|
||||
|
||||
return new DefaultRootAuditorService(auditor);
|
||||
}
|
||||
|
||||
async log(auditorEvent: AuditorEvent): Promise<void> {
|
||||
this.impl.info(...auditorEvent);
|
||||
}
|
||||
|
||||
forPlugin(deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
plugin: PluginMetadataService;
|
||||
}): AuditorService {
|
||||
const impl = new DefaultRootAuditorService(
|
||||
this.impl.child({}) as WinstonLogger,
|
||||
);
|
||||
return DefaultAuditorService.create(impl, deps);
|
||||
}
|
||||
}
|
||||
+10
-9
@@ -15,16 +15,17 @@
|
||||
*/
|
||||
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { DefaultAuditorService, DefaultRootAuditorService } from './Auditor';
|
||||
import { WinstonRootAuditorService } from './WinstonRootAuditorService';
|
||||
import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
|
||||
describe('Auditor', () => {
|
||||
describe('WinstonRootAuditorService', () => {
|
||||
it('creates a auditor instance with default options', () => {
|
||||
const auditor = DefaultRootAuditorService.create();
|
||||
expect(auditor).toBeInstanceOf(DefaultRootAuditorService);
|
||||
const auditor = WinstonRootAuditorService.create();
|
||||
expect(auditor).toBeInstanceOf(WinstonRootAuditorService);
|
||||
});
|
||||
|
||||
it('creates a child logger', () => {
|
||||
const auditor = DefaultRootAuditorService.create();
|
||||
const auditor = WinstonRootAuditorService.create();
|
||||
const childLogger = auditor.forPlugin({
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
@@ -38,7 +39,7 @@ describe('Auditor', () => {
|
||||
it('should log a status "initiated" using createEvent', async () => {
|
||||
const pluginId = 'test-plugin';
|
||||
|
||||
const auditor = DefaultRootAuditorService.create().forPlugin({
|
||||
const auditor = WinstonRootAuditorService.create().forPlugin({
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
plugin: {
|
||||
@@ -61,7 +62,7 @@ describe('Auditor', () => {
|
||||
it('should log a status "succeeded" using createEvent', async () => {
|
||||
const pluginId = 'test-plugin';
|
||||
|
||||
const auditor = DefaultRootAuditorService.create().forPlugin({
|
||||
const auditor = WinstonRootAuditorService.create().forPlugin({
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
plugin: {
|
||||
@@ -88,7 +89,7 @@ describe('Auditor', () => {
|
||||
it('should log a status "failed"', async () => {
|
||||
const pluginId = 'test-plugin';
|
||||
|
||||
const auditor = DefaultRootAuditorService.create().forPlugin({
|
||||
const auditor = WinstonRootAuditorService.create().forPlugin({
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
plugin: {
|
||||
@@ -117,7 +118,7 @@ describe('Auditor', () => {
|
||||
it('should use root meta', async () => {
|
||||
const pluginId = 'test-plugin';
|
||||
|
||||
const auditor = DefaultRootAuditorService.create().forPlugin({
|
||||
const auditor = WinstonRootAuditorService.create().forPlugin({
|
||||
auth: mockServices.auth.mock(),
|
||||
httpAuth: mockServices.httpAuth.mock(),
|
||||
plugin: {
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2025 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 type {
|
||||
AuditorService,
|
||||
AuthService,
|
||||
HttpAuthService,
|
||||
PluginMetadataService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
import type { Format } from 'logform';
|
||||
import * as winston from 'winston';
|
||||
import { WinstonLogger } from '../rootLogger';
|
||||
import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
|
||||
/** @public */
|
||||
export const defaultFormatter = winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.splat(),
|
||||
winston.format.json(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds `isAuditorEvent` field
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const auditorFieldFormat = winston.format(info => {
|
||||
return { ...info, isAuditorEvent: true };
|
||||
})();
|
||||
|
||||
/**
|
||||
* Options for creating a {@link WinstonRootAuditorService}.
|
||||
* @public
|
||||
*/
|
||||
export type WinstonRootAuditorServiceOptions = {
|
||||
meta?: JsonObject;
|
||||
format?: Format;
|
||||
transports?: winston.transport[];
|
||||
};
|
||||
|
||||
/**
|
||||
* An implementation of the {@link @backstage/backend-plugin-api#AuditorService} that logs events using a separate winston logger.
|
||||
*
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export const auditorServiceFactory = createServiceFactory({
|
||||
* service: coreServices.auditor,
|
||||
* deps: {
|
||||
* auth: coreServices.auth,
|
||||
* httpAuth: coreServices.httpAuth,
|
||||
* plugin: coreServices.pluginMetadata,
|
||||
* },
|
||||
* createRootContext() {
|
||||
* return WinstonRootAuditorService.create();
|
||||
* },
|
||||
* factory({ plugin, auth, httpAuth }, root) {
|
||||
* return root.forPlugin({ plugin, auth, httpAuth });
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class WinstonRootAuditorService {
|
||||
private constructor(private readonly winstonLogger: WinstonLogger) {}
|
||||
|
||||
/**
|
||||
* Creates a {@link WinstonRootAuditorService} instance.
|
||||
*/
|
||||
static create(
|
||||
options?: WinstonRootAuditorServiceOptions,
|
||||
): WinstonRootAuditorService {
|
||||
let winstonLogger = WinstonLogger.create({
|
||||
meta: {
|
||||
service: 'backstage',
|
||||
},
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
auditorFieldFormat,
|
||||
options?.format ?? defaultFormatter,
|
||||
),
|
||||
transports: options?.transports,
|
||||
});
|
||||
|
||||
if (options?.meta) {
|
||||
winstonLogger = winstonLogger.child(options.meta) as WinstonLogger;
|
||||
}
|
||||
|
||||
return new WinstonRootAuditorService(winstonLogger);
|
||||
}
|
||||
|
||||
forPlugin(deps: {
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
plugin: PluginMetadataService;
|
||||
}): AuditorService {
|
||||
return DefaultAuditorService.create(
|
||||
e => this.winstonLogger.info(`${e.plugin}.${e.eventId}`, e),
|
||||
deps,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DefaultRootAuditorService } from './Auditor';
|
||||
import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
|
||||
/**
|
||||
* Plugin-level auditing.
|
||||
@@ -32,17 +32,16 @@ import { DefaultRootAuditorService } from './Auditor';
|
||||
export const auditorServiceFactory = createServiceFactory({
|
||||
service: coreServices.auditor,
|
||||
deps: {
|
||||
rootLogger: coreServices.rootLogger,
|
||||
logger: coreServices.logger,
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
async createRootContext({ rootLogger }) {
|
||||
const auditor = DefaultRootAuditorService.create({ rootLogger });
|
||||
|
||||
return auditor;
|
||||
},
|
||||
factory({ plugin, auth, httpAuth }, rootAuditor) {
|
||||
return rootAuditor.forPlugin({ auth, httpAuth, plugin });
|
||||
factory({ logger, plugin, auth, httpAuth }) {
|
||||
const auditLogger = logger.child({ isAuditorEvent: true });
|
||||
return DefaultAuditorService.create(
|
||||
event => auditLogger.info(`${event.plugin}.${event.eventId}`, event),
|
||||
{ plugin, auth, httpAuth },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,5 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './Auditor';
|
||||
export type {
|
||||
AuditorEvent,
|
||||
AuditorEventActorDetails,
|
||||
AuditorEventOptions,
|
||||
AuditorEventRequest,
|
||||
AuditorEventStatus,
|
||||
AuditorLogFunction,
|
||||
} from './DefaultAuditorService';
|
||||
export { DefaultAuditorService } from './DefaultAuditorService';
|
||||
export { auditorServiceFactory } from './auditorServiceFactory';
|
||||
export { WinstonRootAuditorService } from './WinstonRootAuditorService';
|
||||
export type { WinstonRootAuditorServiceOptions } from './WinstonRootAuditorService';
|
||||
|
||||
Reference in New Issue
Block a user