feat: add backend.auditor.severityLogLevelMappings to map severity levels to log levels
Signed-off-by: Paul Schultz <pschultz@pobox.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Adds `backend.auditor.severityLogLevelMappings` to map severity levels to log levels.
|
||||
@@ -151,3 +151,41 @@ To clarify how to utilize the Auditor feature effectively, we recommend explorin
|
||||
- It illustrates how to detail various `eventId` values and their corresponding `meta` fields (e.g., `queryType`, `actionType`) for different plugin operations.
|
||||
|
||||
These examples provide both a code-level demonstration and a documentation guideline for effectively utilizing the `AuditorService` to manage audit events within your Backstage plugins.
|
||||
|
||||
## Severity Log Level Mappings
|
||||
|
||||
The Auditor Service provides a way for plugins to log significant events, categorized by their severity. The `severityLogLevelMappings` configuration option enables you to customize how these severity levels are mapped to actual log levels within your Backstage backend, giving you precise control over the verbosity of your audit logs.
|
||||
|
||||
### Configuration
|
||||
|
||||
The `severityLogLevelMappings` are configured under the `backend.auditor` section of your `app-config.yaml` file. This structure allows you to specify the log level for each severity level supported by the Auditor Service. You can override individual severity levels without changing the entire mapping.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
auditor:
|
||||
severityLogLevelMappings:
|
||||
low: debug
|
||||
medium: info
|
||||
high: warn
|
||||
critical: error
|
||||
```
|
||||
|
||||
### Severity Levels and Default Mappings
|
||||
|
||||
The Auditor Service supports the following severity levels:
|
||||
|
||||
- `low`: Represents low-importance events, typically informational or debug-level.
|
||||
- `medium`: Represents events of moderate importance, requiring some attention.
|
||||
- `high`: Represents high-importance events, potentially indicating a problem or security issue.
|
||||
- `critical`: Represents critical events, requiring immediate attention.
|
||||
|
||||
By default, these severity levels are mapped to the following log levels:
|
||||
|
||||
- `low`: `debug`
|
||||
- `medium`: `info`
|
||||
- `high`: `info`
|
||||
- `critical`: `info`
|
||||
|
||||
As a result, medium, high, and critical events are logged as info-level events by default, while low-level events are treated as debug.
|
||||
|
||||
+26
@@ -82,6 +82,32 @@ export interface Config {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Options used by the default auditor service.
|
||||
*/
|
||||
auditor?: {
|
||||
/**
|
||||
* Defines how audit event severity levels are mapped to log levels.
|
||||
* This allows you to control the verbosity of audit logs based on the
|
||||
* severity of the event. For example, you might want to log 'low' severity
|
||||
* events as 'debug' messages, while logging 'critical' events as 'error'
|
||||
* messages. Each severity level ('low', 'medium', 'high', 'critical')
|
||||
* can be mapped to one of the standard log levels ('debug', 'info', 'warn', 'error').
|
||||
*
|
||||
* By default, audit events are mapped to log levels as follows:
|
||||
* - `low`: `debug`
|
||||
* - `medium`: `info`
|
||||
* - `high`: `info`
|
||||
* - `critical`: `info`
|
||||
*/
|
||||
severityLogLevelMappings?: {
|
||||
low?: 'debug' | 'info' | 'warn' | 'error';
|
||||
medium?: 'debug' | 'info' | 'warn' | 'error';
|
||||
high?: 'debug' | 'info' | 'warn' | 'error';
|
||||
critical?: 'debug' | 'info' | 'warn' | 'error';
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Options used by the default auth, httpAuth and userInfo services.
|
||||
*/
|
||||
|
||||
@@ -69,4 +69,121 @@ describe('auditorServiceFactory', () => {
|
||||
status: 'initiated',
|
||||
});
|
||||
});
|
||||
|
||||
it('should log with custom log level mapping', async () => {
|
||||
const mockLogger = mockServices.logger.mock();
|
||||
mockLogger.child.mockReturnValue(mockLogger);
|
||||
|
||||
const auditor = await ServiceFactoryTester.from(auditorServiceFactory, {
|
||||
dependencies: [
|
||||
mockLogger.factory,
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
auditor: {
|
||||
severityLogLevelMappings: {
|
||||
low: 'info',
|
||||
medium: 'debug',
|
||||
high: 'warn',
|
||||
critical: 'error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}).getSubject();
|
||||
|
||||
await auditor.createEvent({
|
||||
eventId: 'test1',
|
||||
severityLevel: 'low',
|
||||
});
|
||||
await auditor.createEvent({
|
||||
eventId: 'test2',
|
||||
});
|
||||
await auditor.createEvent({
|
||||
eventId: 'test3',
|
||||
severityLevel: 'medium',
|
||||
});
|
||||
await auditor.createEvent({
|
||||
eventId: 'test4',
|
||||
severityLevel: 'high',
|
||||
});
|
||||
await auditor.createEvent({
|
||||
eventId: 'test5',
|
||||
severityLevel: 'critical',
|
||||
});
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('test.test1', {
|
||||
eventId: 'test1',
|
||||
severityLevel: 'low',
|
||||
actor: {
|
||||
actorId: 'plugin:test',
|
||||
},
|
||||
plugin: 'test',
|
||||
status: 'initiated',
|
||||
});
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('test.test2', {
|
||||
eventId: 'test2',
|
||||
severityLevel: 'low',
|
||||
actor: {
|
||||
actorId: 'plugin:test',
|
||||
},
|
||||
plugin: 'test',
|
||||
status: 'initiated',
|
||||
});
|
||||
expect(mockLogger.debug).toHaveBeenCalledWith('test.test3', {
|
||||
eventId: 'test3',
|
||||
severityLevel: 'medium',
|
||||
actor: {
|
||||
actorId: 'plugin:test',
|
||||
},
|
||||
plugin: 'test',
|
||||
status: 'initiated',
|
||||
});
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('test.test4', {
|
||||
eventId: 'test4',
|
||||
severityLevel: 'high',
|
||||
actor: {
|
||||
actorId: 'plugin:test',
|
||||
},
|
||||
plugin: 'test',
|
||||
status: 'initiated',
|
||||
});
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('test.test5', {
|
||||
eventId: 'test5',
|
||||
severityLevel: 'critical',
|
||||
actor: {
|
||||
actorId: 'plugin:test',
|
||||
},
|
||||
plugin: 'test',
|
||||
status: 'initiated',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error given an incorrect custom level', async () => {
|
||||
const mockLogger = mockServices.logger.mock();
|
||||
mockLogger.child.mockReturnValue(mockLogger);
|
||||
|
||||
await expect(
|
||||
ServiceFactoryTester.from(auditorServiceFactory, {
|
||||
dependencies: [
|
||||
mockLogger.factory,
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
auditor: {
|
||||
severityLogLevelMappings: {
|
||||
low: 'invalidloglevel',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
}).getSubject(),
|
||||
).rejects.toThrow(
|
||||
"Failed to instantiate service 'core.auditor' for 'test' because the factory function threw an error, InputError: The configuration value for 'backend.auditor.severityLogLevelMappings.low' was given an invalid value: 'invalidloglevel'. Expected one of the following valid values: 'debug, info, warn, error'.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,15 @@ import {
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
import { z } from 'zod';
|
||||
import { InputError } from '@backstage/errors';
|
||||
|
||||
const CONFIG_ROOT_KEY = 'backend.auditor';
|
||||
|
||||
const severityLogLevelMappingsSchema = z.record(
|
||||
z.enum(['low', 'medium', 'high', 'critical']),
|
||||
z.enum(['debug', 'info', 'warn', 'error']),
|
||||
);
|
||||
|
||||
/**
|
||||
* Plugin-level auditing.
|
||||
@@ -32,21 +41,55 @@ import { DefaultAuditorService } from './DefaultAuditorService';
|
||||
export const auditorServiceFactory = createServiceFactory({
|
||||
service: coreServices.auditor,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
factory({ logger, plugin, auth, httpAuth }) {
|
||||
factory({ config, logger, plugin, auth, httpAuth }) {
|
||||
const auditLogger = logger.child({ isAuditEvent: true });
|
||||
const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY);
|
||||
|
||||
const severityLogLevelMappings = {
|
||||
low:
|
||||
auditorConfig?.getOptionalString('severityLogLevelMappings.low') ??
|
||||
'debug',
|
||||
medium:
|
||||
auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ??
|
||||
'info',
|
||||
high:
|
||||
auditorConfig?.getOptionalString('severityLogLevelMappings.high') ??
|
||||
'info',
|
||||
critical:
|
||||
auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ??
|
||||
'info',
|
||||
} as Required<z.infer<typeof severityLogLevelMappingsSchema>>;
|
||||
|
||||
const res = severityLogLevelMappingsSchema.safeParse(
|
||||
severityLogLevelMappings,
|
||||
);
|
||||
if (!res.success) {
|
||||
const key = res.error.issues.at(0)?.path.at(0) as string;
|
||||
const value = (
|
||||
res.error.issues.at(0) as unknown as Record<PropertyKey, unknown>
|
||||
).received as string;
|
||||
const validKeys = (
|
||||
res.error.issues.at(0) as unknown as Record<PropertyKey, unknown>
|
||||
).options as string[];
|
||||
throw new InputError(
|
||||
`The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join(
|
||||
', ',
|
||||
)}'.`,
|
||||
);
|
||||
}
|
||||
|
||||
return DefaultAuditorService.create(
|
||||
event => {
|
||||
const message = `${event.plugin}.${event.eventId}`;
|
||||
if (event.severityLevel === 'low') {
|
||||
auditLogger.debug(message, event);
|
||||
} else {
|
||||
auditLogger.info(message, event);
|
||||
}
|
||||
auditLogger[severityLogLevelMappings[event.severityLevel]](
|
||||
`${event.plugin}.${event.eventId}`,
|
||||
event,
|
||||
);
|
||||
},
|
||||
{ plugin, auth, httpAuth },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user