Skip to content

Commit

Permalink
[Alerting] write event log entries for alert execution and it's actions
Browse files Browse the repository at this point in the history
resolves elastic#55636

Writes eventLog events for alert executions, and the actions executed from
that alert execution.
  • Loading branch information
pmuellr committed Mar 27, 2020
1 parent 878ab20 commit 548153c
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 9 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "alerting"],
"requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions"],
"requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions", "eventLog"],
"optionalPlugins": ["usageCollection", "spaces", "security"]
}
15 changes: 15 additions & 0 deletions x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ import {
import { Services } from './types';
import { registerAlertsUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
import { IEventLogger, IEventLogService } from '../../event_log/server';

const EVENT_LOG_PROVIDER = 'alerting';
export const EVENT_LOG_ALERTING = {
execute: 'execute',
executeAction: 'execute-action',
};

export interface PluginSetupContract {
registerType: AlertTypeRegistry['register'];
Expand All @@ -73,6 +80,7 @@ export interface AlertingPluginsSetup {
licensing: LicensingPluginSetup;
spaces?: SpacesPluginSetup;
usageCollection?: UsageCollectionSetup;
eventLog: IEventLogService;
}
export interface AlertingPluginsStart {
actions: ActionsPluginStartContract;
Expand All @@ -93,6 +101,7 @@ export class AlertingPlugin {
private readonly alertsClientFactory: AlertsClientFactory;
private readonly telemetryLogger: Logger;
private readonly kibanaIndex: Promise<string>;
private eventLogger?: IEventLogger;

constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get('plugins', 'alerting');
Expand Down Expand Up @@ -133,6 +142,11 @@ export class AlertingPlugin {
]),
});

plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ALERTING));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
});

const alertTypeRegistry = new AlertTypeRegistry({
taskManager: plugins.taskManager,
taskRunnerFactory: this.taskRunnerFactory,
Expand Down Expand Up @@ -211,6 +225,7 @@ export class AlertingPlugin {
actionsPlugin: plugins.actions,
encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects,
getBasePath: this.getBasePath,
eventLogger: this.eventLogger!,
});

scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AlertType } from '../types';
import { createExecutionHandler } from './create_execution_handler';
import { loggingServiceMock } from '../../../../../src/core/server/mocks';
import { actionsMock } from '../../../actions/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';

