feat(slo): Add more context variable to burn rate rule (#153647)

This commit is contained in:
Kevin Delemme 2023-03-27 10:15:53 -04:00 committed by GitHub
parent a38dfe96c9
commit 4531ee489f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 82 additions and 32 deletions

View file

@ -38,8 +38,12 @@ export const registerObservabilityRuleTypes = (
defaultActionMessage: i18n.translate(
'xpack.observability.slo.rules.burnRate.defaultActionMessage',
{
defaultMessage: `\\{\\{rule.name\\}\\} is firing:
- Reason: \\{\\{context.reason\\}\\}`,
defaultMessage: `The rule \\{\\{rule.name\\}\\} for the SLO '\\{\\{context.sloName\\}\\}' is firing:
- Reason: \\{\\{context.reason\\}\\}
- The burn rate over the last \\{\\{context.longWindow.duration\\}\\} is \\{\\{context.longWindow.burnRate\\}\\}
- The burn rate over the last \\{\\{context.shortWindow.duration\\}\\} is \\{\\{context.shortWindow.burnRate\\}\\}
- The burn rate threshold is set to \\{\\{context.burnRateThreshold\\}\\}
- View in the SLO details page: \\{\\{context.viewInAppUrl\\}\\}`,
}
),
});

View file

@ -6,15 +6,16 @@
*/
import { PluginSetupContract } from '@kbn/alerting-plugin/server';
import { Logger } from '@kbn/core/server';
import { IBasePath, Logger } from '@kbn/core/server';
import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server';
import { sloBurnRateRuleType } from './slo_burn_rate';
export function registerRuleTypes(
alertingPlugin: PluginSetupContract,
logger: Logger,
ruleDataClient: IRuleDataClient
ruleDataClient: IRuleDataClient,
basePath: IBasePath
) {
const createLifecycleRuleExecutor = createLifecycleExecutor(logger.get('rules'), ruleDataClient);
alertingPlugin.registerType(sloBurnRateRuleType(createLifecycleRuleExecutor));
alertingPlugin.registerType(sloBurnRateRuleType(createLifecycleRuleExecutor, basePath));
}

View file

