Skip to content

Commit ab43a50

Browse files
Additional context for log threshold rule (#148503)
## Summary Resolves #146348 - Adds new context variables for count and ratio alerts in Log threshold rule - Indexes new context variables to AAD - Adds new context variables to recovered count and ratio alerts The context variables are added based on group by and filter used when creating the rule. When the prefix of `group by` or filter (`WITH` condition) includes one of the following, contextual attributes for that entity are added. - `cloud` - `host` - `orchestrator` - `container` - `labels` - `tags` Following fields are excluded from the context: `*.cpu`, `*.network`, `*.disk`, `*.memory` In case of ratio alerts without `group by`, the prefix from numerator query is considered to add contextual attributes. In both count and ratio alerts, only positive criteria is used from filter (with `is` or `matches` condition) for adding contextual attributes. ### Manual Testing 1. Create different Log threshold rules (Count, Ratio, With group by, Without group by) 2. Configure action template with `{{context}}` 3. Observe additional context to be included in alert notification 4. Let alerts be recovered 5. Observe additional context to be included in recovery notification 6. Observe additional context fields are indexed in AAD ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <[email protected]>
1 parent c30c787 commit ab43a50

File tree

12 files changed

+524
-62
lines changed

12 files changed

+524
-62
lines changed

x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,26 @@ const chartPreviewHistogramBucket = rt.type({
270270
doc_count: rt.number,
271271
});
272272

273+
const AdditionalContext = rt.type({
274+
hits: rt.type({
275+
hits: rt.array(
276+
rt.type({
277+
fields: rt.record(rt.string, rt.array(rt.unknown)),
278+
})
279+
),
280+
}),
281+
});
282+
273283
const ChartPreviewBucketsRT = rt.partial({
274284
histogramBuckets: rt.type({
275285
buckets: rt.array(chartPreviewHistogramBucket),
276286
}),
277287
});
278288

289+
const additionalContextRT = rt.partial({
290+
additionalContext: AdditionalContext,
291+
});
292+
279293
// ES query responses //
280294
const hitsRT = rt.type({
281295
total: rt.type({
@@ -299,7 +313,7 @@ export const UngroupedSearchQueryResponseRT = rt.intersection([
299313
hits: hitsRT,
300314
}),
301315
rt.partial({
302-
aggregations: ChartPreviewBucketsRT,
316+
aggregations: rt.intersection([ChartPreviewBucketsRT, additionalContextRT]),
303317
}),
304318
]),
305319
]);
@@ -320,6 +334,7 @@ export const UnoptimizedGroupedSearchQueryResponseRT = rt.intersection([
320334
doc_count: rt.number,
321335
}),
322336
ChartPreviewBucketsRT,
337+
additionalContextRT,
323338
]),
324339
})
325340
),
@@ -341,7 +356,9 @@ export const OptimizedGroupedSearchQueryResponseRT = rt.intersection([
341356
aggregations: rt.type({
342357
groups: rt.intersection([
343358
rt.type({
344-
buckets: rt.array(rt.intersection([bucketFieldsRT, ChartPreviewBucketsRT])),
359+
buckets: rt.array(
360+
rt.intersection([bucketFieldsRT, ChartPreviewBucketsRT, additionalContextRT])
361+
),
345362
}),
346363
afterKeyRT,
347364
]),

x-pack/plugins/infra/server/lib/alerting/common/utils.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ import type { ElasticsearchClient, IBasePath } from '@kbn/core/server';
1212
import { addSpaceIdToPath } from '@kbn/spaces-plugin/common';
1313
import { ObservabilityConfig } from '@kbn/observability-plugin/server';
1414
import { ALERT_RULE_PARAMETERS, TIMESTAMP } from '@kbn/rule-data-utils';
15-
import { parseTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_technical_fields';
15+
import {
16+
ParsedTechnicalFields,
17+
parseTechnicalFields,
18+
} from '@kbn/rule-registry-plugin/common/parse_technical_fields';
1619
import { ES_FIELD_TYPES } from '@kbn/field-types';
1720
import { set } from '@kbn/safer-lodash-set';
21+
import { ParsedExperimentalFields } from '@kbn/rule-registry-plugin/common/parse_experimental_fields';
1822
import { LINK_TO_METRICS_EXPLORER } from '../../../../common/alerting/metrics';
1923
import { getInventoryViewInAppUrl } from '../../../../common/alerting/metrics/alert_link';
2024
import {
@@ -237,18 +241,17 @@ export const flattenAdditionalContext = (
237241
};
238242

239243
export const getContextForRecoveredAlerts = (
240-
alertHits: AdditionalContext[] | undefined | null
244+
alertHitSource: Partial<ParsedTechnicalFields & ParsedExperimentalFields> | undefined | null
241245
): AdditionalContext => {
242-
const alertHitsSource =
243-
alertHits && alertHits.length > 0 ? unflattenObject(alertHits[0]._source) : undefined;
246+
const alert = alertHitSource ? unflattenObject(alertHitSource) : undefined;
244247

245248
return {
246-
cloud: alertHitsSource?.[ALERT_CONTEXT_CLOUD],
247-
host: alertHitsSource?.[ALERT_CONTEXT_HOST],
248-
orchestrator: alertHitsSource?.[ALERT_CONTEXT_ORCHESTRATOR],
249-
container: alertHitsSource?.[ALERT_CONTEXT_CONTAINER],
250-
labels: alertHitsSource?.[ALERT_CONTEXT_LABELS],
251-
tags: alertHitsSource?.[ALERT_CONTEXT_TAGS],
249+
cloud: alert?.[ALERT_CONTEXT_CLOUD],
250+
host: alert?.[ALERT_CONTEXT_HOST],
251+
orchestrator: alert?.[ALERT_CONTEXT_ORCHESTRATOR],
252+
container: alert?.[ALERT_CONTEXT_CONTAINER],
253+
labels: alert?.[ALERT_CONTEXT_LABELS],
254+
tags: alert?.[ALERT_CONTEXT_TAGS],
252255
};
253256
};
254257

x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts

Lines changed: 129 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
*/
77

88
import {
9-
getPositiveComparators,
10-
getNegativeComparators,
9+
positiveComparators,
10+
negativeComparators,
1111
queryMappings,
1212
buildFiltersFromCriteria,
1313
getUngroupedESQuery,
@@ -155,15 +155,15 @@ const runtimeMappings: estypes.MappingRuntimeFields = {
155155
describe('Log threshold executor', () => {
156156
describe('Comparators', () => {
157157
test('Correctly categorises positive comparators', () => {
158-
expect(getPositiveComparators().length).toBe(7);
158+
expect(positiveComparators.length).toBe(7);
159159
});
160160

161161
test('Correctly categorises negative comparators', () => {
162-
expect(getNegativeComparators().length).toBe(3);
162+
expect(negativeComparators.length).toBe(3);
163163
});
164164

165165
test('There is a query mapping for every comparator', () => {
166-
const comparators = [...getPositiveComparators(), ...getNegativeComparators()];
166+
const comparators = [...positiveComparators, ...negativeComparators];
167167
expect(Object.keys(queryMappings).length).toBe(comparators.length);
168168
});
169169
});
@@ -220,6 +220,7 @@ describe('Log threshold executor', () => {
220220
ignore_unavailable: true,
221221
body: {
222222
track_total_hits: true,
223+
aggregations: {},
223224
query: {
224225
bool: {
225226
filter: [
@@ -290,6 +291,15 @@ describe('Log threshold executor', () => {
290291
},
291292
aggregations: {
292293
groups: {
294+
aggregations: {
295+
additionalContext: {
296+
top_hits: {
297+
_source: false,
298+
fields: ['host.*'],
299+
size: 1,
300+
},
301+
},
302+
},
293303
composite: {
294304
size: 2000,
295305
sources: [
@@ -390,6 +400,15 @@ describe('Log threshold executor', () => {
390400
must_not: [...expectedNegativeFilterClauses],
391401
},
392402
},
403+
aggregations: {
404+
additionalContext: {
405+
top_hits: {
406+
_source: false,
407+
fields: ['host.*'],
408+
size: 1,
409+
},
410+
},
411+
},
393412
},
394413
},
395414
},
@@ -548,6 +567,17 @@ describe('Log threshold executor', () => {
548567
doc_count: 100,
549568
filtered_results: {
550569
doc_count: 10,
570+
additionalContext: {
571+
hits: {
572+
hits: [
573+
{
574+
fields: {
575+
'host.name': ['i-am-a-host-name-1'],
576+
},
577+
},
578+
],
579+
},
580+
},
551581
},
552582
},
553583
{
@@ -558,6 +588,17 @@ describe('Log threshold executor', () => {
558588
doc_count: 100,
559589
filtered_results: {
560590
doc_count: 2,
591+
additionalContext: {
592+
hits: {
593+
hits: [
594+
{
595+
fields: {
596+
'host.name': ['i-am-a-host-name-2'],
597+
},
598+
},
599+
],
600+
},
601+
},
561602
},
562603
},
563604
{
@@ -568,6 +609,17 @@ describe('Log threshold executor', () => {
568609
doc_count: 100,
569610
filtered_results: {
570611
doc_count: 20,
612+
additionalContext: {
613+
hits: {
614+
hits: [
615+
{
616+
fields: {
617+
'host.name': ['i-am-a-host-name-3'],
618+
},
619+
},
620+
],
621+
},
622+
},
571623
},
572624
},
573625
] as GroupedSearchQueryResponse['aggregations']['groups']['buckets'];
@@ -594,6 +646,9 @@ describe('Log threshold executor', () => {
594646
isRatio: false,
595647
reason:
596648
'10 log entries in the last 5 mins for i-am-a-host-name-1, i-am-a-dataset-1. Alert when > 5.',
649+
host: {
650+
name: 'i-am-a-host-name-1',
651+
},
597652
},
598653
},
599654
]);
@@ -617,6 +672,9 @@ describe('Log threshold executor', () => {
617672
isRatio: false,
618673
reason:
619674
'20 log entries in the last 5 mins for i-am-a-host-name-3, i-am-a-dataset-3. Alert when > 5.',
675+
host: {
676+
name: 'i-am-a-host-name-3',
677+
},
620678
},
621679
},
622680
]);
@@ -644,6 +702,17 @@ describe('Log threshold executor', () => {
644702
doc_count: 100,
645703
filtered_results: {
646704
doc_count: 10,
705+
additionalContext: {
706+
hits: {
707+
hits: [
708+
{
709+
fields: {
710+
'host.name': ['i-am-a-host-name-1'],
711+
},
712+
},
713+
],
714+
},
715+
},
647716
},
648717
},
649718
{
@@ -654,6 +723,17 @@ describe('Log threshold executor', () => {
654723
doc_count: 100,
655724
filtered_results: {
656725
doc_count: 2,
726+
additionalContext: {
727+
hits: {
728+
hits: [
729+
{
730+
fields: {
731+
'host.name': ['i-am-a-host-name-2'],
732+
},
733+
},
734+
],
735+
},
736+
},
657737
},
658738
},
659739
{
@@ -664,6 +744,17 @@ describe('Log threshold executor', () => {
664744
doc_count: 100,
665745
filtered_results: {
666746
doc_count: 20,
747+
additionalContext: {
748+
hits: {
749+
hits: [
750+
{
751+
fields: {
752+
'host.name': ['i-am-a-host-name-3'],
753+
},
754+
},
755+
],
756+
},
757+
},
667758
},
668759
},
669760
] as GroupedSearchQueryResponse['aggregations']['groups']['buckets'];
@@ -696,6 +787,17 @@ describe('Log threshold executor', () => {
696787
doc_count: 100,
697788
filtered_results: {
698789
doc_count: 10,
790+
additionalContext: {
791+
hits: {
792+
hits: [
793+
{
794+
fields: {
795+
'host.name': ['i-am-a-host-name-1'],
796+
},
797+
},
798+
],
799+
},
800+
},
699801
},
700802
},
701803
{
@@ -706,6 +808,17 @@ describe('Log threshold executor', () => {
706808
doc_count: 100,
707809
filtered_results: {
708810
doc_count: 2,
811+
additionalContext: {
812+
hits: {
813+
hits: [
814+
{
815+
fields: {
816+
'host.name': ['i-am-a-host-name-2'],
817+
},
818+
},
819+
],
820+
},
821+
},
709822
},
710823
},
711824
{
@@ -716,6 +829,17 @@ describe('Log threshold executor', () => {
716829
doc_count: 100,
717830
filtered_results: {
718831
doc_count: 20,
832+
additionalContext: {
833+
hits: {
834+
hits: [
835+
{
836+
fields: {
837+
'host.name': ['i-am-a-host-name-3'],
838+
},
839+
},
840+
],
841+
},
842+
},
719843
},
720844
},
721845
] as GroupedSearchQueryResponse['aggregations']['groups']['buckets'];

0 commit comments

Comments
 (0)