const alertType: AlertType = {
id: 'test',
Expand All @@ -31,6 +32,7 @@ const createExecutionHandlerParams = {
getBasePath: jest.fn().mockReturnValue(undefined),
alertType,
logger: loggingServiceMock.create().get(),
eventLogger: eventLoggerMock.create(),
actions: [
{
id: '1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { AlertAction, State, Context, AlertType } from '../types';
import { Logger } from '../../../../../src/core/server';
import { transformActionParams } from './transform_action_params';
import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server';
import { IEventLogger, IEvent } from '../../../event_log/server';
import { EVENT_LOG_ALERTING } from '../plugin';

interface CreateExecutionHandlerOptions {
alertId: string;
Expand All @@ -20,6 +22,7 @@ interface CreateExecutionHandlerOptions {
apiKey: string | null;
alertType: AlertType;
logger: Logger;
eventLogger: IEventLogger;
}

interface ExecutionHandlerOptions {
Expand All @@ -39,6 +42,7 @@ export function createExecutionHandler({
spaceId,
apiKey,
alertType,
eventLogger,
}: CreateExecutionHandlerOptions) {
const alertTypeActionGroups = new Set(pluck(alertType.actionGroups, 'id'));
return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => {
Expand All @@ -63,19 +67,47 @@ export function createExecutionHandler({
}),
};
});

const alertLabel = `${alertType.id}:${alertId}: ${alertName}`;
const namespace = namespaceFromSpaceId(spaceId);

for (const action of actions) {
if (actionsPlugin.isActionTypeEnabled(action.actionTypeId)) {
await actionsPlugin.execute({
id: action.id,
params: action.params,
spaceId,
apiKey,
});
} else {
if (!actionsPlugin.isActionTypeEnabled(action.actionTypeId)) {
logger.warn(
`Alert "${alertId}" skipped scheduling action "${action.id}" because it is disabled`
);
continue;
}

// TODO would be nice to add the action name here, but it's not available
const actionLabel = `${action.actionTypeId}:${action.id}`;
const event: IEvent = {
event: { action: EVENT_LOG_ALERTING.executeAction },
kibana: {
namespace,
saved_objects: [
{ type: 'alert', id: alertId },
{ type: 'action', id: action.id },
],
},
};
eventLogger.startTiming(event);

await actionsPlugin.execute({
id: action.id,
params: action.params,
spaceId,
apiKey,
});

eventLogger.stopTiming(event);
event.message = `alert: ${alertLabel} executed action: ${actionLabel}`;
eventLogger.logEvent(event);
}
};
}

function namespaceFromSpaceId(spaceId: string) {
if (spaceId === 'default') return undefined;
return spaceId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_o
import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks';
import { PluginStartContract as ActionsPluginStart } from '../../../actions/server';
import { actionsMock } from '../../../actions/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';

const alertType = {
id: 'test',
Expand Down Expand Up @@ -66,6 +67,7 @@ describe('Task Runner', () => {
logger: loggingServiceMock.create().get(),
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
getBasePath: jest.fn().mockReturnValue(undefined),
eventLogger: eventLoggerMock.create(),
};

const mockedAlertTypeSavedObject = {
Expand Down
15 changes: 15 additions & 0 deletions x-pack/plugins/alerting/server/task_runner/task_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type';
import { taskInstanceToAlertTaskInstance } from './alert_task_instance';
import { AlertInstances } from '../alert_instance/alert_instance';
import { EVENT_LOG_ALERTING } from '../plugin';
import { IEvent } from '../../../event_log/server';

const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' };

Expand Down Expand Up @@ -124,6 +126,7 @@ export class TaskRunner {
actions: actionsWithIds,
spaceId,
alertType: this.alertType,
eventLogger: this.context.eventLogger,
});
}

Expand Down Expand Up @@ -165,6 +168,14 @@ export class TaskRunner {
rawAlertInstance => new AlertInstance(rawAlertInstance)
);

const eventLogger = this.context.eventLogger;
const alertLabel = `${this.alertType.id}:${alertId}: ${name}`;
const event: IEvent = {
event: { action: EVENT_LOG_ALERTING.execute },
kibana: { namespace, saved_objects: [{ type: 'alert', id: alertId }] },
};
eventLogger.startTiming(event);

const updatedAlertTypeState = await this.alertType.executor({
alertId,
services: {
Expand All @@ -183,6 +194,10 @@ export class TaskRunner {
updatedBy,
});

eventLogger.stopTiming(event);
event.message = `alert executed: ${alertLabel}`;
eventLogger.logEvent(event);

// Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object
const instancesWithScheduledActions = pick<AlertInstances, AlertInstances>(
alertInstances,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory';
import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks';
import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks';
import { actionsMock } from '../../../actions/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/event_logger.mock';

const alertType = {
id: 'test',
Expand Down Expand Up @@ -62,6 +63,7 @@ describe('Task Runner Factory', () => {
logger: loggingServiceMock.create().get(),
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
getBasePath: jest.fn().mockReturnValue(undefined),
eventLogger: eventLoggerMock.create(),
};

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
SpaceIdToNamespaceFunction,
} from '../types';
import { TaskRunner } from './task_runner';
import { IEventLogger } from '../../../event_log/server';

export interface TaskRunnerContext {
logger: Logger;
getServices: GetServicesFunction;
actionsPlugin: ActionsPluginStartContract;
eventLogger: IEventLogger;
encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart;
spaceIdToNamespace: SpaceIdToNamespaceFunction;
getBasePath: GetBasePathFunction;
Expand Down

0 comments on commit 548153c

Please sign in to comment.