@ -6,7 +6,7 @@
*/
import { v4 as uuidv4 } from 'uuid';
import { IUiSettingsClient, SavedObjectsClientContract } from '@kbn/core/server';
import { IBasePath, IUiSettingsClient, SavedObjectsClientContract } from '@kbn/core/server';
import {
ElasticsearchClientMock,
elasticsearchServiceMock,
@ -25,7 +25,7 @@ import {
ALERT_EVALUATION_VALUE,
ALERT_REASON,
} from '@kbn/rule-data-utils';
import { FIRED_ACTION, getRuleExecutor } from './executor';
import { ALERT_ACTION, getRuleExecutor } from './executor';
import { aStoredSLO, createSLO } from '../../../services/slo/fixtures/slo';
import { SLO } from '../../../domain/models';
import { SharePluginStart } from '@kbn/share-plugin/server';
@ -60,6 +60,7 @@ describe('BurnRateRuleExecutor', () => {
let esClientMock: ElasticsearchClientMock;
let soClientMock: jest.Mocked<SavedObjectsClientContract>;
let loggerMock: jest.Mocked<MockedLogger>;
const basePathMock = { publicBaseUrl: 'https://kibana.dev' } as IBasePath;
let alertWithLifecycleMock: jest.MockedFn<LifecycleAlertService>;
let alertFactoryMock: jest.Mocked<
PublicAlertFactory<BurnRateAlertState, BurnRateAlertContext, BurnRateAllowedActionGroups>
@ -102,7 +103,7 @@ describe('BurnRateRuleExecutor', () => {
it('throws when the slo is not found', async () => {
soClientMock.get.mockRejectedValue(new Error('NotFound'));
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await expect(
executor({
@ -123,7 +124,7 @@ describe('BurnRateRuleExecutor', () => {
it('returns early when the slo is disabled', async () => {
const slo = createSLO({ objective: { target: 0.9 }, enabled: false });
soClientMock.get.mockResolvedValue(aStoredSLO(slo));
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
const result = await executor({
params: someRuleParams({ sloId: slo.id, burnRateThreshold: BURN_RATE_THRESHOLD }),
@ -152,7 +153,7 @@ describe('BurnRateRuleExecutor', () => {
);
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [] });
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await executor({
params: someRuleParams({ sloId: slo.id, burnRateThreshold: BURN_RATE_THRESHOLD }),
startedAt: new Date(),
@ -177,7 +178,7 @@ describe('BurnRateRuleExecutor', () => {
);
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [] });
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await executor({
params: someRuleParams({ sloId: slo.id, burnRateThreshold: BURN_RATE_THRESHOLD }),
startedAt: new Date(),
@ -202,7 +203,7 @@ describe('BurnRateRuleExecutor', () => {
);
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [] });
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await executor({
params: someRuleParams({ sloId: slo.id, threshold: BURN_RATE_THRESHOLD }),
startedAt: new Date(),
@ -232,7 +233,7 @@ describe('BurnRateRuleExecutor', () => {
alertWithLifecycleMock.mockImplementation(() => alertMock as any);
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [] });
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await executor({
params: someRuleParams({ sloId: slo.id, burnRateThreshold: BURN_RATE_THRESHOLD }),
startedAt: new Date(),
@ -256,7 +257,7 @@ describe('BurnRateRuleExecutor', () => {
},
});
expect(alertMock.scheduleActions).toBeCalledWith(
FIRED_ACTION.id,
ALERT_ACTION.id,
expect.objectContaining({
longWindow: { burnRate: 2, duration: '1h' },
shortWindow: { burnRate: 2, duration: '5m' },
@ -279,7 +280,7 @@ describe('BurnRateRuleExecutor', () => {
};
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [alertMock] as any });
const executor = getRuleExecutor();
const executor = getRuleExecutor({ basePath: basePathMock });
await executor({
params: someRuleParams({ sloId: slo.id, burnRateThreshold: BURN_RATE_THRESHOLD }),

View file

@ -15,6 +15,8 @@ import {
import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server';
import { ExecutorType } from '@kbn/alerting-plugin/server';
import { addSpaceIdToPath } from '@kbn/spaces-plugin/server';
import { IBasePath } from '@kbn/core/server';
import { Duration, toDurationUnit } from '../../../domain/models';
import { DefaultSLIClient, KibanaSavedObjectsSLORepository } from '../../../services/slo';
import { computeBurnRate } from '../../../domain/services';
@ -30,7 +32,11 @@ import {
const SHORT_WINDOW = 'SHORT_WINDOW';
const LONG_WINDOW = 'LONG_WINDOW';
export const getRuleExecutor = (): LifecycleRuleExecutor<
export const getRuleExecutor = ({
basePath,
}: {
basePath: IBasePath;
}): LifecycleRuleExecutor<
BurnRateRuleParams,
BurnRateRuleTypeState,
BurnRateAlertState,
@ -41,6 +47,7 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
services,
params,
startedAt,
spaceId,
}): ReturnType<
ExecutorType<
BurnRateRuleParams,
@ -86,6 +93,12 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
longWindowBurnRate >= params.burnRateThreshold &&
shortWindowBurnRate >= params.burnRateThreshold;
const viewInAppUrl = addSpaceIdToPath(
basePath.publicBaseUrl,
spaceId,
`/app/observability/slos/${slo.id}`
);
if (shouldAlert) {
const reason = buildReason(
longWindowDuration,
@ -101,6 +114,9 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
shortWindow: { burnRate: shortWindowBurnRate, duration: shortWindowDuration.format() },
burnRateThreshold: params.burnRateThreshold,
timestamp: startedAt.toISOString(),
viewInAppUrl,
sloId: slo.id,
sloName: slo.name,
};
const alert = alertWithLifecycle({
@ -112,7 +128,7 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
},
});
alert.scheduleActions(FIRED_ACTION.id, context);
alert.scheduleActions(ALERT_ACTION.id, context);
alert.replaceState({ alertState: AlertStates.ALERT });
}
@ -124,6 +140,9 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
shortWindow: { burnRate: shortWindowBurnRate, duration: shortWindowDuration.format() },
burnRateThreshold: params.burnRateThreshold,
timestamp: startedAt.toISOString(),
viewInAppUrl,
sloId: slo.id,
sloName: slo.name,
};
recoveredAlert.setContext(context);
@ -132,10 +151,10 @@ export const getRuleExecutor = (): LifecycleRuleExecutor<
return { state: {} };
};
const FIRED_ACTION_ID = 'slo.burnRate.fired';
export const FIRED_ACTION = {
id: FIRED_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.fired', {
const ALERT_ACTION_ID = 'slo.burnRate.alert';
export const ALERT_ACTION = {
id: ALERT_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.alertAction', {
defaultMessage: 'Alert',
}),
};

View file

@ -10,11 +10,12 @@ import { i18n } from '@kbn/i18n';
import { LicenseType } from '@kbn/licensing-plugin/server';
import { createLifecycleExecutor } from '@kbn/rule-registry-plugin/server';
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
import { IBasePath } from '@kbn/core/server';
import { sloFeatureId } from '../../../../common';
import { SLO_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
import { SLO_BURN_RATE_RULE_ID } from '../../../../common/constants';
import { FIRED_ACTION, getRuleExecutor } from './executor';
import { ALERT_ACTION, getRuleExecutor } from './executor';
const durationSchema = schema.object({
value: schema.number(),
@ -23,7 +24,10 @@ const durationSchema = schema.object({
type CreateLifecycleExecutor = ReturnType<typeof createLifecycleExecutor>;
export function sloBurnRateRuleType(createLifecycleRuleExecutor: CreateLifecycleExecutor) {
export function sloBurnRateRuleType(
createLifecycleRuleExecutor: CreateLifecycleExecutor,
basePath: IBasePath
) {
return {
id: SLO_BURN_RATE_RULE_ID,
name: i18n.translate('xpack.observability.slo.rules.burnRate.name', {
@ -38,12 +42,12 @@ export function sloBurnRateRuleType(createLifecycleRuleExecutor: CreateLifecycle
shortWindow: durationSchema,
}),
},
defaultActionGroupId: FIRED_ACTION.id,
actionGroups: [FIRED_ACTION],
defaultActionGroupId: ALERT_ACTION.id,
actionGroups: [ALERT_ACTION],
producer: sloFeatureId,
minimumLicenseRequired: 'platinum' as LicenseType,
isExportable: true,
executor: createLifecycleRuleExecutor(getRuleExecutor()),
executor: createLifecycleRuleExecutor(getRuleExecutor({ basePath })),
doesSetRecoveryContext: true,
actionVariables: {
context: [
@ -52,6 +56,9 @@ export function sloBurnRateRuleType(createLifecycleRuleExecutor: CreateLifecycle
{ name: 'burnRateThreshold', description: thresholdActionVariableDescription },
{ name: 'longWindow', description: windowActionVariableDescription },
{ name: 'shortWindow', description: windowActionVariableDescription },
{ name: 'viewInAppUrl', description: viewInAppUrlActionVariableDescription },
{ name: 'sloId', description: sloIdActionVariableDescription },
{ name: 'sloName', description: sloNameActionVariableDescription },
],
},
alerts: {
@ -89,3 +96,24 @@ export const timestampActionVariableDescription = i18n.translate(
defaultMessage: 'A timestamp of when the alert was detected.',
}
);
export const viewInAppUrlActionVariableDescription = i18n.translate(
'xpack.observability.slo.alerting.viewInAppUrlDescription',
{
defaultMessage: 'The url to the SLO details page to help with further investigation.',
}
);
export const sloIdActionVariableDescription = i18n.translate(
'xpack.observability.slo.alerting.sloIdDescription',
{
defaultMessage: 'The SLO unique identifier.',
}
);
export const sloNameActionVariableDescription = i18n.translate(
'xpack.observability.slo.alerting.sloNameDescription',
{
defaultMessage: 'The SLO name.',
}
);

View file

@ -10,7 +10,7 @@ import {
AlertInstanceContext as AlertContext,
AlertInstanceState as AlertState,
} from '@kbn/alerting-plugin/common';
import { FIRED_ACTION } from './executor';
import { ALERT_ACTION } from './executor';
export enum AlertStates {
OK,
@ -27,4 +27,4 @@ export type BurnRateRuleParams = {
export type BurnRateRuleTypeState = RuleTypeState; // no specific rule state
export type BurnRateAlertState = AlertState; // no specific alert state
export type BurnRateAlertContext = AlertContext; // no specific alert context
export type BurnRateAllowedActionGroups = ActionGroupIdsOf<typeof FIRED_ACTION>;
export type BurnRateAllowedActionGroups = ActionGroupIdsOf<typeof ALERT_ACTION>;

View file

@ -231,7 +231,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
},
],
});
registerRuleTypes(plugins.alerting, this.logger, ruleDataClient);
registerRuleTypes(plugins.alerting, this.logger, ruleDataClient, core.http.basePath);
registerSloUsageCollector(plugins.usageCollection);
registerRoutes({

View file

@ -26052,7 +26052,6 @@
"xpack.observability.seriesEditor.show": "Afficher la série",
"xpack.observability.serviceGroupMaxServicesUiSettingDescription": "Limiter le nombre de services dans un groupe de services donné",
"xpack.observability.serviceGroupMaxServicesUiSettingName": "Nombre maximum de services dans un groupe de services",
"xpack.observability.slo.alerting.burnRate.fired": "Alerte",
"xpack.observability.slo.alerting.reasonDescription": "Une description concise de la raison du signalement",
"xpack.observability.slo.alerting.thresholdDescription": "Valeur de seuil du taux d'avancement.",
"xpack.observability.slo.alerting.timestampDescription": "Horodatage du moment où l'alerte a été détectée.",

View file

@ -26033,7 +26033,6 @@
"xpack.observability.seriesEditor.show": "系列を表示",
"xpack.observability.serviceGroupMaxServicesUiSettingDescription": "特定のサービスグループのサービス数を制限",
"xpack.observability.serviceGroupMaxServicesUiSettingName": "サービスグループの最大サービス",
"xpack.observability.slo.alerting.burnRate.fired": "アラート",
"xpack.observability.slo.alerting.reasonDescription": "アラートの理由の簡潔な説明",
"xpack.observability.slo.alerting.thresholdDescription": "バーンレートしきい値。",
"xpack.observability.slo.alerting.timestampDescription": "アラートが検出された時点のタイムスタンプ。",

View file

@ -26049,7 +26049,6 @@
"xpack.observability.seriesEditor.show": "显示序列",
"xpack.observability.serviceGroupMaxServicesUiSettingDescription": "限制给定服务组中服务的数量",
"xpack.observability.serviceGroupMaxServicesUiSettingName": "服务组中的最大服务数",
"xpack.observability.slo.alerting.burnRate.fired": "告警",
"xpack.observability.slo.alerting.reasonDescription": "告警原因的简洁描述",
"xpack.observability.slo.alerting.thresholdDescription": "消耗速度阈值。",
"xpack.observability.slo.alerting.timestampDescription": "检测到告警时的时间戳。",