-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RCA] [Recent events] Create API endpoint to get events (#192947)
Closes elastic/observability-dev#3924 Closes elastic/observability-dev#3927 This PR introduces an events API (`/api/observability/events`) that will fetch - - All the "point in time" annotations from` observability-annotations` index. This includes both manual and auto (e.g. service deployment) annotations - The annotations will be filtered with supported source fields (host.name, service.name, slo.id, slo.instanceId) when specified as `filter` - Alerts that newly triggered on same source in given time range. The source needs to be specified as `filter`, when no filter is specified all alerts triggered in given time range will be returned ### Testing - Create annotations (APM service deployment annotations and annotations using Observability UI) - Generate some alerts - API call should return annotations and alerts, example API requests - `http://localhost:5601/kibana/api/observability/events?rangeFrom=2024-09-01T19:53:20.243Z&rangeTo=2024-09-19T19:53:20.243Z&filter={"annotation.type":"deployment"}` - `http://localhost:5601/kibana/api/observability/events?rangeFrom=2024-09-01T19:53:20.243Z&rangeTo=2024-09-19T19:53:20.243Z&filter={"slo.id":"*"}` - `http://localhost:5601/kibana/api/observability/events?rangeFrom=2024-09-01T19:53:20.243Z&rangeTo=2024-09-19T19:53:20.243Z&filter={"host.name":"host-0"}` - `http://localhost:5601/kibana/api/observability/events?rangeFrom=2024-09-01T19:53:20.243Z&rangeTo=2024-09-19T19:53:20.243Z` --------- Co-authored-by: kibanamachine <[email protected]> (cherry picked from commit 808212e)
- Loading branch information
1 parent
04e1921
commit 1407654
Showing
14 changed files
with
359 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { z } from '@kbn/zod'; | ||
import { eventSchema } from '../schema'; | ||
|
||
const eventResponseSchema = eventSchema; | ||
|
||
type EventResponse = z.output<typeof eventResponseSchema>; | ||
|
||
export { eventResponseSchema }; | ||
export type { EventResponse }; |
31 changes: 31 additions & 0 deletions
31
packages/kbn-investigation-shared/src/rest_specs/get_events.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { z } from '@kbn/zod'; | ||
import { eventResponseSchema } from './event'; | ||
|
||
const getEventsParamsSchema = z | ||
.object({ | ||
query: z | ||
.object({ | ||
rangeFrom: z.string(), | ||
rangeTo: z.string(), | ||
filter: z.string(), | ||
}) | ||
.partial(), | ||
}) | ||
.partial(); | ||
|
||
const getEventsResponseSchema = z.array(eventResponseSchema); | ||
|
||
type GetEventsParams = z.infer<typeof getEventsParamsSchema.shape.query>; | ||
type GetEventsResponse = z.output<typeof getEventsResponseSchema>; | ||
|
||
export { getEventsParamsSchema, getEventsResponseSchema }; | ||
export type { GetEventsParams, GetEventsResponse }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { z } from '@kbn/zod'; | ||
|
||
const eventTypeSchema = z.union([ | ||
z.literal('annotation'), | ||
z.literal('alert'), | ||
z.literal('error_rate'), | ||
z.literal('latency'), | ||
z.literal('anomaly'), | ||
]); | ||
|
||
const annotationEventSchema = z.object({ | ||
eventType: z.literal('annotation'), | ||
annotationType: z.string().optional(), | ||
}); | ||
|
||
const alertStatusSchema = z.union([ | ||
z.literal('active'), | ||
z.literal('flapping'), | ||
z.literal('recovered'), | ||
z.literal('untracked'), | ||
]); | ||
|
||
const alertEventSchema = z.object({ | ||
eventType: z.literal('alert'), | ||
alertStatus: alertStatusSchema, | ||
}); | ||
|
||
const sourceSchema = z.record(z.string(), z.any()); | ||
|
||
const eventSchema = z.intersection( | ||
z.object({ | ||
id: z.string(), | ||
title: z.string(), | ||
description: z.string(), | ||
timestamp: z.number(), | ||
eventType: eventTypeSchema, | ||
source: sourceSchema.optional(), | ||
}), | ||
z.discriminatedUnion('eventType', [annotationEventSchema, alertEventSchema]) | ||
); | ||
|
||
export { eventSchema }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
x-pack/plugins/observability_solution/investigate_app/server/services/get_alerts_client.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { isEmpty } from 'lodash'; | ||
import { ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types'; | ||
import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; | ||
import { InvestigateAppRouteHandlerResources } from '../routes/types'; | ||
|
||
export type AlertsClient = Awaited<ReturnType<typeof getAlertsClient>>; | ||
|
||
export async function getAlertsClient({ | ||
plugins, | ||
request, | ||
}: Pick<InvestigateAppRouteHandlerResources, 'plugins' | 'request'>) { | ||
const ruleRegistryPluginStart = await plugins.ruleRegistry.start(); | ||
const alertsClient = await ruleRegistryPluginStart.getRacClientWithRequest(request); | ||
const alertsIndices = await alertsClient.getAuthorizedAlertsIndices([ | ||
'logs', | ||
'infrastructure', | ||
'apm', | ||
'slo', | ||
'uptime', | ||
'observability', | ||
]); | ||
|
||
if (!alertsIndices || isEmpty(alertsIndices)) { | ||
throw Error('No alert indices exist'); | ||
} | ||
|
||
type RequiredParams = ESSearchRequest & { | ||
size: number; | ||
track_total_hits: boolean | number; | ||
}; | ||
|
||
return { | ||
search<TParams extends RequiredParams>( | ||
searchParams: TParams | ||
): Promise<InferSearchResponseOf<ParsedTechnicalFields, TParams>> { | ||
return alertsClient.find({ | ||
...searchParams, | ||
index: alertsIndices.join(','), | ||
}) as Promise<any>; | ||
}, | ||
}; | ||
} |
123 changes: 123 additions & 0 deletions
123
x-pack/plugins/observability_solution/investigate_app/server/services/get_events.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import datemath from '@elastic/datemath'; | ||
import { estypes } from '@elastic/elasticsearch'; | ||
import { | ||
GetEventsParams, | ||
GetEventsResponse, | ||
getEventsResponseSchema, | ||
} from '@kbn/investigation-shared'; | ||
import { ScopedAnnotationsClient } from '@kbn/observability-plugin/server'; | ||
import { | ||
ALERT_REASON, | ||
ALERT_RULE_CATEGORY, | ||
ALERT_START, | ||
ALERT_STATUS, | ||
ALERT_UUID, | ||
} from '@kbn/rule-data-utils'; | ||
import { AlertsClient } from './get_alerts_client'; | ||
|
||
export function rangeQuery( | ||
start: number, | ||
end: number, | ||
field = '@timestamp' | ||
): estypes.QueryDslQueryContainer[] { | ||
return [ | ||
{ | ||
range: { | ||
[field]: { | ||
gte: start, | ||
lte: end, | ||
format: 'epoch_millis', | ||
}, | ||
}, | ||
}, | ||
]; | ||
} | ||
|
||
export async function getAnnotationEvents( | ||
params: GetEventsParams, | ||
annotationsClient: ScopedAnnotationsClient | ||
): Promise<GetEventsResponse> { | ||
const response = await annotationsClient.find({ | ||
start: params?.rangeFrom, | ||
end: params?.rangeTo, | ||
filter: params?.filter, | ||
size: 100, | ||
}); | ||
|
||
// we will return only "point_in_time" annotations | ||
const events = response.items | ||
.filter((item) => !item.event?.end) | ||
.map((item) => { | ||
const hostName = item.host?.name; | ||
const serviceName = item.service?.name; | ||
const serviceVersion = item.service?.version; | ||
const sloId = item.slo?.id; | ||
const sloInstanceId = item.slo?.instanceId; | ||
|
||
return { | ||
id: item.id, | ||
title: item.annotation.title, | ||
description: item.message, | ||
timestamp: new Date(item['@timestamp']).getTime(), | ||
eventType: 'annotation', | ||
annotationType: item.annotation.type, | ||
source: { | ||
...(hostName ? { 'host.name': hostName } : undefined), | ||
...(serviceName ? { 'service.name': serviceName } : undefined), | ||
...(serviceVersion ? { 'service.version': serviceVersion } : undefined), | ||
...(sloId ? { 'slo.id': sloId } : undefined), | ||
...(sloInstanceId ? { 'slo.instanceId': sloInstanceId } : undefined), | ||
}, | ||
}; | ||
}); | ||
|
||
return getEventsResponseSchema.parse(events); | ||
} | ||
|
||
export async function getAlertEvents( | ||
params: GetEventsParams, | ||
alertsClient: AlertsClient | ||
): Promise<GetEventsResponse> { | ||
const startInMs = datemath.parse(params?.rangeFrom ?? 'now-15m')!.valueOf(); | ||
const endInMs = datemath.parse(params?.rangeTo ?? 'now')!.valueOf(); | ||
const filterJSON = params?.filter ? JSON.parse(params.filter) : {}; | ||
|
||
const body = { | ||
size: 100, | ||
track_total_hits: false, | ||
query: { | ||
bool: { | ||
filter: [ | ||
...rangeQuery(startInMs, endInMs, ALERT_START), | ||
...Object.keys(filterJSON).map((filterKey) => ({ | ||
term: { [filterKey]: filterJSON[filterKey] }, | ||
})), | ||
], | ||
}, | ||
}, | ||
}; | ||
|
||
const response = await alertsClient.search(body); | ||
|
||
const events = response.hits.hits.map((hit) => { | ||
const _source = hit._source; | ||
|
||
return { | ||
id: _source[ALERT_UUID], | ||
title: `${_source[ALERT_RULE_CATEGORY]} breached`, | ||
description: _source[ALERT_REASON], | ||
timestamp: new Date(_source['@timestamp']).getTime(), | ||
eventType: 'alert', | ||
alertStatus: _source[ALERT_STATUS], | ||
}; | ||
}); | ||
|
||
return getEventsResponseSchema.parse(events); | ||
} |
Oops, something went wrong.