mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Infrastructure UI] Add logging to Inventory Threshold Rule (#127838)
* [Infrastructure UI] Add logging to Inventory Threshold Rule * Adding alertId and executionId to log messages * Adding alertExecutionDetails to tests * Refactor logger instead of passing around AlertExecutionDetails * Removing unused deps * Adding warn, error, fatal to scopedLogger
This commit is contained in:
parent
da5dfd9b56
commit
04ccd4d215
8 changed files with 88 additions and 2 deletions
|
@ -112,3 +112,8 @@ export type MetricExpressionParams = NonCountMetricExpressionParams | CountMetri
|
||||||
export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID');
|
export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID');
|
||||||
|
|
||||||
export type FilterQuery = string | typeof QUERY_INVALID;
|
export type FilterQuery = string | typeof QUERY_INVALID;
|
||||||
|
|
||||||
|
export interface AlertExecutionDetails {
|
||||||
|
alertId: string;
|
||||||
|
executionId: string;
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty, isError } from 'lodash';
|
||||||
import { schema } from '@kbn/config-schema';
|
import { schema } from '@kbn/config-schema';
|
||||||
|
import { Logger, LogMeta } from '@kbn/logging';
|
||||||
|
import { AlertExecutionDetails } from '../../../../common/alerting/metrics/types';
|
||||||
|
|
||||||
export const oneOfLiterals = (arrayOfLiterals: Readonly<string[]>) =>
|
export const oneOfLiterals = (arrayOfLiterals: Readonly<string[]>) =>
|
||||||
schema.string({
|
schema.string({
|
||||||
|
@ -33,3 +35,43 @@ export const validateIsStringElasticsearchJSONFilter = (value: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UNGROUPED_FACTORY_KEY = '*';
|
export const UNGROUPED_FACTORY_KEY = '*';
|
||||||
|
|
||||||
|
export const createScopedLogger = (
|
||||||
|
logger: Logger,
|
||||||
|
scope: string,
|
||||||
|
alertExecutionDetails: AlertExecutionDetails
|
||||||
|
): Logger => {
|
||||||
|
const scopedLogger = logger.get(scope);
|
||||||
|
const fmtMsg = (msg: string) =>
|
||||||
|
`[AlertId: ${alertExecutionDetails.alertId}][ExecutionId: ${alertExecutionDetails.executionId}] ${msg}`;
|
||||||
|
return {
|
||||||
|
...scopedLogger,
|
||||||
|
info: <Meta extends LogMeta = LogMeta>(msg: string, meta?: Meta) =>
|
||||||
|
scopedLogger.info(fmtMsg(msg), meta),
|
||||||
|
debug: <Meta extends LogMeta = LogMeta>(msg: string, meta?: Meta) =>
|
||||||
|
scopedLogger.debug(fmtMsg(msg), meta),
|
||||||
|
trace: <Meta extends LogMeta = LogMeta>(msg: string, meta?: Meta) =>
|
||||||
|
scopedLogger.trace(fmtMsg(msg), meta),
|
||||||
|
warn: <Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta) => {
|
||||||
|
if (isError(errorOrMessage)) {
|
||||||
|
scopedLogger.warn(errorOrMessage, meta);
|
||||||
|
} else {
|
||||||
|
scopedLogger.warn(fmtMsg(errorOrMessage), meta);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: <Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta) => {
|
||||||
|
if (isError(errorOrMessage)) {
|
||||||
|
scopedLogger.error(errorOrMessage, meta);
|
||||||
|
} else {
|
||||||
|
scopedLogger.error(fmtMsg(errorOrMessage), meta);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fatal: <Meta extends LogMeta = LogMeta>(errorOrMessage: string | Error, meta?: Meta) => {
|
||||||
|
if (isError(errorOrMessage)) {
|
||||||
|
scopedLogger.fatal(errorOrMessage, meta);
|
||||||
|
} else {
|
||||||
|
scopedLogger.fatal(fmtMsg(errorOrMessage), meta);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { ElasticsearchClient } from 'kibana/server';
|
import { ElasticsearchClient } from 'kibana/server';
|
||||||
import { mapValues } from 'lodash';
|
import { mapValues } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { Logger } from '@kbn/logging';
|
||||||
import { InventoryMetricConditions } from '../../../../common/alerting/metrics';
|
import { InventoryMetricConditions } from '../../../../common/alerting/metrics';
|
||||||
import { InfraTimerangeInput } from '../../../../common/http_api';
|
import { InfraTimerangeInput } from '../../../../common/http_api';
|
||||||
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
import { InventoryItemType } from '../../../../common/inventory_models/types';
|
||||||
|
@ -34,6 +35,7 @@ export const evaluateCondition = async ({
|
||||||
filterQuery,
|
filterQuery,
|
||||||
lookbackSize,
|
lookbackSize,
|
||||||
startTime,
|
startTime,
|
||||||
|
logger,
|
||||||
}: {
|
}: {
|
||||||
condition: InventoryMetricConditions;
|
condition: InventoryMetricConditions;
|
||||||
nodeType: InventoryItemType;
|
nodeType: InventoryItemType;
|
||||||
|
@ -44,6 +46,7 @@ export const evaluateCondition = async ({
|
||||||
filterQuery?: string;
|
filterQuery?: string;
|
||||||
lookbackSize?: number;
|
lookbackSize?: number;
|
||||||
startTime?: number;
|
startTime?: number;
|
||||||
|
logger: Logger;
|
||||||
}): Promise<Record<string, ConditionResult>> => {
|
}): Promise<Record<string, ConditionResult>> => {
|
||||||
const { metric, customMetric } = condition;
|
const { metric, customMetric } = condition;
|
||||||
|
|
||||||
|
@ -69,6 +72,7 @@ export const evaluateCondition = async ({
|
||||||
logQueryFields,
|
logQueryFields,
|
||||||
compositeSize,
|
compositeSize,
|
||||||
condition,
|
condition,
|
||||||
|
logger,
|
||||||
filterQuery,
|
filterQuery,
|
||||||
customMetric
|
customMetric
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
// buildRecoveredAlertReason,
|
// buildRecoveredAlertReason,
|
||||||
stateToAlertMessage,
|
stateToAlertMessage,
|
||||||
} from '../common/messages';
|
} from '../common/messages';
|
||||||
|
import { createScopedLogger } from '../common/utils';
|
||||||
import { evaluateCondition } from './evaluate_condition';
|
import { evaluateCondition } from './evaluate_condition';
|
||||||
|
|
||||||
type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf<
|
type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf<
|
||||||
|
@ -61,9 +62,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
||||||
InventoryMetricThresholdAlertState,
|
InventoryMetricThresholdAlertState,
|
||||||
InventoryMetricThresholdAlertContext,
|
InventoryMetricThresholdAlertContext,
|
||||||
InventoryMetricThresholdAllowedActionGroups
|
InventoryMetricThresholdAllowedActionGroups
|
||||||
>(async ({ services, params }) => {
|
>(async ({ services, params, alertId, executionId }) => {
|
||||||
|
const startTime = Date.now();
|
||||||
const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } = params;
|
const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } = params;
|
||||||
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
|
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
|
||||||
|
const logger = createScopedLogger(libs.logger, 'inventoryRule', { alertId, executionId });
|
||||||
const { alertWithLifecycle, savedObjectsClient } = services;
|
const { alertWithLifecycle, savedObjectsClient } = services;
|
||||||
const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason) =>
|
const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason) =>
|
||||||
alertWithLifecycle({
|
alertWithLifecycle({
|
||||||
|
@ -79,6 +82,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
||||||
const { fromKueryExpression } = await import('@kbn/es-query');
|
const { fromKueryExpression } = await import('@kbn/es-query');
|
||||||
fromKueryExpression(params.filterQueryText);
|
fromKueryExpression(params.filterQueryText);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
logger.error(e.message);
|
||||||
const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able
|
const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able
|
||||||
const reason = buildInvalidQueryAlertReason(params.filterQueryText);
|
const reason = buildInvalidQueryAlertReason(params.filterQueryText);
|
||||||
const alert = alertFactory('*', reason);
|
const alert = alertFactory('*', reason);
|
||||||
|
@ -117,9 +121,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
||||||
esClient: services.scopedClusterClient.asCurrentUser,
|
esClient: services.scopedClusterClient.asCurrentUser,
|
||||||
compositeSize,
|
compositeSize,
|
||||||
filterQuery,
|
filterQuery,
|
||||||
|
logger,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
let scheduledActionsCount = 0;
|
||||||
const inventoryItems = Object.keys(first(results)!);
|
const inventoryItems = Object.keys(first(results)!);
|
||||||
for (const group of inventoryItems) {
|
for (const group of inventoryItems) {
|
||||||
// AND logic; all criteria must be across the threshold
|
// AND logic; all criteria must be across the threshold
|
||||||
|
@ -187,6 +193,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
||||||
: FIRED_ACTIONS.id;
|
: FIRED_ACTIONS.id;
|
||||||
|
|
||||||
const alert = alertFactory(`${group}`, reason);
|
const alert = alertFactory(`${group}`, reason);
|
||||||
|
scheduledActionsCount++;
|
||||||
alert.scheduleActions(
|
alert.scheduleActions(
|
||||||
/**
|
/**
|
||||||
* TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on
|
* TODO: We're lying to the compiler here as explicitly calling `scheduleActions` on
|
||||||
|
@ -207,6 +214,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const stopTime = Date.now();
|
||||||
|
logger.debug(`Scheduled ${scheduledActionsCount} actions in ${stopTime - startTime}ms`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatThreshold = (metric: SnapshotMetricType, value: number) => {
|
const formatThreshold = (metric: SnapshotMetricType, value: number) => {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ElasticsearchClient } from 'kibana/server';
|
import { ElasticsearchClient } from 'kibana/server';
|
||||||
|
import type { Logger } from '@kbn/logging';
|
||||||
import { InventoryMetricConditions } from '../../../../../common/alerting/metrics';
|
import { InventoryMetricConditions } from '../../../../../common/alerting/metrics';
|
||||||
import { InfraTimerangeInput, SnapshotCustomMetricInput } from '../../../../../common/http_api';
|
import { InfraTimerangeInput, SnapshotCustomMetricInput } from '../../../../../common/http_api';
|
||||||
import {
|
import {
|
||||||
|
@ -44,6 +45,7 @@ export const getData = async (
|
||||||
logQueryFields: LogQueryFields | undefined,
|
logQueryFields: LogQueryFields | undefined,
|
||||||
compositeSize: number,
|
compositeSize: number,
|
||||||
condition: InventoryMetricConditions,
|
condition: InventoryMetricConditions,
|
||||||
|
logger: Logger,
|
||||||
filterQuery?: string,
|
filterQuery?: string,
|
||||||
customMetric?: SnapshotCustomMetricInput,
|
customMetric?: SnapshotCustomMetricInput,
|
||||||
afterKey?: BucketKey,
|
afterKey?: BucketKey,
|
||||||
|
@ -70,6 +72,7 @@ export const getData = async (
|
||||||
logQueryFields,
|
logQueryFields,
|
||||||
compositeSize,
|
compositeSize,
|
||||||
condition,
|
condition,
|
||||||
|
logger,
|
||||||
filterQuery,
|
filterQuery,
|
||||||
customMetric,
|
customMetric,
|
||||||
nextAfterKey,
|
nextAfterKey,
|
||||||
|
@ -94,7 +97,9 @@ export const getData = async (
|
||||||
filterQuery,
|
filterQuery,
|
||||||
customMetric
|
customMetric
|
||||||
);
|
);
|
||||||
|
logger.trace(`Request: ${JSON.stringify(request)}`);
|
||||||
const body = await esClient.search<undefined, ResponseAggregations>(request);
|
const body = await esClient.search<undefined, ResponseAggregations>(request);
|
||||||
|
logger.trace(`Response: ${JSON.stringify(body)}`);
|
||||||
if (body.aggregations) {
|
if (body.aggregations) {
|
||||||
return handleResponse(body.aggregations, previousNodes);
|
return handleResponse(body.aggregations, previousNodes);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Logger } from '@kbn/logging';
|
||||||
import { handleEsError } from '../../../../../src/plugins/es_ui_shared/server';
|
import { handleEsError } from '../../../../../src/plugins/es_ui_shared/server';
|
||||||
import { InfraConfig } from '../types';
|
import { InfraConfig } from '../types';
|
||||||
import { GetLogQueryFields } from '../services/log_queries/get_log_query_fields';
|
import { GetLogQueryFields } from '../services/log_queries/get_log_query_fields';
|
||||||
|
@ -31,4 +32,5 @@ export interface InfraBackendLibs extends InfraDomainLibs {
|
||||||
handleEsError: typeof handleEsError;
|
handleEsError: typeof handleEsError;
|
||||||
logsRules: RulesServiceSetup;
|
logsRules: RulesServiceSetup;
|
||||||
metricsRules: RulesServiceSetup;
|
metricsRules: RulesServiceSetup;
|
||||||
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,7 @@ export class InfraServerPlugin implements Plugin<InfraPluginSetup> {
|
||||||
handleEsError,
|
handleEsError,
|
||||||
logsRules: this.logsRules.setup(core, plugins),
|
logsRules: this.logsRules.setup(core, plugins),
|
||||||
metricsRules: this.metricsRules.setup(core, plugins),
|
metricsRules: this.metricsRules.setup(core, plugins),
|
||||||
|
logger: this.logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
plugins.features.registerKibanaFeature(METRICS_FEATURE);
|
plugins.features.registerKibanaFeature(METRICS_FEATURE);
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
|
import type { Logger, LogMeta } from 'kibana/server';
|
||||||
|
import sinon from 'sinon';
|
||||||
import {
|
import {
|
||||||
Comparator,
|
Comparator,
|
||||||
InventoryMetricConditions,
|
InventoryMetricConditions,
|
||||||
|
@ -22,6 +24,21 @@ import { DATES } from './constants';
|
||||||
export default function ({ getService }: FtrProviderContext) {
|
export default function ({ getService }: FtrProviderContext) {
|
||||||
const esArchiver = getService('esArchiver');
|
const esArchiver = getService('esArchiver');
|
||||||
const esClient = getService('es');
|
const esClient = getService('es');
|
||||||
|
const log = getService('log');
|
||||||
|
|
||||||
|
const fakeLogger = <Meta extends LogMeta = LogMeta>(msg: string, meta?: Meta) =>
|
||||||
|
meta ? log.debug(msg, meta) : log.debug(msg);
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
trace: fakeLogger,
|
||||||
|
debug: fakeLogger,
|
||||||
|
info: fakeLogger,
|
||||||
|
warn: fakeLogger,
|
||||||
|
error: fakeLogger,
|
||||||
|
fatal: fakeLogger,
|
||||||
|
log: sinon.stub(),
|
||||||
|
get: sinon.stub(),
|
||||||
|
} as Logger;
|
||||||
|
|
||||||
const baseCondition: InventoryMetricConditions = {
|
const baseCondition: InventoryMetricConditions = {
|
||||||
metric: 'cpu',
|
metric: 'cpu',
|
||||||
|
@ -77,6 +94,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
logQueryFields: void 0,
|
logQueryFields: void 0,
|
||||||
compositeSize: 10000,
|
compositeSize: 10000,
|
||||||
startTime: DATES['8.0.0'].hosts_only.max,
|
startTime: DATES['8.0.0'].hosts_only.max,
|
||||||
|
logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Inventory Threshold Rule Executor', () => {
|
describe('Inventory Threshold Rule Executor', () => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue